在一個網絡裏面,每個樣本只能是屬於一個社區的,那麼這樣的問題就稱爲非重疊社區劃分。html
在非重疊社區劃分算法裏面,有不少的方法:node
基本思想是將社區劃分問題轉換成了模塊度函數的優化,而模塊度是對社區劃分算法結果的一個很重要的衡量標準。git
模塊度函數在實際求解中沒法直接計算獲得全局最優解析解(相似深度神經網絡對應的複雜高維非線性函數),因此一般是採用近似解法,根據求解方法不一樣能夠分爲如下幾種方法:github
1. 凝聚方法(down to top): 經過不斷合併不一樣社區,實現對整個網絡的社區劃分,典型的方法有Newman快速算法,CNM算法和MSG-MV算法; 2. 分裂方法(top to down): 經過不斷的刪除網絡的邊來實現對整個網絡的社區劃分,典型的方法有GN算法; 3. 直接近似求解模塊度函數(近似等價解): 經過優化算法直接對模塊度函數進行求解,典型的方法有EO算法;
undone算法
Relevant Link:網絡
https://www.cnblogs.com/LittleHann/p/9078909.html
在基本思想上,LPA 和 Kmean 本質很是相似,在 LPA 的每輪迭代中,節點被歸屬於哪一個社區,取決於其鄰居中累加權重最大的label(取數量最多的節點列表對應的label是weight=1時的一種特例),而 Kmeans的則是計算和當前節點「最近」的社區,將該節點納入哪一個社區。
可是這兩個算法仍是有細微的區別的:app
1. 首先: Kmeans是基於歐式空間計算節點向量間的距離的,而LPA則是根據節點間的「共有關係」以及「共有關係的強弱程度」來度量度量節點間的距離; 2. 第二點: Kmeasn中節點處在歐式空間中,它假設全部節點之間都存在「必定的關係」,不一樣的距離體現了關係的強弱。可是 LPA 中節點間只有知足「某種共有關係」時,才存在節點間的邊,沒有共有關係的節點是徹底隔斷的,計算鄰居節點的時候也不會計算整個圖結構,而是僅僅計算和該節點有邊鏈接的節點,從這個角度看,LPA 的這個圖結構具備更強的社區型;
劃分結果不穩定,隨機性強是這個算法致命的缺點。具體體如今:異步
1. 更新順序:節點標籤更新順序隨機,可是很明顯,越重要的節點越早更新會加速收斂過程; 2. 隨機選擇:若是一個節點的出現次數最大的鄰居標籤不止一個時,隨機選擇一個標籤做爲本身標籤。這種隨機性可能會帶來一個雪崩效應,即剛開始一個小小的聚類錯誤會不斷被放大。不過話也說話來,若是類似鄰居節點出現多個,多是weight計算的邏輯有問題,須要回過頭去優化weight抽象和計算邏輯;
算法初始化:a、b、c、d各自爲獨立的社區;函數
第一輪標籤傳播:學習
一開始c選擇了a,由於你們的社區標籤都是同樣的,因此隨機選擇了一個;
d也根據本身周圍的鄰居節點來肯定標籤數,最多的是a,因此就是d爲a了;
繼續標籤傳播:以此類推,最後就所有都是a了;
Relevant Link:
https://www.jianshu.com/p/cff65d7595f9 https://arxiv.org/pdf/0709.2938.pdf https://blog.csdn.net/Katherine_hsr/article/details/82343647 http://sighingnow.github.io/%E7%A4%BE%E4%BC%9A%E7%BD%91%E7%BB%9C/community_detection_k_means_clustering.html
第一步:先給每一個節點分配對應標籤,即節點1對應標籤1,節點i對應標籤i;
第二步:遍歷N個節點(for i=1:N),找到對應節點鄰居,獲取此節點鄰居標籤,找到出現次數最大標籤,若出現次數最多標籤不止一個,則隨機選擇一個標籤替換成此節點標籤;
第三步:若本輪標籤重標記後,節點標籤再也不變化(或者達到設定的最大迭代次數),則迭代中止,不然重複第二步
社區圖結構中邊的權重表明了這兩個節點之間的的「關係強弱」,這個關係的定義取決於具體的場景,例如:
1. 兩個DNS域名共享的client ip數量; 2. 兩個微博ID的共同好友數量;
LPA標籤傳播分爲兩種傳播方式,同步更新,異步更新。
同步的意思是實時,即時的意思,每一個節點label更新後當即生效,其餘節點在統計最近鄰社區的時候,永遠取的是當前圖結構中的最新值。
對於節點,在第 t 輪迭代時,根據其所在節點在第t-1代的標籤進行更新。也就是
,其中
表示的就是節點
在第 t 次迭代時的社區標籤。
函數表示的就是取參數節點中社區標籤最多的。
須要注意的是,這種同步更新的方法會存在一個問題,當遇到二分圖的時候,會出現標籤震盪,以下圖:
這種狀況和深度學習中SGD在優化到全局最優勢附近時會圍繞最優勢附近進行布朗運動(震盪)的原理相似。解決的方法就是設置最大迭代次數,提早中止迭代。
異步更新方式能夠理解爲取了一個當前社區的快照信息,基於上一輪迭代的快照信息來進行本輪的標籤更新。
3列分別是:【node_out,node_in,edge_weitght】
import matplotlib.pyplot as plt import pandas as pd import numpy as np import string def loadData(filePath): f = open(filePath) vector_dict = {} edge_dict = {} for line in f.readlines(): lines = line.strip().split(" ") for i in range(2): if lines[i] not in vector_dict: vector_dict[lines[i]] = int(lines[i]) edge_list = [] if len(lines) == 3: edge_list.append(lines[1 - i] + ":" + lines[2]) else: edge_list.append(lines[1 - i] + ":" + "1") edge_dict[lines[i]] = edge_list else: edge_list = edge_dict[lines[i]] if len(lines) == 3: edge_list.append(lines[1 - i] + ":" + lines[2]) else: edge_list.append(lines[1 - i] + ":" + "1") edge_dict[lines[i]] = edge_list return vector_dict, edge_dict if __name__ == '__main__': filePath = './label_data.txt' vector, edge = loadData(filePath) print(vector) print(edge)
初始化時,全部節點都是一個獨立的社區。
# -*- coding: utf-8 -*- import matplotlib.pyplot as plt import pandas as pd import numpy as np import string def loadData(filePath): f = open(filePath) vector_dict = {} edge_dict = {} for line in f.readlines(): lines = line.strip().split(" ") for i in range(2): if lines[i] not in vector_dict: vector_dict[lines[i]] = int(lines[i]) edge_list = [] if len(lines) == 3: edge_list.append(lines[1 - i] + ":" + lines[2]) else: edge_list.append(lines[1 - i] + ":" + "1") edge_dict[lines[i]] = edge_list else: edge_list = edge_dict[lines[i]] if len(lines) == 3: edge_list.append(lines[1 - i] + ":" + lines[2]) else: edge_list.append(lines[1 - i] + ":" + "1") edge_dict[lines[i]] = edge_list return vector_dict, edge_dict def get_max_community_label(vector_dict, adjacency_node_list): label_dict = {} for node in adjacency_node_list: node_id_weight = node.strip().split(":") node_id = node_id_weight[0] node_weight = int(node_id_weight[1]) # 按照label爲group維度,統計每一個label的weight累加和 if vector_dict[node_id] not in label_dict: label_dict[vector_dict[node_id]] = node_weight else: label_dict[vector_dict[node_id]] += node_weight sort_list = sorted(label_dict.items(), key=lambda d: d[1], reverse=True) return sort_list[0][0] def check(vector_dict, edge_dict): for node in vector_dict.keys(): adjacency_node_list = edge_dict[node] # 獲取該節點的鄰居節點 node_label = vector_dict[node] # 獲取該節點當前label label = get_max_community_label(vector_dict, adjacency_node_list) # 從鄰居節點列表中選擇weight累加和最大的label if node_label >= label: continue else: return 0 # 找到weight權重累加和更大的label return 1 def label_propagation(vector_dict, edge_dict): t = 0 print('First Label: ') while True: if (check(vector_dict, edge_dict) == 0): t = t + 1 print('iteration: ', t) # 每輪迭代都更新一遍全部節點的社區label for node in vector_dict.keys(): adjacency_node_list = edge_dict[node] vector_dict[node] = get_max_community_label(vector_dict, adjacency_node_list) else: break return vector_dict if __name__ == '__main__': filePath = './label_data.txt' vector, edge = loadData(filePath) print "load and initial the community...." #print(vector) #print(edge) print "start lpa clustering...." vector_dict = label_propagation(vector, edge) print "ending lpa clustering...." print "the finnal cluster result...." print(vector_dict) cluster_group = dict() for node in vector_dict.keys(): cluster_id = vector_dict[node] print "cluster_id, node", cluster_id, node if cluster_id not in cluster_group.keys(): cluster_group[cluster_id] = [node] else: cluster_group[cluster_id].append(node) print cluster_group
最後獲得的聚類社區爲:
{8: ['15', '9', '8'], 13: ['11', '10', '13', '12', '14'], 6: ['3', '7', '6'], 5: ['1', '0', '2', '5', '4']}
Relevant Link:
https://github.com/GreenArrow2017/MachineLearning/tree/master/MachineLearning/Label%20Propagation https://www.jianshu.com/p/cff65d7595f9
給節點或邊添加權重(勢函數、模塊密度優化、LeaderRank值、局部拓撲信息的類似度、標籤從屬係數等),信息熵等描述節點的傳播優先度。
這樣,在進行鄰居節點的最大標籤統計的時候,能夠將鄰居節點的weight權值等做爲參考因素。
能夠提取一些較爲緊密的子結構來做爲標籤傳播的初始標籤(例如非重疊最小極大團提取算法),或經過初始社區劃分算法先肯定社區的雛形再進行傳播。
Relevant Link:
https://www.cnblogs.com/bethansy/p/6953625.html https://blog.csdn.net/zzz24512653/article/details/26151669