Louvain社区划分算法及Java语言实现
Louvain社區(qū)劃分算法及Java語言實(shí)現(xiàn)
- 社區(qū)劃分算法處理的對(duì)象
- Louvain社區(qū)發(fā)現(xiàn)算法
- 全局模塊度
- 單層算法過程
- 多層算法過程
- Java代碼實(shí)現(xiàn)
- 圖實(shí)現(xiàn)
- 模塊度計(jì)算
- 單層louvain實(shí)現(xiàn)
- 多層louvain實(shí)現(xiàn)
- 運(yùn)行入口,使用方法
社區(qū)劃分算法處理的對(duì)象
社區(qū)劃分算法又稱社區(qū)發(fā)現(xiàn)算法,針對(duì)網(wǎng)絡(luò)數(shù)據(jù)結(jié)構(gòu),把聯(lián)系緊密度比較高的節(jié)點(diǎn)劃分為一個(gè)組。一個(gè)社區(qū)可以代表“物以類聚人以群分”、“家族”、“流量”、“屬性相似”等概念,有很大的應(yīng)用前景。
Louvain社區(qū)發(fā)現(xiàn)算法
Louvain社區(qū)發(fā)現(xiàn)算法是一種基于模塊度的社區(qū)發(fā)現(xiàn)算法。模塊度是一種評(píng)價(jià)網(wǎng)絡(luò)劃分好壞的指標(biāo),后續(xù)會(huì)詳細(xì)介紹。Louvain算法最終目標(biāo)是得到最優(yōu)的模塊度
全局模塊度
對(duì)于圖中任意兩點(diǎn)i,j
(為了寫方便,和圖中符號(hào)有點(diǎn)差異)
含義:
缺點(diǎn):全圖計(jì)算,運(yùn)算速度較慢
單層算法過程
過程A:
為圖中每個(gè)節(jié)點(diǎn)生成社區(qū)編號(hào),如果有n個(gè)節(jié)點(diǎn)就有n個(gè)社區(qū)。
如圖所示,為A~J節(jié)點(diǎn)分配了10個(gè)社區(qū)編號(hào),這里用10中顏色表示
處理一個(gè)節(jié)點(diǎn),它的社區(qū)編號(hào)依次分配為相鄰節(jié)點(diǎn)的社區(qū)編號(hào)。計(jì)算模塊度,選擇模塊度最大且為△Q為正的社區(qū)編號(hào)(一個(gè)節(jié)點(diǎn)的社區(qū)編號(hào)由相鄰節(jié)點(diǎn)決定)
舉個(gè)例子來說,分別把A的社區(qū)號(hào)分配為C,D,E的社區(qū)號(hào)(圖中橙色、黃色和綠色),分別計(jì)算模塊度。由于例子中的圖十分對(duì)稱,且邊權(quán)重為1,所以三種情況的模塊度Q都是相同的,但都比A自己一個(gè)社區(qū)好,這時(shí)顏色隨便選一個(gè)就可以(程序上的比較法工廠選擇第一個(gè))。但如果網(wǎng)絡(luò)拓?fù)浣Y(jié)構(gòu)比較復(fù)雜,情況會(huì)有所不同。
遍歷所有節(jié)點(diǎn),重復(fù)步驟2,完成一輪(后面的社區(qū)編號(hào)分配會(huì)依賴前面的結(jié)果)
過程B:重復(fù)過程A,直到所有節(jié)點(diǎn)社區(qū)編號(hào)不再變化(收斂)
因?yàn)槊恳惠喌慕Y(jié)果可能不同,也就是不穩(wěn)定。可以重復(fù)直到所有節(jié)點(diǎn)社區(qū)編號(hào)都不變化或者一定比例的節(jié)點(diǎn)社區(qū)編號(hào)不變化。因?yàn)閳D如果很大,有時(shí)為了提升效率這么做。
多層算法過程
運(yùn)行完過程A,B后可以把一個(gè)社區(qū)的節(jié)點(diǎn)融合成為一個(gè)節(jié)點(diǎn),對(duì)新的圖形結(jié)構(gòu)再次進(jìn)行單層劃分,即完成了第二層。
如圖中所示,節(jié)點(diǎn)融合時(shí),忽略社區(qū)內(nèi)部的邊(不考慮指向自身的邊),社區(qū)之間的邊合并(權(quán)重合并),然后對(duì)新的圖進(jìn)行單層louvain即可。
可以重復(fù)達(dá)到n層,層數(shù)越多,社區(qū)數(shù)量越少,效果更好,但時(shí)間也會(huì)增加。因此要選擇合適的層數(shù)。
Java代碼實(shí)現(xiàn)
參考我的實(shí)現(xiàn):https://github.com/ghostorsoul/louvain這是一種基于內(nèi)存的單線程計(jì)算方法
圖實(shí)現(xiàn)
模塊度計(jì)算
單層louvain實(shí)現(xiàn)
/*** 單層louvain社區(qū)劃分算法** @return 社區(qū)劃分結(jié)果*/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實(shí)現(xiàn)
/*** 多層louvain社區(qū)劃分算法** @param level 層數(shù)* @return 社區(qū)劃分結(jié)果*/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;}多層劃分基于單層劃分,重點(diǎn)是:
運(yùn)行入口,使用方法
這個(gè)程序沒有寫main類,而是使用了junit單元測(cè)試,展示了使用方法
/*** 單層劃分*/@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)));// 構(gòu)造計(jì)算器LouvainCalculator louvainCalculator = new LouvainCalculator(g);// 執(zhí)行劃分CommunityInfo communityInfo = louvainCalculator.findCommunitiesSingleLevel();// 輸出結(jié)果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)));// 構(gòu)造計(jì)算器LouvainCalculator louvainCalculator = new LouvainCalculator(g);// 執(zhí)行劃分CommunityInfo communityInfo = louvainCalculator.findCommunitiesMultiLevel(2);// 輸出結(jié)果System.out.println(communityInfo);}步驟如下:
總結(jié)
以上是生活随笔為你收集整理的Louvain社区划分算法及Java语言实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Res协议,专题
- 下一篇: java美元兑换,(Java实现) 美元