聚類技術---復雜網絡社團檢測
一、實驗內容
復雜網絡是描述復雜系統的有力工具,其中每個實體定義成一個節點, 實體間的交互關系定義為邊。復雜網絡社團結構定義為內緊外松的拓撲結構, 即一組節點的集合,集合內的節點交互緊密,與外界節點交互松散。
- 導入karate.gml中的空手道網絡數據;
- 根據網絡結構特征給出節點相似性度量指標;
- 采用層次聚類過程對網絡數據進行聚類;
- 計算模塊性指標Q值,當Q值最大時輸出聚類結果;
- 采用Cytoscape工具,可視化聚類結果。
二、實驗分析與設計
-
導入karate.gml中的空手道網絡數據;
利用 igraph 庫中的 get_edgelist()函數,取出所有邊的列表, 遍歷列表計算出數據集的鄰接矩陣 A。若𝐴𝑖𝑗 = 1則表示數據集中點 i 和點 j 之間存在一條邊
-
根據網絡結構特征給出節點相似性度量指標;
給定節點 i,其鄰居節點定義為與該節點相連接的所有節點組成
的集合,即N(i) = {𝑗|𝐴𝑖𝑗 = 1,𝑗 = 1,2,…𝑚}。給定一對節點(𝑖 , 𝑗),其 相似性定義為這兩個節點的公共鄰居節點與鄰居節點的並的比值,即:\[similar_{ij}=\frac{|N(i)\cap N(j)|}{|N(i)\cup N(j)|} \]其中相似度度量的分子為兩節點鄰居節點的交集,分母為兩節點鄰居節點的並集的元素個數
-
根據相似度矩陣計算不同種類社團的Q值
定義模塊度量值為:
\[Q_i={\frac{Q_{real}-Q_{null}}{M}}=\frac{1}{2M}\sum_{ij}(a_{ij}-\frac{k_ik_j}{2M})\delta(C_i,C_j) \]其中
\[M=給定的網絡中的邊的數量 \]\[k_i,k_j為節點node_i的degreed(度) \]\[\delta(C_i,C_j),如果Ci和C_j屬於同一個划分,\delta(C_i,C_j)=1,否則=0 \]\[A=[a_{ij}]為鄰接矩陣,所以a_{ij}為鄰接矩陣內的值 \] -
采用層次聚類過程對網絡數據進行聚類,計算模塊性指標Q值,當Q值最大時輸出聚類結果
聚類算法步驟:
- 將所有的節點看作一個單獨的簇
- 遍歷similar矩陣,得到相似度最高的兩個簇
- 選取最高相似度,檢測最高相似的是否高於某個閾值,如果是就結束聚類輸出結果(步驟5)
- 將相似度最高的兩個簇合並,並更新similar矩陣,將合並的兩個簇中的最小的similar值更新為新的簇的值(MAX全鏈),記錄合並的簇
- 計算模塊度量值Q,如果Q值大於當前最大值則輸出分類
-
采用Cytoscape工具,可視化聚類結果。
三、具體實現
-
利用 igraph 庫函數 get_edgelist()導入 karate.gml 數據集,設 m 為數據 集中節點個數,n 為數據集中邊的條數。neighbors 為各個節點的鄰居 節點的集合列表。之后計算鄰接矩陣 A,設置兩層 for 循環來遍歷所有 節點,若第 i 個節點的鄰居節點中有節點 j,則令A[i, j] = 1,且有 A[j, i] = 1。
def compute_A(): A = np.zeros((m, m), dtype=np.int) for i in range(m): for j in range(m): if i == j: break if j in neighbors[i]: A[i][j] = 1 A[j][i] = 1 return A
計算的得到的鄰接矩陣為:
-
根據數據集的節點個數m,建立一個m*m大小的矩陣存放相似度,同時維護兩個 _list P_和_Q_分別存放節點的鄰居節點的交集和並集,遍歷兩個節點的鄰居節點,將其交集和並集的結果存放到 _list P_和_Q_中,最后將由_P_和_Q_計算得到的𝑠𝑖𝑚𝑖𝑙𝑎𝑟,分別將其存放到矩陣的[i, j]及其對稱點[j, i],是的相似度矩陣成為對稱矩陣
def similarity(): # 計算相似度 similar = np.zeros((m, m), dtype=np.float) for i in range(m): for j in range(m): p = [] q = [] for k in range(m): if(i in neighbors[k] and j in neighbors[k]): p.append(k) if(i in neighbors[k] or j in neighbors[k]): q.append(k) l1 = len(p) l2 = len(q) similar[i][j] = l1/l2 similar[j][i] = l1/l2 print('similar矩陣:\n', similar) return similar
-
根據相似度矩陣進行層次聚類,這里采用全鏈聚類,全鏈聚類比單鏈聚類更是適合於現實問題,對離群點和噪聲更不敏感,同時更新相似度矩陣,進行一次分類,返回合並的兩個簇x ,y(這里的x y 為要合並的兩個簇的某個節點,用這兩個節點更新簇的集合)
# ----------------------層次聚類函數--------------------------------------# def cluster(sim): m = len(sim) max_s = 0 x = 0 y = 0 # 選出最大相似度 for i in range(m): for j in range(m): if(sim[i][j] > max_s): x = i y = j max_s = sim[i][j] # 全鏈算法更新相似度矩陣 for i in range(m): sim[x][i] = min(sim[x][i], sim[y][i]) sim[y][i] = min(sim[x][i], sim[y][i]) return x, y
-
計算模塊化量值Q並與閾值進行比較,輸出完成聚類的簇,設分類標簽_clusters_為一維List,clusters[i]的值表示節點__i__屬於簇__clusters[i]__,初始將clusters置為-1,表示所有的點都未分類
同時對於模塊化量值Q的計算,利用sum_cluster和counter 進行計算
# -------------------------模塊化度量值----------------------------------# def init_compute_clusters(clusters, m): # 初始化clusters列表,將clusters的每一位置為-1,表示未分類 for i in range(m): clusters.append(-1) return clusters # 模塊度Q計量 def compute_Q(f, adjacency): Q = 0 for i in range(M): for j in range(M): if f[i] == f[j]: Q += (adjacency[i][j]-(k[i]*k[j]/(2*N)))/(2*N) else: Q += 0 Q_list.append(Q) return Q
-
主函數負責定義初始變量及對聚類函數返回的x ,y 值進行聚類更新及clusters聚類標簽的更新
# -------------------------主函數-----------------------------------# A = compute_A(M) similar = similarity(M) clusters = [] cluster_complete = [] clusters = init_compute_clusters(clusters, M) i = 0 flag = 0 Q = 0 q_max = -1 while(i < 100): x, y = cluster(similar) if clusters[x] != -1 and clusters[y] != -1: # x,y都已經分類,則將x, y的聚類合並成為一個,這里采用合並樹的思想,將相同簇的節點遍歷並進行修改 temp = clusters[y] for j in range(M): if clusters[j] == temp: clusters[j] = clusters[x] if clusters[x] == -1 or clusters[y] == -1: # 兩個簇中至少有一個未分類 if clusters[x] == -1 and clusters[y] == -1: # 兩個簇都未分類 flag += 1 # 新建一個分類保存此簇 clusters[x] = clusters[y] = flag elif clusters[x] == -1: # x未分類y已分類,將x並到y上去 clusters[x] = clusters[y] elif clusters[y] == -1: # x已分類y未分類,將y並到x上去 clusters[y] = clusters[x] Q = compute_Q(clusters, A) if(Q > q_max): # 輸出Q值最大的情況的分類 outputData(clusters, edgelist) q_max = Q print(clusters) i += 1 draw()
-
輸出函數,將分類與無向圖分別保存在graph.csv和class.csv並輸出
def outputData(clusters, edges): # 輸出無向圖的連接關系,輸出分類結果 edge_out = [] graph_file = data_source+'grafh.csv' name1 = ['nodea', 'edgetype', 'nodeb'] for edge in edges: edge_out.append((edge[0]+1, 1, edge[1]+1)) out1 = pd.DataFrame(columns=name1, data=edge_out) out1.to_csv(graph_file) attribute_file = data_source+'class.csv' name2 = ['node', 'class'] attribute_out = [] for i in range(1, M+1): attribute_out.append((i, clusters[i-1])) out2 = pd.DataFrame(columns=name2, data=attribute_out) out2.to_csv(attribute_file)
-
畫圖函數
# ---------------------畫圖函數------------------------------------# def draw(): plt.figure(1) plt.plot(range(len(Q_list)), Q_list, color="b", linewidth=2) plt.xlabel('merge times') plt.ylabel("Q") plt.scatter(range(len(Q_list)), Q_list, linewidths=3, s=3, c='b') f1 = plt.gcf() plt.show()
四、實驗結果
未分類前的karate節點數據:
最終分類結果:
[9, 9, 9, 9, 4, 4, 4, 9, 3, 3, 4, 9, 9, 9, 3, 3, 4, 9, 3, 9, 3, 9, 3, 3, 5, 5, 3, 3, 3, 3, 3,
5, 3, 3]
分類后的簇(使用cytoscape進行可視化)
mergeTimes-Q圖像
五、實驗總結
經過本次實驗我初步理解了基本的層次聚類技術及其應用,更好的掌握了數據挖掘的相關知識與模型,代碼實現較實驗一較為簡單,較好的應用了已學知識,更好的理解了本學習學習的理論知識的應用,同時有機會應用了 igraph 庫和 pandas 庫等很簡潔好用的庫函數,這大大簡化了我的實驗過程。對於分類結果我個人認為符合我的預期,但在定義模塊化量值Q時遇到了很多困難,ppt中的各個參數的意義不明,,最后在與其他同學交流與網絡查詢中基本解決,希望在以后的實驗中可以做的更好