日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

社区发现算法——Louvain 算法

發(fā)布時間:2023/12/10 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 社区发现算法——Louvain 算法 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Louvain 算法

原始論文為:《Fast unfolding of communities in large networks》。

所以又被稱為Fast unfolding算法。

Louvain算法是一種基于模塊度的社區(qū)發(fā)現(xiàn)算法。其基本思想是網(wǎng)絡(luò)中節(jié)點(diǎn)嘗試遍歷所有鄰居的社區(qū)標(biāo)簽,并選擇最大化模塊度增量的社區(qū)標(biāo)簽。在最大化模塊度之后,每個社區(qū)看成一個新的節(jié)點(diǎn),重復(fù)直到模塊度不再增大。

首先復(fù)習(xí)下模塊度:


這里引入了權(quán)重方便擴(kuò)展到有權(quán)圖,但其實(shí)對于無權(quán)圖,可以看做所有邊權(quán)重為1,這時候就等于用節(jié)點(diǎn)的度計(jì)算,用度理解一樣。

算法詳述:

  • 模塊度優(yōu)化階段:每個節(jié)點(diǎn)將自己作為自己社區(qū)標(biāo)簽。每個節(jié)點(diǎn)遍歷自己的所有鄰居節(jié)點(diǎn),嘗試將自己的社區(qū)標(biāo)簽更新成鄰居節(jié)點(diǎn)的社區(qū)標(biāo)簽,選擇模塊度增量最大(貪婪思想)的社區(qū)標(biāo)簽,直到所有節(jié)點(diǎn)都不能通過改變社區(qū)標(biāo)簽來增加模塊度

    也就是說,首先將每個節(jié)點(diǎn)指定到唯一的一個社區(qū),然后按順序?qū)⒐?jié)點(diǎn)在這些社區(qū)間進(jìn)行移動。怎么移動呢?假設(shè)有一節(jié)點(diǎn) i ,它有三個鄰居節(jié)點(diǎn) j1, j2, j3,我們分別嘗試將節(jié)點(diǎn) i 移動到 j1, j2, j3 所在的社區(qū),并計(jì)算相應(yīng)的 modularity 變化值ΔQ,哪個變化值最大就將節(jié)點(diǎn) i 移動到相應(yīng)的社區(qū)中去(當(dāng)然,這里我們要求最大的 modularity 變化值要為正,如果變化值均為負(fù),則節(jié)點(diǎn) i 保持不動)。按照這個方法反復(fù)迭代,直到網(wǎng)絡(luò)中任何節(jié)點(diǎn)的移動都不能再改善總的 modularity 值為止。

    移動到一個社區(qū)C中所獲得的模塊化Q增益可以很容易地計(jì)算出來:

    =12m(ki,in?Σtotkim)\frac{1}{2m}(k_{i,in}-{Σ_{tot}ki\over m})2m1?(ki,in??mΣtot?ki?)

    其中ΣinΣ_{in}Σin?是在社區(qū)C內(nèi)的鏈路的權(quán)重總和(權(quán)重為1時就等于度數(shù)),如果是初始情況,即一個節(jié)點(diǎn)作為一個社區(qū)時,它就為這個節(jié)點(diǎn)自己到自己的連接,這時候仍然需要起點(diǎn)加weight,終點(diǎn)加weight(即使這個時候起點(diǎn)和終點(diǎn)為同一節(jié)點(diǎn),對于無環(huán)圖權(quán)為0)

    ΣtotΣ_{tot}Σtot?是關(guān)聯(lián)到C中的節(jié)點(diǎn)的鏈路上的權(quán)重的總和

    ki是關(guān)聯(lián)到節(jié)點(diǎn)i的鏈路的權(quán)重的總和

    ki,in是從節(jié)點(diǎn)i連接到C中的節(jié)點(diǎn)的鏈路的總和

    m是網(wǎng)絡(luò)中的所有鏈路的權(quán)重的總和

  • 網(wǎng)絡(luò)凝聚階段:每個社區(qū)合并為一個新的超級節(jié)點(diǎn),超級節(jié)點(diǎn)的邊權(quán)重為原始社區(qū)內(nèi)所有節(jié)點(diǎn)的邊權(quán)重之和,形成一個新的網(wǎng)絡(luò)

    將第一個階段得到的社區(qū)視為新的“節(jié)點(diǎn)”(一個社區(qū)對應(yīng)一個),重新構(gòu)造子圖,兩個新“節(jié)點(diǎn)”之間邊的權(quán)值為相應(yīng)兩個社區(qū)之間各邊的權(quán)值的總和。如這個圖所示,如果第一個階段得到的社區(qū)有以下三個,那么第二個階段的任務(wù)就是將這三個社分別看一個新的節(jié)點(diǎn),然后將任意兩個新節(jié)點(diǎn)之間的所有連線的權(quán)重相加得到的和,作為這兩個新節(jié)點(diǎn)之間的連線的權(quán)重。

  • 上述兩個階段迭代進(jìn)行,直到模塊度不再增大。
    ??下圖很好的描述了這兩個階段。第一次迭代,經(jīng)過模塊度優(yōu)化階段,總的 modularity 值不再改變,算法將16個節(jié)點(diǎn)劃分成4個社區(qū);在網(wǎng)絡(luò)凝聚階段,4個社區(qū)被凝聚成4個超級節(jié)點(diǎn),并重新更新了邊權(quán)重。之后就進(jìn)入第二次迭代中。

    算法通過引入分步迭代的思想(類比EM算法),避免陷入貪婪思想導(dǎo)致的局部最優(yōu)。

    算法流程:

    1、初始時將每個頂點(diǎn)當(dāng)作一個社區(qū),社區(qū)個數(shù)與頂點(diǎn)個數(shù)相同。
    2、依次將每個頂點(diǎn)與之相鄰頂點(diǎn)合并在一起,計(jì)算它們最大的模塊度增益是否大于0,如果大于0,就將該結(jié)點(diǎn)放入模塊度增量最大的相鄰結(jié)點(diǎn)所在社區(qū)。
    3、迭代第二步,直至算法穩(wěn)定,即所有頂點(diǎn)所屬社區(qū)不再變化。
    4、將各個社區(qū)所有節(jié)點(diǎn)壓縮成為一個結(jié)點(diǎn),社區(qū)內(nèi)點(diǎn)的權(quán)重轉(zhuǎn)化為新結(jié)點(diǎn)環(huán)的權(quán)重,社區(qū)間權(quán)重轉(zhuǎn)化為新結(jié)點(diǎn)邊的權(quán)重。
    5、重復(fù)步驟1-3,直至算法穩(wěn)定。

    可以看到Louvain 算法與FN算法非常相似,我覺得最大的不同時Louvain 算法在凝聚階段是將整個社區(qū)看做一個新的超節(jié)點(diǎn)來看,而FN算法是以社區(qū)的觀點(diǎn)來凝聚。

    算法優(yōu)缺點(diǎn)

    優(yōu)點(diǎn)

    1、時間復(fù)雜度低,適合大規(guī)模的網(wǎng)絡(luò)。
    ??2、社區(qū)劃分結(jié)果穩(wěn)定,有具體指標(biāo)能夠評估社區(qū)劃分好壞。
    ??3、消除了模塊化分辨率的限制。由于模塊度是全局指標(biāo),最大化的時候很難發(fā)現(xiàn)小型的社區(qū),很容易將小型社區(qū)合并。而算法第一次迭代是以單個節(jié)點(diǎn)作為社區(qū)的粒度,規(guī)避了這種問題。
    ??4、天然自帶層次化的社區(qū)劃分結(jié)果,每一次迭代的社區(qū)劃分結(jié)果都可以保留下來,作為社區(qū)劃分的中間結(jié)果,可供選擇。

    缺點(diǎn)

    1、社區(qū)過大,不能及時收斂。如果我們將模塊度類比為損失函數(shù)的話,Fast Unfolding在模塊度優(yōu)化階段采用的貪婪思想很容易將整個社區(qū)劃分“過擬合”。因?yàn)镕ast Unfolding是針對點(diǎn)遍歷,很容易將一些外圍的點(diǎn)加入到原本緊湊的社區(qū)中,導(dǎo)致一些錯誤的合并。這種劃分有時候在局部視角是優(yōu)的,但是全局視角下會變成劣的。
    ??
    ??Python代碼如下:代碼與數(shù)據(jù)下載

    class Louvain:def __init__(self, G):self._G = Gself._m = 0 # 邊數(shù)量 圖會凝聚動態(tài)變化self._cid_vertices = {} # 需維護(hù)的關(guān)于社區(qū)的信息(社區(qū)編號,其中包含的結(jié)點(diǎn)編號的集合)self._vid_vertex = {} # 需維護(hù)的關(guān)于結(jié)點(diǎn)的信息(結(jié)點(diǎn)編號,相應(yīng)的Vertex實(shí)例)for vid in self._G.keys():# 剛開始每個點(diǎn)作為一個社區(qū)self._cid_vertices[vid] = {vid}# 剛開始社區(qū)編號就是節(jié)點(diǎn)編號self._vid_vertex[vid] = Vertex(vid, vid, {vid})# 計(jì)算邊數(shù) 每兩個點(diǎn)維護(hù)一條邊self._m += sum([1 for neighbor in self._G[vid].keys() if neighbor > vid])# 模塊度優(yōu)化階段def first_stage(self):mod_inc = False # 用于判斷算法是否可終止visit_sequence = self._G.keys()# 隨機(jī)訪問random.shuffle(list(visit_sequence))while True:can_stop = True # 第一階段是否可終止# 遍歷所有節(jié)點(diǎn)for v_vid in visit_sequence:# 獲得節(jié)點(diǎn)的社區(qū)編號v_cid = self._vid_vertex[v_vid]._cid# k_v節(jié)點(diǎn)的權(quán)重(度數(shù)) 內(nèi)部與外部邊權(quán)重之和k_v = sum(self._G[v_vid].values()) + self._vid_vertex[v_vid]._kin# 存儲模塊度增益大于0的社區(qū)編號cid_Q = {}# 遍歷節(jié)點(diǎn)的鄰居for w_vid in self._G[v_vid].keys():# 獲得該鄰居的社區(qū)編號w_cid = self._vid_vertex[w_vid]._cidif w_cid in cid_Q:continueelse:# tot是關(guān)聯(lián)到社區(qū)C中的節(jié)點(diǎn)的鏈路上的權(quán)重的總和tot = sum([sum(self._G[k].values()) + self._vid_vertex[k]._kin for k in self._cid_vertices[w_cid]])if w_cid == v_cid:tot -= k_v# k_v_in是從節(jié)點(diǎn)i連接到C中的節(jié)點(diǎn)的鏈路的總和k_v_in = sum([v for k, v in self._G[v_vid].items() if k in self._cid_vertices[w_cid]])delta_Q = k_v_in - k_v * tot / self._m # 由于只需要知道delta_Q的正負(fù),所以少乘了1/(2*self._m)cid_Q[w_cid] = delta_Q# 取得最大增益的編號cid, max_delta_Q = sorted(cid_Q.items(), key=lambda item: item[1], reverse=True)[0]if max_delta_Q > 0.0 and cid != v_cid:# 讓該節(jié)點(diǎn)的社區(qū)編號變?yōu)槿〉米畲笤鲆驵従庸?jié)點(diǎn)的編號self._vid_vertex[v_vid]._cid = cid# 在該社區(qū)編號下添加該節(jié)點(diǎn)self._cid_vertices[cid].add(v_vid)# 以前的社區(qū)中去除該節(jié)點(diǎn)self._cid_vertices[v_cid].remove(v_vid)# 模塊度還能增加 繼續(xù)迭代can_stop = Falsemod_inc = Trueif can_stop:breakreturn mod_inc# 網(wǎng)絡(luò)凝聚階段def second_stage(self):cid_vertices = {}vid_vertex = {}# 遍歷社區(qū)和社區(qū)內(nèi)的節(jié)點(diǎn)for cid, vertices in self._cid_vertices.items():if len(vertices) == 0:continuenew_vertex = Vertex(cid, cid, set())# 將該社區(qū)內(nèi)的所有點(diǎn)看做一個點(diǎn)for vid in vertices:new_vertex._nodes.update(self._vid_vertex[vid]._nodes)new_vertex._kin += self._vid_vertex[vid]._kin# k,v為鄰居和它們之間邊的權(quán)重 計(jì)算kin社區(qū)內(nèi)部總權(quán)重 這里遍歷vid的每一個在社區(qū)內(nèi)的鄰居 因?yàn)檫叡粌牲c(diǎn)共享后面還會計(jì)算 所以權(quán)重/2for k, v in self._G[vid].items():if k in vertices:new_vertex._kin += v / 2.0# 新的社區(qū)與節(jié)點(diǎn)編號cid_vertices[cid] = {cid}vid_vertex[cid] = new_vertexG = collections.defaultdict(dict)# 遍歷現(xiàn)在不為空的社區(qū)編號 求社區(qū)之間邊的權(quán)重for cid1, vertices1 in self._cid_vertices.items():if len(vertices1) == 0:continuefor cid2, vertices2 in self._cid_vertices.items():# 找到cid后另一個不為空的社區(qū)if cid2 <= cid1 or len(vertices2) == 0:continueedge_weight = 0.0# 遍歷 cid1社區(qū)中的點(diǎn)for vid in vertices1:# 遍歷該點(diǎn)在社區(qū)2的鄰居已經(jīng)之間邊的權(quán)重(即兩個社區(qū)之間邊的總權(quán)重 將多條邊看做一條邊)for k, v in self._G[vid].items():if k in vertices2:edge_weight += vif edge_weight != 0:G[cid1][cid2] = edge_weightG[cid2][cid1] = edge_weight# 更新社區(qū)和點(diǎn) 每個社區(qū)看做一個點(diǎn)self._cid_vertices = cid_verticesself._vid_vertex = vid_vertexself._G = Gdef get_communities(self):communities = []for vertices in self._cid_vertices.values():if len(vertices) != 0:c = set()for vid in vertices:c.update(self._vid_vertex[vid]._nodes)communities.append(list(c))return communitiesdef execute(self):iter_time = 1while True:iter_time += 1# 反復(fù)迭代,直到網(wǎng)絡(luò)中任何節(jié)點(diǎn)的移動都不能再改善總的 modularity 值為止mod_inc = self.first_stage()if mod_inc:self.second_stage()else:breakreturn self.get_communities()

    參考結(jié)果如下:

    社區(qū) 1 [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 16, 17, 19, 21]
    社區(qū) 2 [32, 33, 8, 14, 15, 18, 20, 22, 26, 29, 30]
    社區(qū) 3 [23, 24, 25, 27, 28, 31]
    0.388560157790927
    算法執(zhí)行時間0.002000093460083008

    總結(jié)

    以上是生活随笔為你收集整理的社区发现算法——Louvain 算法的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。