一、自组织竞争学习神经网络模型(无监督学习)
(一)竞争神经网络
在竞争神经网络中,一层是输入层,一层输出层,输出层又称为竞争层或者核心层。在一次输入中,权值是随机给定的,在竞争层每个神经元获胜的概率相同,但是最后会有一个兴奋最强的神经元。兴奋最强的神经元战胜了其他神经元,在权值调整中,其兴奋程度得到了进一步的加强,而其他神经元保持不变,竞争神经网络通过这种竞争学习的方式获取训练样本的分布信息,每个训练样本都对应一个兴奋的核心层神经元,也就是对应一个类别,当有新样本输入时,就可以根据兴奋的神经元进行模式分类。
当有新样本输入时,要进行相似性测量。神经网络是我输入模式可用向量表示,比较两个不同模式的相似性可转化为比较两个向量的距离,因而可用模式向量间的距离作为聚类依据(聚类就是无监督学习时的分类),传统模式识别中常用到的两种聚类判据是欧式距离法和余弦法;
- 欧式距离法:
欧氏距离越小,两个向量越接近,因此越相似。如果对同一类内各个模式向量间的欧氏距离做出规定,不允许超过某一个最大值T,则最大欧氏距离T就成为一种聚类判据,同类模式欧氏距离小于T,两类模式向量的欧式距离大于T。
如图所示:
- 余弦法:描述两个模式向量的另一个常用方法是计算其夹角的余弦,
即:,两个模式向量越接近,其夹角越小,余弦越大。如果对同一类内各个模式向量间的夹角做出规定,不允许超过某一最大角,最这个最大夹角就成为一种聚类判据。同模式向量的夹角小于此最大角,不同模式类的夹角大于此最大角。余弦法适合模式向量长度相同或者模式特征只与向量相关的相似性测量。
如图所示:
- 内积法:描述两个模式向量的常用方法是计算内积。即:
,内积值越大,相似度越高。
不同的相似度会导致所形成的聚类不同,如图所示:
向量归一化:不同的向量有不同的角度和不同的长度,归一化的目的是将向量变成方向不变长度为1的单位向量。二维和三维单位向量可以在单位圆和单位球上直观表示。单位向量进行比较时,只需要比较向量的夹角。向量归一化按下式进行:
,上边有一个小三角的标志,代表归一化以后的向量。
竞争神经网络算法
1、竞争学习规则
在竞争网络中采用的学习规则是胜者为王规则。该算法可分为3个步骤:
- 向量归一化 将输入模式X,和对应的权值向量矩阵归一化处理。
- 寻找获胜神经元 输入一个模式时,竞争层的所有神经元对应的权向量和输入模式进行相似性比较,将相似性最大的权向量判为获胜神经元。使用欧氏距离法时,转化为求两向量点积最大的问题。
- 网络输出和权值调整 胜者为王学习规则规定,获胜神经元输出为1,其余输出为0,只有获胜神经元才有权利调整权向量,其余神经元没有权利调整权向量:
其中,α是学习率,一般随着学习的进展而减小。注意,归一化后的权向量经过调整后得到的新向量不再是单位向量,因此需要对调整后的向量重新归一化。
典型例题:这个例题的计算过程就是竞争学习规则的应用。注意:极坐标3应该是45度。
(二)自组织特征映射网络SOM
SOM网的生物学基础是对于某一图形或某一频率的特定兴奋过程是自组织特征映射网中的竞争机制。也就是,神经网络对特定的模式产生兴奋。
自组织映射网络与竞争神经网络非常相似,神经元都具有竞争性,都采用无监督学习方式。主要区别在于自组织映射网络除了能学习输入样本的分布外,还能够识别输入向量的拓扑结构。
SOM网络共有两层,输入层各神经元通过权向量将外界信息汇集到输出层的各神经元。输入层的形式与BP网相同,节点数与样本维数相等。输出层也是竞争层,神经元的排列有多种形式,如一维线阵、二维平面阵、三维栅格阵,常见的是前两种。
一维线阵:输出层按照一维阵列组织的SOM网是最简单的自组织神经网络,下图中的一维阵列SOM网的输出层只标出相邻神经元间的侧向连接。
二维平面阵:输出按照二维平面组织是SOM网最典型的组织方式,该组织方式更具有大脑皮层的形象。输出层的每个神经元同他周围的其他神经元侧向连接,排列成期盼状平面,结构如图所示:
SOM网采用的算法是在胜者为王算法基础上加以改进而成的,其主要区别在于调整权向量与侧抑制的方式不同。在胜者为王算法中,只有竞争获胜神经元才能调整权向量,其他神经元无权调整。而SOM网的获胜神经元对齐邻近神经元的影响是由远及近的,由兴奋逐渐转变为抑制,因此其学习算法中不仅获胜神经元本身要调整权向量,它周围的神经元在其影响下也要程度不同的调整权向量。这种调整可用下图的三种函数表示,其中b图是a图的两个函数曲线组合而成的。
优胜领域开始定的很大,但是其大小随着训练次数的增加不断收缩,最终收缩到半径为0。
自组织特征神经网络的学习算法
算法流程图:
自组织特征神经网络的设计
SOM网输入层的设计与BP网相似,而输出层的设计以及网络参数的设计比BP网复杂的多,是网络设计的重点。
输出层的设计:
输出层的设计包括两个问题:一个是节点数的设计,一个是节点排列的设计。
节点数与训练集样本有多少模式类有关。如果节点数少于模式类数,则不足以区分全部模式类,训练的结果势必将相近的模式类合并为一类。这种情况相当于将输入样本进行粗分。如果节点数多于模式类,训练的结果一种是将类别分的过细,一种可能出现死节点,即在训练过程中,某个节点从未获胜过,且远离其他获胜节点,因此它的权向量从未得到过调整。在解决分类问题时,如果没有确切的分类数,宁可先设置较多的输出节点,如果分类过细在减小节点数。死节点问题一般可通过重新初始化权值得到解决。
输出层的节点排列成哪种形式取决于实际应用的需要,排列形式应尽量直观反映出实际问题的物理意义。例如,对于旅行路径类的问题,二维平面比较直观;对于一般的分类问题,一个输出节点就能代表一个模式类,用一维线阵意义明确且结构简单;而对于机器人手臂控制问题,按三维栅格排列的输出节点更能反映出手臂的运动轨迹的空间特征。
权值初始化问题:
SOM网权值一半初始化为较小的随机数,这个可以使权向量充分分散在样本空间。但是在某些应用中,样本整体集中在空间的某些局部区域,权值向量却区分在样本空间的广阔区域,训练时离整个样本群近的权值向量被调整,而远离样本群的向量得不到调整。解决这个问题的思路是尽量使权值的初始位置和输入样本群的大致分布区域重合。
根据上述思路,一种简单易行的方法就是从训练集中随机抽出输入样本作为初始权值。另一种办法是先计算全体样本的中心向量,在该中心向量基础上叠加小随机数作为权向量初始值,也可将权向量的初始位置确定在样本群中。
优胜邻域的设计:
优胜邻域的设计原则是使邻域不断缩小,这样输出平面上相邻神经元对应的权向量之间既有区别又有相当的相似性,从而保证当获胜节点对某一类模式产生最大响应时,其临近节点也能产生较大响应。邻域的形状可以是正方形、六边形、圆形。
优胜邻域的大小用邻域半径表示,利用经验公式:
学习率的设计:
学习率在刚开始时,学习率可以取值较大,之后以较快的速度下降,这样有利于很快捕捉到输入向量的大致结构。然后学习率又在较小的值上缓降至0值。表达式:
SOM网的局限性:
- 隐层神经元数目难以确定,因此隐层神经元往往未能充分利用,某些距离学习向量远的神经元不能获胜,从而成为死节点;
- 聚类网络的学习速率需要人为设定,学习终止需要人为控制,影响学习进度;
- 隐层的聚类结果与初始权值有关。
二、python实现
(一)竞争网络实现
# -*- coding: utf-8 -*-
"""
Created on Sat Oct 6 18:40:23 2018
@author: Heisenberg
"""
import numpy as np
import matplotlib.pyplot as plt
#定义激活函数
def sigmoid(x):
return 1/(1+np.exp(-x))
#对一维向量归一化
def normalization(M):
"""
M行向量
"""
M = M/(np.sqrt(np.dot(M,M.T)))
return M
#对矩阵进行归一化
def normalization_all(N):
"""
对矩阵进行归一化
N代表mxn的矩阵
返回一个归一化以后的矩阵
"""
N_all=[]
for i in range(len(N)): #len(N):N的行数
K = normalization(N[i])
N_all.append(K)
return N_all
class competitive_network(object):
"""
竞争神经网络
"""
def __init__(self, num_in, num_out, lr):
"""
初始化参数分别为输入层节点数、输出层或者是竞争层节点数、学习率
"""
w = np.random.rand(num_out, num_in)*(-2) + 1 #随机生成一个权值矩阵,取值范围是[-1,1];
self.w = normalization_all(w)
self.lr = lr
#信号向前传播
def forward_propagation(self, x):
x= x.reshape(1, x.shape[0]) #shape函数,输出一维的值也就是X的行数;
#reshape()是数组对象中的方法,用于改变数组的形状。
#reshape函数创建一个行数为1,列数为X矩阵的行数。
#寻找获胜神经元
y = np.dot(self.w, x.T) #输入矩阵和权值向量的点积
o = sigmoid(y) #神经元输出值
argmax = np.where(o == np.amax(o))[0][0]
return argmax #返回获胜神经元的位置
#反向传播,求权值矩阵;
def back_propagation(self, argmax, x): #x是经过归一化处理的
self.w[argmax] = self.lr * (x - self.w[argmax])
self.w[argmax] = normalization(self.w[argmax])
self.lr -= self.decay #学习率逐渐减小
#训练权重
def train(self, x, num_item): #num_item:迭代次数,x:输入矩阵
x = np.array(x)
self.decay = self.lr/num_item
for i in range(num_item): #迭代一定次数
for j in range(x.shape[0]): #对于输入模式的行数进行循环
ar = self.forward_propagation(x[j])
self.back_propagation(ar, x[j])
for i in range(2):
for j in range(2):
print(self.w[i][j]) #输出权值
def prediction(self, x):
argmax = self.forward_propagation(x)
return argmax
datamat = np.random.rand(100,2) * (-2) + 1 #随机生成的输入矩阵
print(datamat)
#获胜神经元
c = []
CN = competitive_network(2, 2, 0.1) #建立神经网络
CN.train(datamat, 1000) #训练权重向量
for i in range(len(datamat)):
prediction=CN.prediction(datamat[i])
c.append(prediction*1)
print(c) #输出100个输入数据的输出值0或者1;
datamat=normalization_all(datamat)
datamat=np.array(datamat) #参考:https://blog.csdn.net/qq_38237214/article/details/76851107
plt.figure()
plt.scatter(datamat[:,0],datamat[:,1], c=c)
plt.xlim(-2,2)
plt.ylim(-2,2)
plt.show()
计算结果显示:
(二)自组织特征映射神经网络SOM实现
# -*- coding: utf-8 -*-
"""
Created on Sat Oct 6 12:36:18 2018
@author: Heisenberg
"""
import numpy as np
import pylab as pl
#神经网络
class SOM(object):
def __init__(self, X, output, iteration, batch_size):
"""
:param X: 形状是N*D,输入样本有N个,每个D维
:param output: (n,m)一个元组,为输出层的形状是一个n*m的二维矩阵
:param iteration:迭代次数
:param batch_size:每次迭代时的样本数量
初始化一个权值矩阵,形状为D*(n*m),即有n*m权值向量,每个D维
"""
self.X = X
self.output = output
self.iteration = iteration
self.batch_size = batch_size
self.W = np.random.rand(X.shape[1], output[0] * output[1])
print (self.W.shape)
print("权值矩阵:",self.W)
def GetN(self, t):
"""
:param t:时间t, 这里用迭代次数来表示时间
:return: 返回一个整数,表示拓扑距离,时间越大,拓扑邻域越小
"""
a = min(self.output)
return int(a-float(a)*t/self.iteration)
#求学习率
def Geteta(self, t, n):
"""
:param t: 时间t, 这里用迭代次数来表示时间
:param n: 拓扑距离
:return: 返回学习率,
"""
return np.power(np.e, -n)/(t+2)
#更新权值矩阵
def updata_W(self, X, t, winner):
N = self.GetN(t) #表示随时间变化的拓扑距离
for x, i in enumerate(winner):
to_update = self.getneighbor(i[0], N)
for j in range(N+1):
e = self.Geteta(t, j) #表示学习率
for w in to_update[j]:
self.W[:, w] = np.add(self.W[:,w], e*(X[x,:] - self.W[:,w]))
def getneighbor(self, index, N):
"""
:param index:获胜神经元的下标
:param N: 邻域半径
:return ans: 返回一个集合列表,分别是不同邻域半径内需要更新的神经元坐标
"""
a, b = self.output
length = a*b
def distence(index1, index2):
i1_a, i1_b = index1 // a, index1 % b #//:向下取整; %:返回除法的余数;
i2_a, i2_b = index2 // a, index2 % b
return np.abs(i1_a - i2_a), np.abs(i1_b - i2_b) #abs() 函数返回数字的绝对值。
ans = [set() for i in range(N+1)]
for i in range(length):
dist_a, dist_b = distence(i, index)
if dist_a <= N and dist_b <= N: ans[max(dist_a, dist_b)].add(i)
return ans
def train(self):
"""
train_Y:训练样本与形状为batch_size*(n*m)
winner:一个一维向量,batch_size个获胜神经元的下标
:return:返回值是调整后的W
"""
count = 0
while self.iteration > count:
train_X = self.X[np.random.choice(self.X.shape[0], self.batch_size)]
normal_W(self.W)
normal_X(train_X)
train_Y = train_X.dot(self.W)
winner = np.argmax(train_Y, axis=1).tolist()
self.updata_W(train_X, count, winner)
count += 1
return self.W
def train_result(self):
normal_X(self.X)
train_Y = self.X.dot(self.W)
winner = np.argmax(train_Y, axis=1).tolist()
print (winner)
return winner
def normal_X(X):
"""
:param X:二维矩阵,N*D,N个D维的数据
:return: 将X归一化的结果
"""
N, D = X.shape
for i in range(N):
temp = np.sum(np.multiply(X[i], X[i]))
X[i] /= np.sqrt(temp)
return X
def normal_W(W):
"""
:param W:二维矩阵,D*(n*m),D个n*m维的数据
:return: 将W归一化的结果
"""
for i in range(W.shape[1]):
temp = np.sum(np.multiply(W[:,i], W[:,i]))
W[:, i] /= np.sqrt(temp)
return W
#画图
def draw(C):
colValue = ['r', 'y', 'g', 'b', 'c', 'k', 'm']
for i in range(len(C)):
coo_X = [] #x坐标列表
coo_Y = [] #y坐标列表
for j in range(len(C[i])):
coo_X.append(C[i][j][0])
coo_Y.append(C[i][j][1])
pl.scatter(coo_X, coo_Y, marker='x', color=colValue[i%len(colValue)], label=i)
pl.legend(loc='upper right') #图例位置
pl.show()
#数据集:每三个是一组分别是西瓜的编号,密度,含糖量
data = """
1,0.697,0.46,2,0.774,0.376,3,0.634,0.264,4,0.608,0.318,5,0.556,0.215,
6,0.403,0.237,7,0.481,0.149,8,0.437,0.211,9,0.666,0.091,10,0.243,0.267,
11,0.245,0.057,12,0.343,0.099,13,0.639,0.161,14,0.657,0.198,15,0.36,0.37,
16,0.593,0.042,17,0.719,0.103,18,0.359,0.188,19,0.339,0.241,20,0.282,0.257,
21,0.748,0.232,22,0.714,0.346,23,0.483,0.312,24,0.478,0.437,25,0.525,0.369,
26,0.751,0.489,27,0.532,0.472,28,0.473,0.376,29,0.725,0.445,30,0.446,0.459"""
a = data.split(',') #split() 通过指定分隔符对字符串进行切片,就是以逗号为分割依据,进行分割;
dataset = np.mat([[float(a[i]), float(a[i+1])] for i in range(1, len(a)-1, 3)]) #用mat函数转换为矩阵之后可以才进行一些线性代数的操作。
dataset_old = dataset.copy()
print("输入数据:", dataset)
som = SOM(dataset, (5, 5), 1, 30)
som.train()
res = som.train_result()
classify = {}
for i, win in enumerate(res):
if not classify.get(win[0]):
classify.setdefault(win[0], [i])
else:
classify[win[0]].append(i)
C = []#未归一化的数据分类结果
D = []#归一化的数据分类结果
for i in classify.values():
C.append(dataset_old[i].tolist())
D.append(dataset[i].tolist())
draw(C)
draw(D)
计算结果: