Louvain社区划分算法及Java语言实现
Louvain社區劃分算法及Java語言實現
- 社區劃分算法處理的對象
- Louvain社區發現算法
- 全局模塊度
- 單層算法過程
- 多層算法過程
- Java代碼實現
- 圖實現
- 模塊度計算
- 單層louvain實現
- 多層louvain實現
- 運行入口,使用方法
社區劃分算法處理的對象
社區劃分算法又稱社區發現算法,針對網絡數據結構,把聯系緊密度比較高的節點劃分為一個組。一個社區可以代表“物以類聚人以群分”、“家族”、“流量”、“屬性相似”等概念,有很大的應用前景。
Louvain社區發現算法
Louvain社區發現算法是一種基于模塊度的社區發現算法。模塊度是一種評價網絡劃分好壞的指標,后續會詳細介紹。Louvain算法最終目標是得到最優的模塊度
全局模塊度
對于圖中任意兩點i,j
(為了寫方便,和圖中符號有點差異)
含義:
缺點:全圖計算,運算速度較慢
單層算法過程
過程A:
為圖中每個節點生成社區編號,如果有n個節點就有n個社區。
如圖所示,為A~J節點分配了10個社區編號,這里用10中顏色表示
處理一個節點,它的社區編號依次分配為相鄰節點的社區編號。計算模塊度,選擇模塊度最大且為△Q為正的社區編號(一個節點的社區編號由相鄰節點決定)
舉個例子來說,分別把A的社區號分配為C,D,E的社區號(圖中橙色、黃色和綠色),分別計算模塊度。由于例子中的圖十分對稱,且邊權重為1,所以三種情況的模塊度Q都是相同的,但都比A自己一個社區好,這時顏色隨便選一個就可以(程序上的比較法工廠選擇第一個)。但如果網絡拓撲結構比較復雜,情況會有所不同。
遍歷所有節點,重復步驟2,完成一輪(后面的社區編號分配會依賴前面的結果)
過程B:重復過程A,直到所有節點社區編號不再變化(收斂)
因為每一輪的結果可能不同,也就是不穩定。可以重復直到所有節點社區編號都不變化或者一定比例的節點社區編號不變化。因為圖如果很大,有時為了提升效率這么做。
多層算法過程
運行完過程A,B后可以把一個社區的節點融合成為一個節點,對新的圖形結構再次進行單層劃分,即完成了第二層。
如圖中所示,節點融合時,忽略社區內部的邊(不考慮指向自身的邊),社區之間的邊合并(權重合并),然后對新的圖進行單層louvain即可。
可以重復達到n層,層數越多,社區數量越少,效果更好,但時間也會增加。因此要選擇合適的層數。
Java代碼實現
參考我的實現:https://github.com/ghostorsoul/louvain這是一種基于內存的單線程計算方法
圖實現
模塊度計算
單層louvain實現
/*** 單層louvain社區劃分算法** @return 社區劃分結果*/public CommunityInfo findCommunitiesSingleLevel() {while (true) {int[] copy = nodeCommunityNo.clone();double Q = getModuleQ();if (Double.isNaN(Q)) {break;}System.out.println(String.format("initQ: %s", Q));for (int id1 : graph.getNodes()) {System.out.println("deal id: " + id1);int bestCommunityNo = -1;double deltaQ = 0.0;int id1OriginNo = nodeCommunityNo[id1];for (int id2 : graph.getBothNodes(id1)) {if (c(id1, id2) == 1) {continue;}nodeCommunityNo[id1] = nodeCommunityNo[id2];double currentQ = getModuleQ();if (Double.isNaN(currentQ)) {continue;}System.out.println(String.format("currentQ: %s", currentQ));if (currentQ - Q > 0.00001 && currentQ - Q > deltaQ) {deltaQ = currentQ - Q;bestCommunityNo = nodeCommunityNo[id2];}}if (bestCommunityNo == -1) {nodeCommunityNo[id1] = id1OriginNo;System.out.println(String.format("no best communityNo. id: %s", id1));} else {nodeCommunityNo[id1] = bestCommunityNo;System.out.println(String.format("set best communityNo. id: %s, comm: %s", id1, bestCommunityNo));Q = getModuleQ();}}if (Arrays.equals(nodeCommunityNo, copy)) {break;}}Map<Integer, List<Integer>> map = new HashMap<>();for (int i = 0; i < nodeCommunityNo.length; i++) {int commNum = nodeCommunityNo[i];List<Integer> integers = map.get(commNum);if (integers == null) {integers = new ArrayList<>();map.put(commNum, integers);}integers.add(i);}CommunityInfo communityInfo = new CommunityInfo();communityInfo.communitiesNo = map.size();communityInfo.communityNodeIds = new int[map.size()][];AtomicInteger nextCommNum = new AtomicInteger(0);map.forEach((commNum, ids) -> {communityInfo.communityNodeIds[nextCommNum.get()] = new int[ids.size()];for (int i = 0; i < ids.size(); i++) {communityInfo.communityNodeIds[nextCommNum.get()][i] = ids.get(i);}nextCommNum.incrementAndGet();});communityInfo.nodeCommunityNo = new int[nodeCommunityNo.length];for (int c = 0; c < communityInfo.communityNodeIds.length; c++) {for (int nodeId : communityInfo.communityNodeIds[c]) {communityInfo.nodeCommunityNo[nodeId] = c;}}return communityInfo;}多層louvain實現
/*** 多層louvain社區劃分算法** @param level 層數* @return 社區劃分結果*/public CommunityInfo findCommunitiesMultiLevel(int level) {CommunityInfo[] levelResult = new CommunityInfo[level + 1];Graph currentGraph = graph;while (level > 0) {System.out.println("current level: " + level);CommunityInfo communityInfo;if (currentGraph == this.graph) {communityInfo = findCommunitiesSingleLevel();} else {communityInfo = new LouvainCalculator(currentGraph).findCommunitiesSingleLevel();}levelResult[level] = communityInfo;System.out.println(String.format("levelResult: %s, level: %s", communityInfo, level));Graph newGraph = new Graph();for (int c1 = 0; c1 < communityInfo.communitiesNo; c1++) {boolean ac = true;for (int c2 = 0; c2 < communityInfo.communitiesNo; c2++) {if (c1 == c2) {continue;}int[] c1NodeIds = communityInfo.communityNodeIds[c1];int[] c2NodeIds = communityInfo.communityNodeIds[c2];List<Link> links = new ArrayList<>();for (int c1OneNode : c1NodeIds) {for (int c2OneNode : c2NodeIds) {Link tempLink = currentGraph.getLinkFromOneToAnother(c1OneNode, c2OneNode);if (tempLink != null) {links.add(tempLink);}}}if (!links.isEmpty()) {Link newLink = new Link(c1, c2, 0.0);links.forEach(link -> newLink.weight += link.weight);newGraph.addLinks(Arrays.asList(newLink));ac = false;}}if (ac) {newGraph.addAcNodes(Arrays.asList(c1));}}currentGraph = newGraph;level--;}CommunityInfo finalComm = new CommunityInfo();Map<Integer, Integer> commNoFinalCommNoMap = new HashMap<>();int cCommNum = 0;for (int i = 0; i < levelResult[levelResult.length - 1].nodeCommunityNo.length; i++) {for (int j = levelResult.length - 2; j >= 1; j--) {int c = levelResult[levelResult.length - 1].nodeCommunityNo[i];int newC = levelResult[j].nodeCommunityNo[c];levelResult[levelResult.length - 1].nodeCommunityNo[i] = newC;}int c = levelResult[levelResult.length - 1].nodeCommunityNo[i];if (!commNoFinalCommNoMap.containsKey(c)) {commNoFinalCommNoMap.put(c, cCommNum++);}levelResult[levelResult.length - 1].nodeCommunityNo[i] = commNoFinalCommNoMap.get(c);}finalComm.nodeCommunityNo = levelResult[levelResult.length - 1].nodeCommunityNo;finalComm.communitiesNo = cCommNum;Map<Integer, List<Integer>> map = new HashMap<>();for (int i = 0; i < cCommNum; i++) {map.put(i, new ArrayList<>());}for (int id = 0; id < finalComm.nodeCommunityNo.length; id++) {map.get(finalComm.nodeCommunityNo[id]).add(id);}finalComm.communityNodeIds = new int[cCommNum][];map.forEach((cId, nodeIds) -> {int[] ids = new int[nodeIds.size()];for (int i = 0; i < nodeIds.size(); i++) {ids[i] = nodeIds.get(i);}finalComm.communityNodeIds[cId] = ids;});return finalComm;}多層劃分基于單層劃分,重點是:
運行入口,使用方法
這個程序沒有寫main類,而是使用了junit單元測試,展示了使用方法
/*** 單層劃分*/@Testpublic void testSingle() {Graph g = new Graph();// 0->1->2->0g.addLinks(Arrays.asList(new Link(0, 1, 1.0)));g.addLinks(Arrays.asList(new Link(1, 2, 1.0)));g.addLinks(Arrays.asList(new Link(2, 0, 1.0)));// 3->4->5->3g.addLinks(Arrays.asList(new Link(3, 4, 1.0)));g.addLinks(Arrays.asList(new Link(4, 5, 1.0)));g.addLinks(Arrays.asList(new Link(5, 3, 1.0)));// 構造計算器LouvainCalculator louvainCalculator = new LouvainCalculator(g);// 執行劃分CommunityInfo communityInfo = louvainCalculator.findCommunitiesSingleLevel();// 輸出結果System.out.println(communityInfo);}/*** 多層劃分*/@Testpublic void testMultiple() {Graph g = new Graph();// 0->1->2->0g.addLinks(Arrays.asList(new Link(0, 1, 1.0)));g.addLinks(Arrays.asList(new Link(1, 2, 1.0)));g.addLinks(Arrays.asList(new Link(2, 0, 1.0)));// 3->4->5->3g.addLinks(Arrays.asList(new Link(3, 4, 1.0)));g.addLinks(Arrays.asList(new Link(4, 5, 1.0)));g.addLinks(Arrays.asList(new Link(5, 3, 1.0)));// 6->7->8->6->5g.addLinks(Arrays.asList(new Link(6, 7, 1.0)));g.addLinks(Arrays.asList(new Link(7, 8, 1.0)));g.addLinks(Arrays.asList(new Link(8, 9, 1.0)));g.addLinks(Arrays.asList(new Link(9, 6, 1.0)));g.addLinks(Arrays.asList(new Link(6, 8, 1.0)));g.addLinks(Arrays.asList(new Link(7, 9, 1.0)));g.addLinks(Arrays.asList(new Link(6, 5, 1.0)));// 構造計算器LouvainCalculator louvainCalculator = new LouvainCalculator(g);// 執行劃分CommunityInfo communityInfo = louvainCalculator.findCommunitiesMultiLevel(2);// 輸出結果System.out.println(communityInfo);}步驟如下:
總結
以上是生活随笔為你收集整理的Louvain社区划分算法及Java语言实现的全部內容,希望文章能夠幫你解決所遇到的問題。