2022年 11月 3日

聚类算法——python实现SOM算法

算法简介
SOM网络是一种竞争学习型的无监督神经网络,将高维空间中相似的样本点映射到网络输出层中的邻近神经元。

训练过程简述:在接收到训练样本后,每个输出层神经元会计算该样本与自身携带的权向量之间的距离,距离最近的神经元成为竞争获胜者,称为最佳匹配单元。然后最佳匹配单元及其邻近的神经元的权向量将被调整,以使得这些权向量与当前输入样本的距离缩小。这个过程不断迭代,直至收敛。

  1. 网络结构:输入层和输出层(或竞争层),如下图所示。
  2. 输入层:假设一个输入样本为X=[x1,x2,x3,…,xn],是一个n维向量,则输入层神经元个数为n个。
  3. 输出层(竞争层):通常输出层的神经元以矩阵方式排列在二维空间中,每个神经元都有一个权值向量。
  4. 假设输出层有m个神经元,则有m个权值向量,Wi = [wi1,wi2,....,win], 1<=i<=m。

è¿éåå¾çæè¿°
算法流程:

  1. 1. 初始化:权值使用较小的随机值进行初始化,并对输入向量和权值做归一化处理 
  2.           X’ = X/||X|| 
  3.           ω’i= ωi/||ωi||, 1<=i<=m 
  4.           ||X||和||ωi||分别为输入的样本向量和权值向量的欧几里得范数。
  5. 2.将样本输入网络:样本与权值向量做点积,点积值最大的输出神经元赢得竞争,
  6. (或者计算样本与权值向量的欧几里得距离,距离最小的神经元赢得竞争)记为获胜神经元。
  7. 3.更新权值:对获胜的神经元拓扑邻域内的神经元进行更新,并对学习后的权值重新归一化。 
  8.         ω(t+1)= ω(t)+ η(t,n) * (x-ω(t))
  9.         η(t,n):η为学习率是关于训练时间t和与获胜神经元的拓扑距离n的函数。
  10.         η(t,n)=η(t)e^(-n)
  11.         η(t)的几种函数图像如下图所示。
  12. 4.更新学习速率η及拓扑邻域N,N随时间增大距离变小,如下图所示。
  13. 5.判断是否收敛。如果学习率η<=ηmin或达到预设的迭代次数,结束算法。

è¿éåå¾çæè¿°

è¿éåå¾çæè¿°
python代码实现SOM
 

  1. import numpy as np
  2. import pylab as pl
  3. class SOM(object):
  4.     def __init__(self, X, output, iteration, batch_size):
  5.         """
  6.         :param X:  形状是N*D, 输入样本有N个,每个D维
  7.         :param output: (n,m)一个元组,为输出层的形状是一个n*m的二维矩阵
  8.         :param iteration:迭代次数
  9.         :param batch_size:每次迭代时的样本数量
  10.         初始化一个权值矩阵,形状为D*(n*m),即有n*m权值向量,每个D维
  11.         """
  12.         self.X = X
  13.         self.output = output
  14.         self.iteration = iteration
  15.         self.batch_size = batch_size
  16.         self.W = np.random.rand(X.shape[1], output[0] * output[1])
  17.         print (self.W.shape)
  18.     def GetN(self, t):
  19.         """
  20.         :param t:时间t, 这里用迭代次数来表示时间
  21.         :return: 返回一个整数,表示拓扑距离,时间越大,拓扑邻域越小
  22.         """
  23.         a = min(self.output)
  24.         return int(a-float(a)*t/self.iteration)
  25.     def Geteta(self, t, n):
  26.         """
  27.         :param t: 时间t, 这里用迭代次数来表示时间
  28.         :param n: 拓扑距离
  29.         :return: 返回学习率,
  30.         """
  31.         return np.power(np.e, -n)/(t+2)
  32.     def updata_W(self, X, t, winner):
  33.         N = self.GetN(t)
  34.         for x, i in enumerate(winner):
  35.             to_update = self.getneighbor(i[0], N)
  36.             for j in range(N+1):
  37.                 e = self.Geteta(t, j)
  38.                 for w in to_update[j]:
  39.                     self.W[:, w] = np.add(self.W[:,w], e*(X[x,:] - self.W[:,w]))
  40.     def getneighbor(self, index, N):
  41.         """
  42.         :param index:获胜神经元的下标
  43.         :param N: 邻域半径
  44.         :return ans: 返回一个集合列表,分别是不同邻域半径内需要更新的神经元坐标
  45.         """
  46.         a, b = self.output
  47.         length = a*b
  48.         def distence(index1, index2):
  49.             i1_a, i1_b = index1 // a, index1 % b
  50.             i2_a, i2_b = index2 // a, index2 % b
  51.             return np.abs(i1_a - i2_a), np.abs(i1_b - i2_b)
  52.         ans = [set() for i in range(N+1)]
  53.         for i in range(length):
  54.             dist_a, dist_b = distence(i, index)
  55.             if dist_a <= N and dist_b <= N: ans[max(dist_a, dist_b)].add(i)
  56.         return ans
  57.     def train(self):
  58.         """
  59.         train_Y:训练样本与形状为batch_size*(n*m)
  60.         winner:一个一维向量,batch_size个获胜神经元的下标
  61.         :return:返回值是调整后的W
  62.         """
  63.         count = 0
  64.         while self.iteration > count:
  65.             train_X = self.X[np.random.choice(self.X.shape[0], self.batch_size)]
  66.             normal_W(self.W)
  67.             normal_X(train_X)
  68.             train_Y = train_X.dot(self.W)
  69.             winner = np.argmax(train_Y, axis=1).tolist()
  70.             self.updata_W(train_X, count, winner)
  71.             count += 1
  72.         return self.W
  73.     def train_result(self):
  74.         normal_X(self.X)
  75.         train_Y = self.X.dot(self.W)
  76.         winner = np.argmax(train_Y, axis=1).tolist()
  77.         print (winner)
  78.         return winner
  79. def normal_X(X):
  80.     """
  81.     :param X:二维矩阵,N*D,N个D维的数据
  82.     :return: 将X归一化的结果
  83.     """
  84.     N, D = X.shape
  85.     for i in range(N):
  86.         temp = np.sum(np.multiply(X[i], X[i]))
  87.         X[i] /= np.sqrt(temp)
  88.     return X
  89. def normal_W(W):
  90.     """
  91.     :param W:二维矩阵,D*(n*m),D个n*m维的数据
  92.     :return: 将W归一化的结果
  93.     """
  94.     for i in range(W.shape[1]):
  95.         temp = np.sum(np.multiply(W[:,i], W[:,i]))
  96.         W[:, i] /= np.sqrt(temp)
  97.     return W
  98. #画图
  99. def draw(C):
  100.     colValue = ['r', 'y', 'g', 'b', 'c', 'k', 'm']
  101.     for i in range(len(C)):
  102.         coo_X = []    #x坐标列表
  103.         coo_Y = []    #y坐标列表
  104.         for j in range(len(C[i])):
  105.             coo_X.append(C[i][j][0])
  106.             coo_Y.append(C[i][j][1])
  107.         pl.scatter(coo_X, coo_Y, marker='x', color=colValue[i%len(colValue)], label=i)
  108.     pl.legend(loc='upper right')
  109.     pl.show()
  110. #数据集:每三个是一组分别是西瓜的编号,密度,含糖量
  111. data = """
  112. 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,
  113. 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,
  114. 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,
  115. 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,
  116. 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,
  117. 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"""
  118. a = data.split(',')
  119. dataset = np.mat([[float(a[i]), float(a[i+1])] for i in range(1, len(a)-1, 3)])
  120. dataset_old = dataset.copy()
  121. som = SOM(dataset, (5, 5), 1, 30)
  122. som.train()
  123. res = som.train_result()
  124. classify = {}
  125. for i, win in enumerate(res):
  126.     if not classify.get(win[0]):
  127.         classify.setdefault(win[0], [i])
  128.     else:
  129.         classify[win[0]].append(i)
  130. C = []#未归一化的数据分类结果
  131. D = []#归一化的数据分类结果
  132. for i in classify.values():
  133.     C.append(dataset_old[i].tolist())
  134.     D.append(dataset[i].tolist())
  135. draw(C)
  136. draw(D)

由于数据比较少,就直接用的训练集做测试了,运行结果图如下,分别是对未归一化的数据和归一化的数据进行的展示。

è¿éåå¾çæè¿° 

è¿éåå¾çæè¿°

——————— 
作者:_almost_ 
来源:CSDN 
原文:https://blog.csdn.net/u014028027/article/details/72458117 
版权声明:本文为博主原创文章,转载请附上博文链接!