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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程语言 > java >内容正文

java

Louvain社区划分算法及Java语言实现

發(fā)布時(shí)間:2023/12/10 java 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Louvain社区划分算法及Java语言实现 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

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

  • A(i,j)表示兩點(diǎn)之間的邊的權(quán)重,不考慮方向
  • 2m表示所有邊的權(quán)重和
  • k(i)表示點(diǎn)i所有邊的權(quán)重和(度中心性)
  • c(i,j)表示兩者是否在一個(gè)社區(qū),如果在則為1否為為0
    (為了寫方便,和圖中符號(hào)有點(diǎn)差異)
  • 含義:

  • A(i,j) * c(i,j) / 2m表示社區(qū)內(nèi)邊的權(quán)重與圖中總權(quán)重的比值,社區(qū)對(duì)內(nèi)邊越多,對(duì)外邊越少,模塊度越大
  • -k(i) * k(j) * c(i,j) / 2m給度中心性較大的節(jié)點(diǎn)一個(gè)排斥的效果 ,減緩社區(qū)的增大速度
  • 缺點(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)

  • com.wjj.community.louvain.graph.entity包內(nèi)封裝了圖的實(shí)現(xiàn):Graph是圖定義,Link是邊定義。
  • 采用了鄰接表和逆鄰接表的方式存儲(chǔ)圖,因?yàn)閳D通常不是很稠密,鄰接矩陣的存儲(chǔ)空間達(dá)到了O(n^2),而且求權(quán)重、臨邊和臨接節(jié)點(diǎn)效率都比較低下;而鄰接表和逆鄰接表優(yōu)勢(shì)更加明顯,盡管求兩點(diǎn)之間的邊效率略低,但后期會(huì)有優(yōu)化手段。個(gè)人認(rèn)為優(yōu)勢(shì)比劣勢(shì)大。
  • 模塊度計(jì)算

  • com.wjj.community.louvain.graph.algo.LouvainCalculator中包括了緩存、模塊度計(jì)算、單層劃分和多層劃分功能。
  • 緩存存儲(chǔ)節(jié)點(diǎn)權(quán)重、圖總權(quán)重和社區(qū)編號(hào)分配情況
  • /*** 緩存總權(quán)重*/private double totalW;/*** 節(jié)點(diǎn)社區(qū)編號(hào)*/private int[] nodeCommunityNo;/*** 緩存節(jié)點(diǎn)度中心性(帶權(quán)重版,無向)*/private double[] nodeBothWeight;
  • 基于上述緩存計(jì)算全局模塊度,整個(gè)過程使用了之前提到的公式,但計(jì)算還有優(yōu)化的空間。
  • /*** 計(jì)算當(dāng)前模塊度(全局模塊度)** @return ModuleQ值*/private double getModuleQ() {System.out.println("comm status: " + Arrays.toString(nodeCommunityNo));System.out.println("total weight: " + totalW);double q = 0.0;Set<Integer> nodeIds = graph.getNodes();for (int id1 : nodeIds) {for (int id2 : nodeIds) {if (id1 == id2) {continue;}double A = 0.0;for (Link link : graph.getLinksBetweenTwoNodes(id1, id2)) {A += link.weight;}q += c(id1, id2) * (A - nodeBothWeight[id1] * nodeBothWeight[id2] / totalW * stickingK);System.out.printf("id1: %s,id2: %s, A: %s, c: %s, k1: %s, k2: %s%n", id1, id2, A, c(id1, id2),nodeBothWeight[id1], nodeBothWeight[id2]);}}return q / totalW;}

    單層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;}
  • 第一個(gè)while表示執(zhí)行多輪,等到所有節(jié)點(diǎn)的社區(qū)編號(hào)都穩(wěn)定后,就退出循環(huán)
  • 第一個(gè)for循環(huán)代表尋找一個(gè)節(jié)點(diǎn)的最優(yōu)社區(qū)劃分結(jié)果
  • 第二個(gè)for循環(huán)代表為一個(gè)節(jié)點(diǎn)分配社區(qū)編號(hào)和比較模塊度的過程
  • 后面的過程是一些社區(qū)編號(hào)轉(zhuǎn)換(讀者可以PASS…)
  • 多層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)是:

  • 劃分結(jié)果進(jìn)行節(jié)點(diǎn)融合,進(jìn)入下次劃分
  • 最后一層劃分完成后,由于節(jié)點(diǎn)編號(hào)已經(jīng)不是原來的,需要還原回去
  • 第一個(gè)while循環(huán)用于保證n層的劃分
  • 第一個(gè)雙層for循環(huán)完成了節(jié)點(diǎn)的融合和生成新圖的功能
  • while循環(huán)后面的內(nèi)容代表節(jié)點(diǎn)編號(hào)的還原過程,還包括一些轉(zhuǎ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);}

    步驟如下:

  • 創(chuàng)建Graph對(duì)象g
  • 向圖g添加邊和孤立點(diǎn)
  • 點(diǎn)ID用int表示,要求是從0開始連續(xù)的編號(hào)
  • 邊的權(quán)重不能為0
  • 如果有孤立點(diǎn),必須通過addAcNodes( List )方法添加
  • 創(chuàng)建LouvainCalculator對(duì)象(louvain算法計(jì)算對(duì)象)執(zhí)行findCommunitiesSingleLevel進(jìn)行單層劃分或者findCommunitiesMultiLevel( int )進(jìn)行多層劃分,返回對(duì)象類型為CommunityInfo能詳細(xì)表明社區(qū)分布情況。
  • 總結(jié)

    以上是生活随笔為你收集整理的Louvain社区划分算法及Java语言实现的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

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