1.算法簡介
AP(Affinity Propagation)通常被翻譯為近鄰傳播算法或者親和力傳播算法,是在2007年的Science雜誌上提出的一種新的聚類算法。AP算法的基本思想是將全部數據點都當作潛在的聚類中心(稱之為exemplar),然後數據點兩兩之間連線構成一個網絡(相似度矩陣),再通過網絡中各條邊的消息(responsibility和availability)傳遞計算出各樣本的聚類中心。
2.相關概念(假如有數據點i和數據點j)
(圖1) (圖2) (圖3)
1)相似度: 點j作為點i的聚類中心的能力,記為S(i,j)。一般使用負的歐式距離,所以S(i,j)越大,表示兩個點距離越近,相似度也就越高。使用負的歐式距離,相似度是對稱的,如果采用其他算法,相似度可能就不是對稱的。
2)相似度矩陣:N個點之間兩兩計算相似度,這些相似度就組成了相似度矩陣。如圖1所示的黃色區域,就是一個5*5的相似度矩陣(N=5)
3) preference:指點i作為聚類中心的參考度(不能為0),取值為S對角線的值(圖1紅色標註部分),此值越大,最為聚類中心的可能性就越大。但是對角線的值為0,所以需要重新設置對角線的值,既可以根據實際情況設置不同的值,也可以設置成同一值。一般設置為S相似度值的中值。(有的說設置成S的最小值產生的聚類最少,但是在下面的算法中設置成中值產生的聚類是最少的)
4)Responsibility(吸引度):指點k適合作為數據點i的聚類中心的程度,記為r(i,k)。如圖2紅色箭頭所示,表示點i給點k發送信息,是一個點i選點k的過程。
5)Availability(歸屬度):指點i選擇點k作為其聚類中心的適合程度,記為a(i,k)。如圖3紅色箭頭所示,表示點k給點i發送信息,是一個點k選diani的過程。
6)exemplar:指的是聚類中心。
7)r (i, k)加a (i, k)越大,則k點作為聚類中心的可能性就越大,並且i點隸屬於以k點為聚類中心的聚類的可能性也越大
3.數學公式
1)吸引度叠代公式:
(公式一)
說明1:Rt+1(i,k)表示新的R(i,k),Rt(i,k)表示舊的R(i,k),也許這樣說更容易理解。其中λ是阻尼系數,取值[0.5,1),用於算法的收斂
說明2:網上還有另外一種數學公式:
(公式二)
sklearn官網的公式是:
(公式三)
我試了這兩種公式之後,發現還是公式一的聚類效果最好。同樣的數據都采取S的中值作為參考度,我自己寫的算法聚類中心是5個,sklearn提供的算法聚類中心是十三個,但是如果把參考度設置為p=-50,則我自己寫的算法聚類中心很多,sklearn提供的聚類算法產生標準的3個聚類中心(因為數據是圍繞三個中心點產生的),目前還不清楚這個p=-50是怎麽得到的。
2)歸屬度叠代公式
說明:At+1(i,k)表示新的A(i,k),At(i,k)表示舊的A(i,k)。其中λ是阻尼系數,取值[0.5,1),用於算法的收斂
4.詳細的算法流程
1)設置實驗數據。使用sklearn包中提供的函數,隨機生成以[1, 1], [-1, -1], [1, -1]三個點為中心的150個數據。

def init_sample(): ## 生成的測試數據的中心點 centers = [[1, 1], [-1, -1], [1, -1]] ##生成數據 Xn, labels_true = make_blobs(n_samples=150, centers=centers, cluster_std=0.5, random_state=0) #3數據的長度,即:數據點的個數 dataLen = len(Xn) return Xn,dataLenView Code
2)計算相似度矩陣,並且設置參考度,這裏使用相似度矩陣的中值

def cal_simi(Xn): ##這個數據集的相似度矩陣,最終是二維數組 simi = [] for m in Xn: ##每個數字與所有數字的相似度列表,即矩陣中的一行 temp = [] for n in Xn: ##采用負的歐式距離計算相似度 s =-np.sqrt((m[0]-n[0])**2 + (m[1]-n[1])**2) temp.append(s) simi.append(temp) ##設置參考度,即對角線的值,一般為最小值或者中值 #p = np.min(simi) ##11個中心 #p = np.max(simi) ##14個中心 p = np.median(simi) ##5個中心 for i in range(dataLen): simi[i][i] = p return simiView Code
3)計算吸引度矩陣,即R值。
如果有細心的同學會發現,在上述求R和求A的公式中,求R需要A,求A需要R,所以R或者A不是一開始就可以求解出的,需要先初始化,然後再更新。(我開始就陷入了這個誤區,總覺得公式有問題,囧)

##初始化R矩陣、A矩陣 def init_R(dataLen): R = [[0]*dataLen for j in range(dataLen)] return R def init_A(dataLen): A = [[0]*dataLen for j in range(dataLen)] return A ##叠代更新R矩陣 def iter_update_R(dataLen,R,A,simi): old_r = 0 ##更新前的某個r值 lam = 0.5 ##阻尼系數,用於算法收斂 ##此循環更新R矩陣 for i in range(dataLen): for k in range(dataLen): old_r = R[i][k] if i != k: max1 = A[i][0] + R[i][0] ##註意初始值的設置 for j in range(dataLen): if j != k: if A[i][j] + R[i][j] > max1 : max1 = A[i][j] + R[i][j] ##更新後的R[i][k]值 R[i][k] = simi[i][k] - max1 ##帶入阻尼系數重新更新 R[i][k] = (1-lam)*R[i][k] +lam*old_r else: max2 = simi[i][0] ##註意初始值的設置 for j in range(dataLen): if j != k: if simi[i][j] > max2: max2 = simi[i][j] ##更新後的R[i][k]值 R[i][k] = simi[i][k] - max2 ##帶入阻尼系數重新更新 R[i][k] = (1-lam)*R[i][k] +lam*old_r print("max_r:"+str(np.max(R))) #print(np.min(R)) return RView Code
4)計算歸屬度矩陣,即A值

##叠代更新A矩陣 def iter_update_A(dataLen,R,A): old_a = 0 ##更新前的某個a值 lam = 0.5 ##阻尼系數,用於算法收斂 ##此循環更新A矩陣 for i in range(dataLen): for k in range(dataLen): old_a = A[i][k] if i ==k : max3 = R[0][k] ##註意初始值的設置 for j in range(dataLen): if j != k: if R[j][k] > 0: max3 += R[j][k] else : max3 += 0 A[i][k] = max3 ##帶入阻尼系數更新A值 A[i][k] = (1-lam)*A[i][k] +lam*old_a else : max4 = R[0][k] ##註意初始值的設置 for j in range(dataLen): ##上圖公式中的i!=k 的求和部分 if j != k and j != i: if R[j][k] > 0: max4 += R[j][k] else : max4 += 0 ##上圖公式中的min部分 if R[k][k] + max4 > 0: A[i][k] = 0 else : A[i][k] = R[k][k] + max4 ##帶入阻尼系數更新A值 A[i][k] = (1-lam)*A[i][k] +lam*old_a print("max_a:"+str(np.max(A))) #print(np.min(A)) return AView Code
5)叠代更新R值和A值。終止條件是聚類中心在一定程度上不再更新或者達到最大叠代次數

##計算聚類中心 def cal_cls_center(dataLen,simi,R,A): ##進行聚類,不斷叠代直到預設的叠代次數或者判斷comp_cnt次後聚類中心不再變化 max_iter = 100 ##最大叠代次數 curr_iter = 0 ##當前叠代次數 max_comp = 30 ##最大比較次數 curr_comp = 0 ##當前比較次數 class_cen = [] ##聚類中心列表,存儲的是數據點在Xn中的索引 while True: ##計算R矩陣 R = iter_update_R(dataLen,R,A,simi) ##計算A矩陣 A = iter_update_A(dataLen,R,A) ##開始計算聚類中心 for k in range(dataLen): if R[k][k] +A[k][k] > 0: if k not in class_cen: class_cen.append(k) else: curr_comp += 1 curr_iter += 1 print(curr_iter) if curr_iter >= max_iter or curr_comp > max_comp : break return class_cenView Code
6)根據求出的聚類中心,對數據進行分類
這個步驟產生的是一個歸類列表,列表中的每個數字對應著樣本數據中對應位置的數據的分類

##根據聚類中心劃分數據 c_list = [] for m in Xn: temp = [] for j in class_cen: n = Xn[j] d = -np.sqrt((m[0]-n[0])**2 + (m[1]-n[1])**2) temp.append(d) ##按照是第幾個數字作為聚類中心進行分類標識 c = class_cen[temp.index(np.max(temp))] c_list.append(c)View Code
7)完整代碼及效果圖

from sklearn.datasets.samples_generator import make_blobs import numpy as np import matplotlib.pyplot as plt ''' 第一步:生成測試數據 1.生成實際中心為centers的測試樣本300個, 2.Xn是包含150個(x,y)點的二維數組 3.labels_true為其對應的真是類別標簽 ''' def init_sample(): ## 生成的測試數據的中心點 centers = [[1, 1], [-1, -1], [1, -1]] ##生成數據 Xn, labels_true = make_blobs(n_samples=150, centers=centers, cluster_std=0.5, random_state=0) #3數據的長度,即:數據點的個數 dataLen = len(Xn) return Xn,dataLen ''' 第二步:計算相似度矩陣 ''' def cal_simi(Xn): ##這個數據集的相似度矩陣,最終是二維數組 simi = [] for m in Xn: ##每個數字與所有數字的相似度列表,即矩陣中的一行 temp = [] for n in Xn: ##采用負的歐式距離計算相似度 s =-np.sqrt((m[0]-n[0])**2 + (m[1]-n[1])**2) temp.append(s) simi.append(temp) ##設置參考度,即對角線的值,一般為最小值或者中值 #p = np.min(simi) ##11個中心 #p = np.max(simi) ##14個中心 p = np.median(simi) ##5個中心 for i in range(dataLen): simi[i][i] = p return simi ''' 第三步:計算吸引度矩陣,即R 公式1:r(n+1) =s(n)-(s(n)+a(n))-->簡化寫法,具體參見上圖公式 公式2:r(n+1)=(1-λ)*r(n+1)+λ*r(n) ''' ##初始化R矩陣、A矩陣 def init_R(dataLen): R = [[0]*dataLen for j in range(dataLen)] return R def init_A(dataLen): A = [[0]*dataLen for j in range(dataLen)] return A ##叠代更新R矩陣 def iter_update_R(dataLen,R,A,simi): old_r = 0 ##更新前的某個r值 lam = 0.5 ##阻尼系數,用於算法收斂 ##此循環更新R矩陣 for i in range(dataLen): for k in range(dataLen): old_r = R[i][k] if i != k: max1 = A[i][0] + R[i][0] ##註意初始值的設置 for j in range(dataLen): if j != k: if A[i][j] + R[i][j] > max1 : max1 = A[i][j] + R[i][j] ##更新後的R[i][k]值 R[i][k] = simi[i][k] - max1 ##帶入阻尼系數重新更新 R[i][k] = (1-lam)*R[i][k] +lam*old_r else: max2 = simi[i][0] ##註意初始值的設置 for j in range(dataLen): if j != k: if simi[i][j] > max2: max2 = simi[i][j] ##更新後的R[i][k]值 R[i][k] = simi[i][k] - max2 ##帶入阻尼系數重新更新 R[i][k] = (1-lam)*R[i][k] +lam*old_r print("max_r:"+str(np.max(R))) #print(np.min(R)) return R ''' 第四步:計算歸屬度矩陣,即A ''' ##叠代更新A矩陣 def iter_update_A(dataLen,R,A): old_a = 0 ##更新前的某個a值 lam = 0.5 ##阻尼系數,用於算法收斂 ##此循環更新A矩陣 for i in range(dataLen): for k in range(dataLen): old_a = A[i][k] if i ==k : max3 = R[0][k] ##註意初始值的設置 for j in range(dataLen): if j != k: if R[j][k] > 0: max3 += R[j][k] else : max3 += 0 A[i][k] = max3 ##帶入阻尼系數更新A值 A[i][k] = (1-lam)*A[i][k] +lam*old_a else : max4 = R[0][k] ##註意初始值的設置 for j in range(dataLen): ##上圖公式中的i!=k 的求和部分 if j != k and j != i: if R[j][k] > 0: max4 += R[j][k] else : max4 += 0 ##上圖公式中的min部分 if R[k][k] + max4 > 0: A[i][k] = 0 else : A[i][k] = R[k][k] + max4 ##帶入阻尼系數更新A值 A[i][k] = (1-lam)*A[i][k] +lam*old_a print("max_a:"+str(np.max(A))) #print(np.min(A)) return A ''' 第5步:計算聚類中心 ''' ##計算聚類中心 def cal_cls_center(dataLen,simi,R,A): ##進行聚類,不斷叠代直到預設的叠代次數或者判斷comp_cnt次後聚類中心不再變化 max_iter = 100 ##最大叠代次數 curr_iter = 0 ##當前叠代次數 max_comp = 30 ##最大比較次數 curr_comp = 0 ##當前比較次數 class_cen = [] ##聚類中心列表,存儲的是數據點在Xn中的索引 while True: ##計算R矩陣 R = iter_update_R(dataLen,R,A,simi) ##計算A矩陣 A = iter_update_A(dataLen,R,A) ##開始計算聚類中心 for k in range(dataLen): if R[k][k] +A[k][k] > 0: if k not in class_cen: class_cen.append(k) else: curr_comp += 1 curr_iter += 1 print(curr_iter) if curr_iter >= max_iter or curr_comp > max_comp : break return class_cen if __name__=='__main__': ##初始化數據 Xn,dataLen = init_sample() ##初始化R、A矩陣 R = init_R(dataLen) A = init_A(dataLen) ##計算相似度 simi = cal_simi(Xn) ##輸出聚類中心 class_cen = cal_cls_center(dataLen,simi,R,A) #for i in class_cen: # print(str(i)+":"+str(Xn[i])) #print(class_cen) ##根據聚類中心劃分數據 c_list = [] for m in Xn: temp = [] for j in class_cen: n = Xn[j] d = -np.sqrt((m[0]-n[0])**2 + (m[1]-n[1])**2) temp.append(d) ##按照是第幾個數字作為聚類中心進行分類標識 c = class_cen[temp.index(np.max(temp))] c_list.append(c) ##畫圖 colors = ['red','blue','black','green','yellow'] plt.figure(figsize=(8,6)) plt.xlim([-3,3]) plt.ylim([-3,3]) for i in range(dataLen): d1 = Xn[i] d2 = Xn[c_list[i]] c = class_cen.index(c_list[i]) plt.plot([d2[0],d1[0]],[d2[1],d1[1]],color=colors[c],linewidth=1) #if i == c_list[i] : # plt.scatter(d1[0],d1[1],color=colors[c],linewidth=3) #else : # plt.scatter(d1[0],d1[1],color=colors[c],linewidth=1) plt.show()View Code
叠代11次出結果:
補充說明:這個算法重點在講解實現過程,執行效率不是特別高,有優化的空間。以後我會補充進來
5.sklearn包中的AP算法
1)函數:sklearn.cluster.AffinityPropagation
2)主要參數:
damping : 阻尼系數,取值[0.5,1)
convergence_iter :比較多少次聚類中心不變之後停止叠代,默認15
max_iter :最大叠代次數
preference :參考度
3)主要屬性
cluster_centers_indices_ : 存放聚類中心的數組
labels_ :存放每個點的分類的數組
n_iter_ : 叠代次數
4)示例
preference(即p值)取不同值時的聚類中心的數目在代碼中註明了。

from sklearn.cluster import AffinityPropagation from sklearn import metrics from sklearn.datasets.samples_generator import make_blobs import numpy as np ## 生成的測試數據的中心點 centers = [[1, 1], [-1, -1], [1, -1]] ##生成數據 Xn, labels_true = make_blobs(n_samples=150, centers=centers, cluster_std=0.5, random_state=0) simi = [] for m in Xn: ##每個數字與所有數字的相似度列表,即矩陣中的一行 temp = [] for n in Xn: ##采用負的歐式距離計算相似度 s =-np.sqrt((m[0]-n[0])**2 + (m[1]-n[1])**2) temp.append(s) simi.append(temp) p=-50 ##3個中心 #p = np.min(simi) ##9個中心, #p = np.median(simi) ##13個中心 ap = AffinityPropagation(damping=0.5,max_iter=500,convergence_iter=30, preference=p).fit(Xn) cluster_centers_indices = ap.cluster_centers_indices_ for idx in cluster_centers_indices: print(Xn[idx])View Code
6.AP算法的優點
1) 不需要制定最終聚類族的個數
2) 已有的數據點作為最終的聚類中心,而不是新生成一個族中心。
3)模型對數據的初始值不敏感。
4)對初始相似度矩陣數據的對稱性沒有要求。
5).相比與k-centers聚類方法,其結果的平方差誤差較小。
7.AP算法的不足
1)AP算法需要事先計算每對數據對象之間的相似度,如果數據對象太多的話,內存放不下,若存在數據庫,頻繁訪問數據庫也需要時間。
2)AP算法的時間復雜度較高,一次叠代大概O(N3)
3)聚類的好壞受到參考度和阻尼系數的影響。
Tags: 對角線 可能性 親和力 黃色 能力
文章來源: