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

article/2025/9/27 20:38:34

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

    • 社区划分算法处理的对象
    • Louvain社区发现算法
    • 全局模块度
    • 单层算法过程
    • 多层算法过程
    • Java代码实现
        • 图实现
        • 模块度计算
        • 单层louvain实现
        • 多层louvain实现
        • 运行入口,使用方法

社区划分算法处理的对象

社区划分算法又称社区发现算法,针对网络数据结构,把联系紧密度比较高的节点划分为一个组。一个社区可以代表“物以类聚人以群分”、“家族”、“流量”、“属性相似”等概念,有很大的应用前景。

Louvain社区发现算法

Louvain社区发现算法是一种基于模块度的社区发现算法。模块度是一种评价网络划分好坏的指标,后续会详细介绍。Louvain算法最终目标是得到最优的模块度

全局模块度

模块度公式

  1. 对于图中任意两点i,j

    1. A(i,j)表示两点之间的边的权重,不考虑方向
    2. 2m表示所有边的权重和
    3. k(i)表示点i所有边的权重和(度中心性)
    4. c(i,j)表示两者是否在一个社区,如果在则为1否为为0
      (为了写方便,和图中符号有点差异)
  2. 含义:

    1. A(i,j) * c(i,j) / 2m表示社区内边的权重与图中总权重的比值,社区对内边越多,对外边越少,模块度越大
    2. -k(i) * k(j) * c(i,j) / 2m给度中心性较大的节点一个排斥的效果 ,减缓社区的增大速度
  3. 缺点:全图计算,运算速度较慢

单层算法过程

  1. 过程A:

    1. 为图中每个节点生成社区编号,如果有n个节点就有n个社区。

      如图所示,为A~J节点分配了10个社区编号,这里用10中颜色表示

    2. 处理一个节点,它的社区编号依次分配为相邻节点的社区编号。计算模块度,选择模块度最大且为△Q为正的社区编号(一个节点的社区编号由相邻节点决定)
      在这里插入图片描述
      举个例子来说,分别把A的社区号分配为C,D,E的社区号(图中橙色、黄色和绿色),分别计算模块度。由于例子中的图十分对称,且边权重为1,所以三种情况的模块度Q都是相同的,但都比A自己一个社区好,这时颜色随便选一个就可以(程序上的比较法工厂选择第一个)。但如果网络拓扑结构比较复杂,情况会有所不同。

    3. 遍历所有节点,重复步骤2,完成一轮(后面的社区编号分配会依赖前面的结果)

  2. 过程B:重复过程A,直到所有节点社区编号不再变化(收敛)
    因为每一轮的结果可能不同,也就是不稳定。可以重复直到所有节点社区编号都不变化或者一定比例的节点社区编号不变化。因为图如果很大,有时为了提升效率这么做。

多层算法过程

运行完过程A,B后可以把一个社区的节点融合成为一个节点,对新的图形结构再次进行单层划分,即完成了第二层。
在这里插入图片描述
如图中所示,节点融合时,忽略社区内部的边(不考虑指向自身的边),社区之间的边合并(权重合并),然后对新的图进行单层louvain即可。

可以重复达到n层,层数越多,社区数量越少,效果更好,但时间也会增加。因此要选择合适的层数。

Java代码实现

参考我的实现:https://github.com/ghostorsoul/louvain这是一种基于内存的单线程计算方法

图实现

  1. com.wjj.community.louvain.graph.entity包内封装了图的实现:Graph是图定义,Link是边定义。
  2. 采用了邻接表和逆邻接表的方式存储图,因为图通常不是很稠密,邻接矩阵的存储空间达到了O(n^2),而且求权重、临边和临接节点效率都比较低下;而邻接表和逆邻接表优势更加明显,尽管求两点之间的边效率略低,但后期会有优化手段。个人认为优势比劣势大。

模块度计算

  1. com.wjj.community.louvain.graph.algo.LouvainCalculator中包括了缓存、模块度计算、单层划分和多层划分功能。
  2. 缓存存储节点权重、图总权重和社区编号分配情况
/*** 缓存总权重*/private double totalW;/*** 节点社区编号*/private int[] nodeCommunityNo;/*** 缓存节点度中心性(带权重版,无向)*/private double[] nodeBothWeight;
  1. 基于上述缓存计算全局模块度,整个过程使用了之前提到的公式,但计算还有优化的空间。
/*** 计算当前模块度(全局模块度)** @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实现

/*** 单层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;}
  1. 第一个while表示执行多轮,等到所有节点的社区编号都稳定后,就退出循环
  2. 第一个for循环代表寻找一个节点的最优社区划分结果
  3. 第二个for循环代表为一个节点分配社区编号和比较模块度的过程
  4. 后面的过程是一些社区编号转换(读者可以PASS…)

多层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;}

多层划分基于单层划分,重点是:

  1. 划分结果进行节点融合,进入下次划分
  2. 最后一层划分完成后,由于节点编号已经不是原来的,需要还原回去
  3. 第一个while循环用于保证n层的划分
  4. 第一个双层for循环完成了节点的融合和生成新图的功能
  5. while循环后面的内容代表节点编号的还原过程,还包括一些转换功能。

运行入口,使用方法

这个程序没有写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);}

步骤如下:

  1. 创建Graph对象g
  2. 向图g添加边和孤立点
    1. 点ID用int表示,要求是从0开始连续的编号
    2. 边的权重不能为0
    3. 如果有孤立点,必须通过addAcNodes( List )方法添加
  3. 创建LouvainCalculator对象(louvain算法计算对象)执行findCommunitiesSingleLevel进行单层划分或者findCommunitiesMultiLevel( int )进行多层划分,返回对象类型为CommunityInfo能详细表明社区分布情况。

http://chatgpt.dhexx.cn/article/j3CwbFZs.shtml

相关文章

社区发现算法-Community Detection-NormalizeCut/Louvain/NMF/LPA

本文结构安排 图聚类简介 正则化割 Louvain 非负矩阵分解&#xff08;NMF&#xff09; 其他常见方法 图(graph):是一种由点和边集构成的结构 G ( V , E ) G(V,E) G(V,E) 图聚类(graph clustering) : 将点划分为不同的簇&#xff0c;使得簇内的边尽量多&#xff0c;簇之间…

Louvain算法在反作弊上的应用

作者 | ANTI 一、概述 随着互联网技术的发展&#xff0c;人们享受互联网带来的红利的同时&#xff0c;也面临着黑产对整个互联网健康发展带来的危害&#xff0c;例如薅羊毛、刷单、刷流量/粉丝、品控、诈骗、快排等等&#xff0c;反作弊作为打击黑产的中坚力量&#xff0c;持…

community_louvain社群划分方法

第一、 这个方法是一个典型的EM算法。定义了一个“模块度”的量化评价指标&#xff0c;然后结合上优化方法&#xff0c;不断地优化模块度&#xff0c;最终得到社群划分的结果。 第二、模块度的定义&#xff0c;具体如下&#xff1a; 对于图中任意两个节点&#xff0c;i和j 1、…

Louvain 社团发现算法学习(我的java实现+数据用例)

为了大家方便&#xff0c;直接把数据放在github了&#xff1a; https://github.com/qq547276542/Louvain 算法介绍&#xff1a; Louvain 算法是基于模块度的社区发现算法&#xff0c;该算法在效率和效果上都表现较好&#xff0c;并且能够发现层次性的社区结构&#xff0c;其…

‘ network communites’(网络社区)(二)(louvain算法实现)

引言&#xff1a; 在&#xff08;一&#xff09;中我们学习到了什么是‘network communites’&#xff08;网络社区&#xff09;及其目标函数Q的求取&#xff0c;接下来我们要说明的是&#xff0c;我们要通过怎样的算法来实现将你的网络分成若干个集群。 一&#xff1a;louva…

neo4j实现Louvain算法

文章目录 例子一&#xff1a;创建一个属性图&#xff08;无权&#xff09;一、属性图如下二、实现算法1.stream模式执行Louvain算法&#xff08;匿名图&#xff09;2.结果如下 总结一&#xff1a;例子二&#xff1a;创建一个属性图&#xff08;有权&#xff09;一、属性图如下二…

社区发现系列03-Louvain算法分辨率

1、分辨率局限 louvain算法存在的问题&#xff1a;分辨率局限。就是说当通过优化模块度来发现社区结构时&#xff0c;网络在存在一个固有的分辨率局限&#xff0c;导致一些规模较小但是结构显著的社区淹没在大的社区中&#xff0c;无法被识别到。 造成这个问题的根本原因是模块…

(Leiden)From Louvain to Leiden:guaranteeing well-connected communities

Leiden算法 论文地址 Leiden算法是近几年的SOTA算法之一。 Louvain 算法有一个主要的缺陷&#xff1a;可能会产生任意的连接性不好的社区(甚至不连通)。为了解决这个问题&#xff0c;作者引入了Leiden算法。证明了该算法产生的社区保证是连通的。此外证明了当Leiden算法迭代应…

社区发现不得不了解的库,包含Louvain 算法、Girvan-Newman 算法等多种社区发现算法,还具有可视化功能

熟知社区发现算法&#xff0c;你不能错过这个 Python 库。它涵盖 Louvain 算法、Girvan-Newman 算法等多种社区发现算法&#xff0c;还具有可视化功能。 网络是由一些紧密相连的节点组成的&#xff0c;并且根据不同节点之间连接的紧密程度&#xff0c;网络也可视为由不同簇组成…

【积】有向图中的louvain社区检测(二)

有向图中的louvain社区检测 请学着自己长大&#xff0c;参考连接《无向louvain社团算法》 无向到有向的修改真的很简单。如果你连这个都做不到&#xff0c;建议不要用了。每个算法与数据匹配的时候&#xff0c;都会对数据或者算法小修。如果你连小修都做不到的话&#xff0c;…

Louvain算法实现

谢谢平台提供-http://bjbsair.com/2020-04-13/tech-info/65263.html 社区查找找的算法 Louvain是一种无监督算法&#xff08;执行前不需要输入社区数量或社区大小&#xff09;&#xff0c;分为两个阶段&#xff1a;模块化优化和社区聚集[1]。 第一步完成后&#xff0c;接下来…

Louvain 算法原理 及设计实现

模块度: Louvain算法是一种基于图数据的社区发现算法。原始论文为:《Fast unfolding of communities in large networks》。 算法的优化目标为最大化整个数据的模块度,模块度的计算如下: 其中m为图中边的总数量,k_i表示所有指向节点i的连边权重之和,k_j同理。A_{i,j} 表…

Louvain算法介绍

Louvain算法 一种基于模块度的图算法模型&#xff0c;与普通的基于模块度和模块度增益不同的是&#xff0c;该算法速度很快&#xff0c;而且对一些点多边少的图&#xff0c;进行聚类效果特别明显。 算法流程&#xff1a; 1、初始时将每个顶点当作一个社区&#xff0c;社区个数与…

Python社区发现—Louvain—networkx和community

社区 如果一张图是对一片区域的描述的话&#xff0c;将这张图划分为很多个子图。当子图之内满足关联性尽可能大&#xff0c;而子图之间关联性尽可能低时&#xff0c;这样的子图可以称之为一个社区。 社区发现算法 社区发现算法有很多&#xff0c;例如LPA&#xff0c;HANP&am…

关于在networkx中使用louvain算法报错的问题

module ‘networkx.algorithms.community’ has no attribute ‘louvain_communities’ Networkx是复杂网络科学中常用的python包&#xff0c;louvain也是常用的社团发现算法之一。在networkx的文档中也有描述。louvain_communities — NetworkX 2.8.5 documentationhttps://n…

【知识图谱】Louvain、LPA等5类经典社区发现算法 Python 实战

一、社区发现概述 根据图论&#xff0c;加权网络表示为&#x1d43a;(&#x1d449;,&#x1d438;,&#x1d44a;)&#xff0c;未加权网络表示为&#x1d43a;(&#x1d449;,&#x1d438;)&#xff0c;其中&#x1d449;和&#x1d438;表示节点和边的集合&#xff0c;&…

社区发现算法之——Louvain

1、什么是社区 如果一张图是对一片区域的描述的话&#xff0c;我们将这张图划分为很多个子图。当子图之内满足关联性尽可能大&#xff0c;而子图之间关联性尽可能低时&#xff0c;这样的子图我们可以称之为一个社区。 2、社区发现算法及评价标准 社区发现算法有很多&#xf…

python-louvain

安装 louvain当前最新版本&#xff1a;0.14 pip install python-louvain由于是处理社区的数据&#xff0c;这里还是安装networkx pip install networkx使用 不妨来运行下一个案例&#xff1a; import community as community_louvain import matplotlib.cm as cm import m…

louvain算法

简介 louvain算法由比利时鲁汶大学的 Vincent D.Blondel 等人于 2008 年提出&#xff0c;因其能以较高的效率计算出令人满意的社区识别结果&#xff0c;是近年来最多被提及和使用的社区识别算法。 Louvain算法是一种基于模块度(模块度越大则表明社区划分效果越好。Q值的…

社区发现算法——Louvain 算法

Louvain 算法 原始论文为&#xff1a;《Fast unfolding of communities in large networks》。 所以又被称为Fast unfolding算法。 Louvain算法是一种基于模块度的社区发现算法。其基本思想是网络中节点尝试遍历所有邻居的社区标签&#xff0c;并选择最大化模块度增量的社区…