並行Louvain社群檢測演算法

orion發表於2021-12-12

因為在我最近的科研中需要用到分散式的社群檢測(也稱為圖聚類(graph clustering))演算法,專門去查詢了相關文獻對其進行了學習。下面我們就以這篇論文IPDPS2018的文章[1]為例介紹並行社群檢測演算法。

關於基本的單機/序列社群檢測演算法,大家可以參考我的另一篇部落格《圖資料探勘:社群檢測演算法(一)》(連結:https://www.cnblogs.com/orion-orion/p/15662253.html)。總而言之,目前對於圖的簇/社團劃分,目前最廣泛的測量方法是使用模組性(modularity)。然而模組性的優化是NP完全問題,目前最有名的一種啟發式求解方法是基於貪心準則的Louvain方法。由於其速度和對社群檢測結果的高質量,Louvain方法在序列/單機社群檢測中一直是最常用的方法之一。

目前有許多方式可以對Louvain方法進行並行化。目前最快的基於共享記憶體多執行緒的Louvain方法是Grappolo軟體包[2]。該軟體能夠在812s內在20個核上處理現實世界中大型的網路(soc-friendER; 18億條邊)。

在本篇論文中,作者將這個方法擴充套件到了分散式記憶體領域(和前面基於共享記憶體的領域不同)。而設計一個分散式的Louvain演算法的挑戰在於執行高效的鄰居節點掃描(為了捕捉鄰居節點所屬社群狀態的變化),因為在圖的分散式表示的情況下,通訊開銷會變得非常重要。另一個關鍵的挑戰是我們對哪個社群的狀態進行查詢和更新。序列演算法的好處在於每輪迭代之間有同步操作,然而在分散式的環境下維護與傳播最新的資訊是被禁止的。此外,在分散式環境下一條邊跨幾個程式的處理也是一個棘手的問題。

1. 序列Louvain方法

我們這裡簡要描述一下序列的Louvain方法(詳細描述見另一篇部落格)。Louvain方法是一個多輪的迭代過程,每輪迭代分幾個步驟。第一步,將每個節點視為一個社群,然後遍歷每個節點——其中對於節點\(v\),計算將\(v\)移入各鄰居可能導致的模組性增益\(\Delta Q\),如果最大的增益為正,那麼\(v\)就會從其現在的社群移入該社群;第二步,進行圖重建,即將所有劃分好的社群縮為超節點。這個迭代過程一直持續直到不再獲得模組性的增益(可以人工給定一個閾值\(\tau\))。

該演算法的虛擬碼描述如下(虛擬碼中省略了圖重建的過程):

NLP多工學習

2. 並行Louvain方法

我們開始敘述並行演算法部分。我們使用\(p\)表示程式的數量,編號\(i\)表示區間\([0, p-1]\)內的任意程式編號。

首先,我們將輸入的節點及它們的邊集列表(edge list)(我們採用鄰接表進行儲存)分攤到各程式上,使每個程式接收到的邊數量大致相同(這裡沒有用高階的圖劃分演算法)。每個程式除了它本身擁有的節點集合之外,還儲存了雖然屬於其他程式、但與本程式中節點相關聯的其他節點的拷貝(後面我們統一把這類節點稱為影子節點)。論文采用稀疏行(CSR)的方式儲存點和邊集列表。

類似地,每個程式也獲得一個任意集合的社群(大致使每個程式獲得的社群數量相同),同時也儲存了和本程式的社群之間有關聯邊(incident edges,即“跨邊”)的社群(我們稱為影子社群)。

下面的虛擬碼給出了並行的Louvain演算法的大致框架,它包含了LouvainIteration過程(也就是前面的模組性增益最大化過程)和BuildNextPhaseGraph(對應前面的圖的重構)這兩個過程。注意,LouvainIteration過程的步驟我們還沒有展開,其中包括了程式之間通訊的過程。

NLP多工學習

接下來我們詳細的看LouvainIteration過程和BuildNextPhaseGraph是怎麼實現的。

(a)Louvain Iteration

下面的虛擬碼展示了LouvainIteration過程:

NLP多工學習

在這個過程中,每個程式都擁有節點和社群的集合,而程式之間的通訊主要交換的資訊也是節點和社群之間的資訊。對每個區域性的節點,都會儲存一個社群ID。對每個區域性的社群,關聯度(\(a_c\))也被區域性地儲存。因為節點和程式編號的對映關係沒輪迭代都在變化,每個程式需要儲存其影子節點的列表以及這些影子節點對應的程式。此外,因為在每輪迭代開始時每個節點都在其自身的社群,故初始化的影子社群資訊能夠由節點資訊推匯出來。

下面我們解釋一下LouvainIteration過程的主要步驟:

  • 2行(即過程開始),因為節點到程式的對映在每輪迭代(這裡的迭代是指LouvainIteration過程外的迭代,別搞混了)會改變(由於圖的縮點重構),我們會呼叫ExchangeGhostVertices操作執行一次通訊操作來自交換影子的座標資訊。

  • 4-5行(相當於每輪迭代的開始),論文進行了每輪迭代一次的send-receive通訊步驟來交換對應的影子節點資訊(參照演算法4),send操作指將程式的區域性節點資訊發往以這些節點做為影子節點的其他程式;receive操作指從其他程式獲得影子節點的更新。

  • 7-9行,使用最新的節點資訊來計算所有區域性節點的社群分配。

  • 10-11行(相當於每輪迭代的末尾),我們需要將影子社群已更新的資訊送往它們所屬的程式,並且使各程式的社群接受相關更新的社群。

  • 12-13行,先新的社群狀態計算當前程式子圖的模組性,然後採用AllReduce操作規約得到全域性模組性。

  • 如果網路的模組性增益(\(\Delta Q\))相對於前面的迭代低於需要的一個閾值下限,我們就終止迭代(否則繼續迭代)。

其中,ExchangeGhost函式的定義如下:

NLP多工學習

ExchangeGhost函式中6-7行表示當\(u\)節點的所有者\(owner\)不是當前節點,就將\(u\)增加到列表 \(vmap[owner]\) 中(\(vmap\)做為一個區域性緩衝區,後面在第10行將\(vmap\)發給程式\(j\),然後在第11行接受傳到第\(j\)個程式的影子節點資料)。

(b)Graph reconstruction

下面我們來看圖的重構過程。上一個社群劃分過程中所劃分的社團將在圖的重構過程中被壓縮為單個節點。在一個社群內部的邊會形成自環,對於連線兩個不同社群的“跨邊”會形成連線兩個節點的一條邊(權重為所有"跨邊"之和)。圖的重建過程可以參考下圖:

NLP多工學習

我們可以看到,模組性優化步驟最初始將節點\(\{0, 1, 3\}\)分配給社群\(0\),節點\(2\)分配給社群\(2\),節點\(4\)分配給社群\(4\)(節點\(2\)\(4\)各自成為一個社群)。由於社群ID由節點ID產生,我們認為\(0-2\)號社群屬於(儲存在)程式\(0\)\(3-4\)號社群屬於(儲存在)程式\(1\)。注意,圖例中[Array]表示陣列,<Map>表示字典對映。<Vlocal, C> 字典中儲存了節點編號和社群編號的對映關係;[Edges]陣列中儲存了邊的權重;<Vremote, C>字典儲存了影子節點編號與其所屬社群的對映關係。

然後圖的重建可分為7個步驟:

(1) 每個程式將其區域性社群進行重編號。重編號使用一個關聯原始社群ID和新社群ID的雜湊表<Cold, Cnew>來實現。

(2) 每個程式檢查可能已分配給遠端節點(但和區域性節點已無任何聯絡)的區域性社群的ID。

(3) 基於前\(N\)個程式中區域性社群的數量,為第\(N+1\)個程式的區域性社群進行全域性編號(並行字首和計算)。

(4) 將最新的區域性社群ID和全域性社群ID編號的對映關係(<C, Cnew>) 以AllReduce的方式更新到各個程式上。

(5) 每個程式會檢查其所擁有的節點並構建部分的新的邊集列表。對程式所擁有的每個節點,程式會檢查其鄰居列表,其中和新的社群ID相關聯的鄰居會形成一個自環。

(6) 一旦新的“部分邊集列表”生成,它們就會被重新分配到各程式,然後生成新的圖劃分並調整邊的權重(每個程式會盡可能獲得相同數量的節點)。

(7) 根據新的超節點的帶權鄰接列表<Cnew, <Cnew, weight>>構建新的圖。

參考文獻

  • [1] Ghosh S, Halappanavar M, Tumeo A, et al. Distributed louvain algorithm for graph community detection[C]//2018 IEEE international parallel and distributed processing symposium (IPDPS). IEEE, 2018: 885-895.

  • [2] Lu H, Halappanavar M, Kalyanaraman A. Parallel heuristics for scalable community detection[J]. Parallel Computing, 2015, 47: 19-37.

相關文章