基于SOM算法的Iris数据分类
基于SOM算法的Iris數據分類
自組織特征映射神經網絡SOM(Self-Organizing Feature Map)是一種無監督學習算法,不同于一般神經網絡基于損失函數的優化訓練,SOM是運用競爭學習策略來逐步優化網絡的。SOM算法作為一種優良的聚類工具,具有無需監督,能自動對輸入模式進行聚類的優點,目前已經得到了廣泛的應用。本文利用SOM算法,最終實現了對Iris數據集的分類。
一、 算法介紹
1.1 SOM算法
自組織映射神經網絡(self-organizing map,SOM)是20世紀80年代 Kohonen教授在研究聯想記憶和自適應學習機器的基礎上發展起來的,早期叫做自組織特征映射(self-organizing feature map即SOFM)。后來,人們發現這種學習機器和大腦皮層上的分區組織現象有很多相似性,所以也經常從對大腦功能數學模擬的角度來討論SOM網絡。
SOM是一種無監督的人工神經網絡。不同于一般神經網絡基于損失函數的反向傳遞來訓練,它運用競爭學習(competitive learning)策略,依靠神經元之間互相競爭逐步優化網絡,且使用近鄰關系函數(neighborhood function)來維持輸入空間的拓撲結構。維持輸入空間的拓撲結構:意味著二維映射包含了數據點之間的相對距離。輸入空間中相鄰的樣本會被映射到相鄰的輸出神經元。
由于SOM是基于無監督學習的,這就意味著訓練階段將不需要人工介入(即不需要樣本標簽),我們可以在不知道類別的情況下,對數據進行聚類;可以識別針對某問題具有內在關聯的特征。
1.2 算法特點
SOM作為一種廣泛應用的算法,具有著其自獨特的優勢:
(1) 神經網絡,競爭學習策略
(2) 無監督學習,不需要額外標簽
(3) 適合高維數據的可視化,能夠維持輸入空間的拓撲結構
(4) 具有很高的泛化能力,甚至能識別之前從沒遇過的輸入樣本
二、 相關理論概述
2.1 SOM網絡結構
SOM網絡結構比較簡單,只有輸入層和輸出層(輸出層我們通常也稱為競爭層),輸入層神經元的數量是由輸入向量的維度決定的,一個神經元對應一個特征,SOM網絡結構的區別主要在競爭層:可以的有一維和二維的(競爭層也可以有更高的維度。不過出于可視化的目的,高維競爭層用的比較少)。其中,二維平面有2種平面結構:①Rectangular;②Hexagonal
在競爭層,神經元可以排列形成多種形式。每個樣本都可以映射到與其距離最小的競爭層神經元上,最終在競爭層生成一個低維、離散的映射。競爭層SOM神經元的數量決定了最終模型的粒度與規模,這對最終模型的準確性與泛化能力影響很大。結構如下圖所示:
與前饋型神經網路不同,SOM網絡的神經元節點都在同一層上,在一個平面上呈規則排列。常見的排列形式包括方形網格排列或蜂窩狀排列。樣本特征向量的每一維都通過一定的權重輸入到SOM網絡的每一個節點上。
自組織映射網絡的神經元節點之間并沒有直接的連接,但是,在神經元平面上相鄰的節點間在學習(訓練)過程中有一定的相互影響,構成領域相互作用。
神經元節點的計算功能就是對輸入的樣本給出響應。輸入向量連接到某個節點的權值組成的向量稱作該節點的權值向量。一個節點對輸入樣本的響應強度,就是該節點的權值向量與輸入向量的匹配程度,可以用歐式距離或內積來計算,如果距離小或內積大則響應強度大。對一個輸入樣本,在神經元平面上所有的節點中響應最大的節點稱作獲勝節點(winner)。
最終,可以將SOM算法的結構歸納為:①輸入層:接收外界信息,將輸入模式向競爭層傳遞,起“觀察”作用;②競爭層:負責對輸入模式進行“分析比較”,尋找規律并歸類。SOM完整網絡結構如下圖所示:
2.2 算法原理
自組織映射網絡的學習過程比較簡單,基本算法如下:
設X={x∈R^d}是d維樣本向量集合,記所有神經元集合為A,第i個每個神經元的權重為m_i:
(1)權值初始化:用小隨機數初始化權重向量。注意各個節點的初始權值不能相等;
(2)在時刻t,按照給定的順序或隨機順序加入一個樣本。記為x(t);
(3)計算神經元響應,找到當前獲勝節點c。如用歐氏距離作為匹配準則,則獲勝節點為:
(4)權值競爭學習。對所有神經元節點,用下述準則更新各自的權值:
其中,α(t)是學習的步長,d[.,.]是兩個向量間的歐式距離,h_ci (t)是節點i與c之間的近鄰函數值,如果采用方形網格結構,則相當于在節點c的周圍定義一個矩形領域范圍N_c (t),在該領域內h_ci (t)為1、否則為0,即權值按照以下規則更新:
(5)更新步長α(t)和領域N_c (t),如達到終止條件,則算法停止;否則置t=t+1,繼續步驟(2)
因此,總的來說競爭學習的步驟是:①向量歸一化②尋找獲勝神經元③網絡輸出與權值調整步驟④完成后回到步驟①繼續訓練,直到學習率衰減到0。其中,學習率α?(0,1],一般隨著學習的進展而減小,即調整的程度越來越小,神經元(權重)趨于聚類中心。
2.3 理論推導
(1)尋找獲勝神經元
網絡的輸出神經元之間相互競爭以求被激活,結果在每一時刻只有一個輸出神經元被激活。這個被激活的神經元稱為競爭獲勝神經元,而其它神經元的狀態被抑制,故稱為Winner Take All。
首先,對網絡當前輸入模式向量X和競爭層中各神經元對應的權重向量W_j全部進行歸一化,使得X和W_j模為1。當網絡得到一個輸入模式向量X時,競爭層的所有神經元對應的權重向量均與其進行相似性比較,并將最相似的權重向量判為競爭獲勝神經元。前面剛說過,歸一化后,相似度最大就是內積最大:
也就是在單位圓(2D情況)中找到夾角最小的點,即:
(2)neighborhood function
neighborhood函數用來確定優勝節點對其近鄰節點的影響強弱,即優勝鄰域中每個節點的更新幅度。最常見的選擇是高斯函數,它可以表征優勝鄰域內,影響強弱與距離的關系。
其中,winner是優勝節點在輸出平面的坐標;sigma確定鄰域范圍,且sigma越大,鄰域范圍越大。但是sigma必須大于0,否則沒有神經元會被更新,且sigma不能大于2維輸出平面的邊長。
(3)Bubble近鄰函數
Bubble函數能很好的在計算成本與高斯核的準確性之間平衡,我們可以用bubble函數很好地去近似估計高斯,bubble在優勝節點的鄰域范圍內是個常數,因此,鄰域內的所有神經元更新的幅度是相同的。
三、 實驗結果
我們本次實驗是對Iris數據集利用SOM算法進行分類,下圖為SOM算法在Iris數據集上的分類情況:
為了能夠更加清晰的觀察到SOM算法對Iris數據集的分類效果,我們對最后的輸出結果進行了圖層可視化。
首先,我們利用了U-Matrix矩陣來對結果就行可視化。根據權重矩陣W,我們可以計算每個神經元距離它的鄰近神經元們的距離,計算好的矩陣就是U-Matrix矩陣。這樣我們就能夠看到訓練樣本都分別落在輸出層的哪些格子和它們對應的標簽信息。可視化結果如下圖所示:
我們可以看到,三種類別的樣本落在了輸出平面上的不同位置,并且這條分界線大致將藍色樣本與橙色、綠色樣本劃分開了。
雖然上述的可視化圖案能讓我們看到樣本的大致分布,并且具有較好視覺效果。但仍有兩個不足之處:①不清楚每個格子的樣本數量具體是多少②如果有個類別落到同一個格子,看不出該類別的比例。
因此,我們對上述可視化進行了改進,我們決定在每個格子里畫餅圖,且用顏色表示類別,用數字表示總樣本數量,這樣可以更為清楚的可視化Iris數據集的分類結果。改進的可視化結果如下圖所示:
四、 總結
Iris數據集比較簡單,我們的分類器基本上可以在測試集上達得96的F1-score。從上述實驗結果看來,它將相鄰關系強加在簇質心上,所以,互為鄰居的簇之間比非鄰居的簇之間更相關。這種聯系有利于聚類結果的解釋和可視化。最后,由于SOM算法提出的比較早,因此我個人認為它還是具有很大的發展空間的。
五、 代碼實現
GitHub:https://github.com/DeepVegChicken/Learning-Som_IrisClassification
運行環境:
python 3.7.9
numpy 1.19.1
minisom 2.2.7
scikit-learn 0.23.2
matplotlib 3.3.1
SOM算法的單獨實現:
import numpy as np import pylab as plclass 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 = Xself.output = outputself.iteration = iterationself.batch_size = batch_sizeself.W = np.random.rand(X.shape[1], output[0] * output[1])print(self.W.shape)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.outputlength = a * bdef distence(index1, index2):i1_a, i1_b = index1 // a, index1 % bi2_a, i2_b = index2 // a, index2 % breturn np.abs(i1_a - i2_a), np.abs(i1_b - i2_b)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 ansdef train(self):"""train_Y:訓練樣本與形狀為batch_size*(n*m)winner:一個一維向量,batch_size個獲勝神經元的下標:return:返回值是調整后的W"""count = 0while 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 += 1return self.Wdef 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 winnerdef normal_X(X):""":param X:二維矩陣,N*D,N個D維的數據:return: 將X歸一化的結果"""N, D = X.shapefor i in range(N):temp = np.sum(np.multiply(X[i], X[i]))X[i] /= np.sqrt(temp)return Xdef 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(',') dataset = np.mat([[float(a[i]), float(a[i + 1])] for i in range(1, len(a) - 1, 3)]) dataset_old = dataset.copy()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)基于SOM的Iris數據集分類:
import math import numpy as np from minisom import MiniSom from sklearn import datasets from numpy import sum as npsum from sklearn.metrics import classification_report from sklearn.model_selection import train_test_splitimport matplotlib.pyplot as plt from matplotlib.patches import Patch from matplotlib.gridspec import GridSpec# 分類函數 def classify(som,data,winmap):default_class = npsum(list(winmap.values())).most_common()[0][0]result = []for d in data:win_position = som.winner(d)if win_position in winmap:result.append(winmap[win_position].most_common()[0][0])else:result.append(default_class)return result# 可視化 def show(som):""" 在輸出層畫標簽圖案 """plt.figure(figsize=(16, 16))# 定義不同標簽的圖案標記markers = ['o', 's', 'D']colors = ['C0', 'C1', 'C2']category_color = {'setosa': 'C0', 'versicolor': 'C1', 'virginica': 'C2'}# 背景上畫U-Matrixheatmap = som.distance_map()# 畫背景圖plt.pcolor(heatmap, cmap='bone_r')for cnt, xx in enumerate(X_train):w = som.winner(xx)# 在樣本Heat的地方畫上標記plt.plot(w[0] + .5, w[1] + .5, markers[Y_train[cnt]], markerfacecolor='None',markeredgecolor=colors[Y_train[cnt]], markersize=12, markeredgewidth=2)plt.axis([0, size, 0, size])ax = plt.gca()# 顛倒y軸方向ax.invert_yaxis()legend_elements = [Patch(facecolor=clr, edgecolor='w', label=l) for l, clr in category_color.items()]plt.legend(handles=legend_elements, loc='center left', bbox_to_anchor=(1, .95))plt.show()""" 在每個格子里畫餅圖,且用顏色表示類別,用數字表示總樣本數量 """plt.figure(figsize=(16, 16))the_grid = GridSpec(size, size)for position in winmap.keys():label_fracs = [winmap[position][label] for label in [0, 1, 2]]plt.subplot(the_grid[position[1], position[0]], aspect=1)patches, texts = plt.pie(label_fracs)plt.text(position[0] / 100, position[1] / 100, str(len(list(winmap[position].elements()))),color='black', fontdict={'weight': 'bold', 'size': 15}, va='center', ha='center')plt.legend(patches, class_names, loc='center right', bbox_to_anchor=(-1, 9), ncol=3)plt.show()if __name__ == '__main__':# 導入數據集iris = datasets.load_iris()# 提取iris數據集的標簽與數據feature_names = iris.feature_namesclass_names = iris.target_namesX = iris.dataY = iris.target# 劃分訓練集、測試集 7:3X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.3, random_state=0)# 樣本數量N = X_train.shape[0]# 維度/特征數量M = X_train.shape[1]# 最大迭代次數max_iter = 200# 經驗公式:決定輸出層尺寸size = math.ceil(np.sqrt(5 * np.sqrt(N)))print("訓練樣本個數:{} 測試樣本個數:{}".format(N, X_test.shape[0]))print("輸出網格最佳邊長為:", size)# 初始化模型som = MiniSom(size, size, M, sigma=3, learning_rate=0.5, neighborhood_function='bubble')# 初始化權值som.pca_weights_init(X_train)# 模型訓練som.train_batch(X_train, max_iter, verbose=False)# 利用標簽信息,標注訓練好的som網絡winmap = som.labels_map(X_train, Y_train)# 進行分類預測y_pred = classify(som, X_test, winmap)# 展示在測試集上的效果print(classification_report(Y_test, np.array(y_pred)))# 可視化show(som)總結
以上是生活随笔為你收集整理的基于SOM算法的Iris数据分类的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: TenorFlowJS-激活函数
- 下一篇: 软件名称介绍