图社区发现算法-Louvain算法

Louvain社区发现算法出自2008年的论文《Fast unfolding of communities in large networks》,其名字是根据作者所在的城市来命名的。它基于模块度优化来实现社区划分。

准备知识

模块度(modularity)是用来衡量社区内部的链接密度相比社区之间的链接密度的介于-1和1的分数。对于带权网络,其定义如下:
Q = 1 2 m ∑ i , j [ A i j − k i k j 2 m ] δ ( c i , c j ) ( 1 ) Q = \frac{1}{2m} \sum_{i,j} \left[A_{ij} - \frac{k_i k_j}{2m} \right] \delta(c_i, c_j) \qquad (1) Q=2m1i,j[Aij2mkikj]δ(ci,cj)(1)
上式中的 A i j A_{ij} Aij是节点i和节点j之间的权重, k i = ∑ j A i j k_i=\sum_j A_{ij} ki=jAij是与节点i相连的边的权重之和, c i c_i ci是节点i所属的社区,如果u=v,则 δ ( u , v ) \delta(u,v) δ(u,v)为1否则为0。 m = 1 2 ∑ i j A i j m = \frac{1}{2} \sum_{ij} A_{ij} m=21ijAij(图中所有边的权重之和,因为一条边的权重被计算了两次,所以要除以2;当权重为1时,m即图中边的条数)。

根据上述定义,社区c的模块度可被计算为:
Q c = 1 2 m ∑ i ∑ j A i j 1 { c i = c j = c } − ( ∑ i k i 2 m 1 { c i = c } ) = ∑ i n 2 m − ( ∑ t o t 2 m ) 2 ( 2 ) \begin{aligned} Q_c &= \frac{1}{2m} \sum_i \sum_j A_{ij} \mathbb{1}\{c_i = c_j = c \} - \left( \sum_i \frac{k_i}{2m} \mathbb{1} \{c_i =c \} \right) \\ &= \frac{\sum_{in}}{2m} - \left(\frac{\sum_{tot}}{2m} \right)^2 \qquad (2) \end{aligned} Qc=2m1ijAij1{ci=cj=c}(i2mki1{ci=c})=2min(2mtot)2(2)
上式中 ∑ i n \sum_{in} in是社区c内部的边的权重之和(每条边会被计算两次), ∑ t o t \sum_{tot} tot是所有与c内节点相连的边的权重之和(包括与其他社区相连的边)。网络的模块度也记为:
Q = ∑ c Q c ( 3 ) Q = \sum_c Q_c \qquad (3) Q=cQc(3)

WeChatWorkScreenshot_5f6e6d13-f5ef-4d7a-85f6-b30b7678cf1e

Louvain算法详情

Louvain算法分为两阶段,第一阶段是模块度优化,第二阶段是社区合并。不断重复这个阶段直到网络的模块度不再发生变化或者达到最大迭代次数。

第一阶段:

在初始化时,将每个节点都当做一个独立的社区,接着对每一个节点i,计算将i从原来的社区移到其邻居节点j对应的社区后的模块度增益,将i的社区变成增益最大的邻居对应的社区,注意这里的增益只考虑正增益,如果最大增益为负则i的社区保持不变。这个过程对所有节点不断地重复直到无法因为改变某个节点的社区得到正模块度增益。

Lovain算法比较高效是因为将孤立节点i加入到社区C的模块度增益计算可按下式进行(注:下式中的 k i , i n k_{i,in} ki,in 网上资料有两个版本,有的前面有系数2,有的没有,arxiv上和networkx都是使用下面式子的版本,如果从前面定义的社区c的模块度来看,应该是下面的式子,式(4)的第一部分是加入节点i后的社群C的模块度,第二部分的前两项是社区c的模块度,第二项是节点i的模块度),
Δ Q = [ ∑ i n + k i , i n 2 m − ( ∑ t o t + k i 2 m ) 2 ] − [ ∑ i n 2 m − ( ∑ t o t 2 m ) 2 − ( k i 2 m ) 2 ] ( 4 ) = k i , i n 2 m − ∑ t o t ⋅ k i 2 m 2 ( 5 ) \begin{aligned} \Delta Q &= \left[ \frac{\sum_{in} + k_{i, in}}{2m} - \left( \frac{\sum_{tot} + k_i}{2m} \right)^2 \right] - \left[ \frac{\sum_{in} }{2m} - \left(\frac{\sum_{tot} }{2m}\right)^2 - \left(\frac{k_i }{2m}\right)^2 \right] \qquad (4)\\ &= \frac{k_{i,in}}{2m} - \frac{\sum_{tot} \cdot k_i}{2m^2} \qquad (5) \end{aligned} ΔQ=[2min+ki,in(2mtot+ki)2][2min(2mtot)2(2mki)2](4)=2mki,in2m2totki(5)
∑ i n \sum_{in} in是社区C内部的边的权重之和, ∑ t o t \sum_{tot} tot是所有与C内节点相连的边的权重之和(包括与其他社区相连的边), k i k_i ki是与节点i相连的边的权重之和, k i , i n k_{i, in} ki,in是节点i与社区C内的节点之间的边的权重之和,m是图中所有边的权重之和。

每个节点的最终社区取决于节点遍历顺序,作者说实验结果显示遍历顺序对最终模块度没有显著影响,但是会影响计算时间。因此实际实现时都会对节点进行随机排序后再进行遍历。

第二阶段:

第二阶段将前一阶段得到每个社区合并成一个新的节点构成新的图。新节点之间的边的权重为对应的两个社区里节点的边的权重之和,而原来社区内部节点之间的边当做新节点自环(self-loop)其权重为原来边的权重之和。

Louvain算法的优点

  1. 时间复杂度低,计算快适合大规模的网络。
  2. 社区划分结果稳定,可用模块度来评估社区划分好坏。
  3. 算法流程使其划分结果有层次化,如果将每一次迭代的社区划分结果保存下来,可使用其中间结果。
  4. 因为其层次化特性可以规避分辨率问题,因为在初始化时每个节点都是一个独立的社区。

Louvain算法的缺点

  1. 计算过程存储在内存中,如果图很大,算法对内存要求高。
  2. 不容易进行分布式实现(面有针对分布式的改进Louvain算法)

Louvain算法实现

Networkx中实现了Louvain算法,使用很简单:

import networkx as nx
G = nx.karate_club_graph()
nx.community.louvain_communities(G, seed=123)

csdn blog实现的代码理解算法更容易

import collections
import random
import time
import networkx as nx
import matplotlib.pyplot as pltdef load_graph(path):G = collections.defaultdict(dict)with open(path) as text:for line in text:vertices = line.strip().split()v_i = int(vertices[0])v_j = int(vertices[1])w = 1.0  # 数据集有权重的话则读取数据集中的权重G[v_i][v_j] = wG[v_j][v_i] = wreturn G# 节点类 存储社区与节点编号信息
class Vertex:def __init__(self, vid, cid, nodes, k_in=0):# 节点编号self._vid = vid# 社区编号self._cid = cidself._nodes = nodesself._kin = k_in  # 结点内部的边的权重class Louvain:def __init__(self, G, seed=123):self._G = Gself.seed = seedself._m = 0  # 边数量 图会凝聚动态变化self._cid_vertices = {}  # 需维护的关于社区的信息(社区编号,其中包含的结点编号的集合)self._vid_vertex = {}  # 需维护的关于结点的信息(结点编号,相应的Vertex实例)for vid in self._G.keys():# 刚开始每个点作为一个社区self._cid_vertices[vid] = {vid}# 刚开始社区编号就是节点编号self._vid_vertex[vid] = Vertex(vid, vid, {vid})# 计算边数  每两个点维护一条边self._m += sum([1 for neighbor in self._G[vid].keys()if neighbor > vid])# 模块度优化阶段def first_stage(self):mod_inc = False  # 用于判断算法是否可终止visit_sequence = self._G.keys()# 随机访问random.Random(self.seed).shuffle(list(visit_sequence))while True:can_stop = True  # 第一阶段是否可终止# 遍历所有节点for v_vid in visit_sequence:# 获得节点的社区编号v_cid = self._vid_vertex[v_vid]._cid# k_v节点的权重(度数)  内部与外部边权重之和k_v = sum(self._G[v_vid].values()) + \self._vid_vertex[v_vid]._kin# 存储模块度增益大于0的社区编号cid_Q = {}# 遍历节点的邻居for w_vid in self._G[v_vid].keys():# 获得该邻居的社区编号w_cid = self._vid_vertex[w_vid]._cidif w_cid in cid_Q:continueelse:# tot是关联到社区C中的节点的链路上的权重的总和tot = sum([sum(self._G[k].values()) + self._vid_vertex[k]._kin for k in self._cid_vertices[w_cid]])if w_cid == v_cid:tot -= k_v# k_v_in是从节点i连接到C中的节点的链路的总和k_v_in = sum([v for k, v in self._G[v_vid].items() if k in self._cid_vertices[w_cid]])# 由于只需要知道delta_Q的正负,所以少乘了1/(2*self._m)delta_Q = k_v_in - k_v * tot / self._mcid_Q[w_cid] = delta_Q# 取得最大增益的编号cid, max_delta_Q = sorted(cid_Q.items(), key=lambda item: item[1], reverse=True)[0]if max_delta_Q > 0.0 and cid != v_cid:# 让该节点的社区编号变为取得最大增益邻居节点的编号self._vid_vertex[v_vid]._cid = cid# 在该社区编号下添加该节点self._cid_vertices[cid].add(v_vid)# 以前的社区中去除该节点self._cid_vertices[v_cid].remove(v_vid)# 模块度还能增加 继续迭代can_stop = Falsemod_inc = Trueif can_stop:breakreturn mod_inc# 网络凝聚阶段def second_stage(self):cid_vertices = {}vid_vertex = {}# 遍历社区和社区内的节点for cid, vertices in self._cid_vertices.items():if len(vertices) == 0:continuenew_vertex = Vertex(cid, cid, set())# 将该社区内的所有点看做一个点for vid in vertices:new_vertex._nodes.update(self._vid_vertex[vid]._nodes)new_vertex._kin += self._vid_vertex[vid]._kin# k,v为邻居和它们之间边的权重 计算kin社区内部总权重 这里遍历vid的每一个在社区内的邻居   因为边被两点共享后面还会计算  所以权重/2for k, v in self._G[vid].items():if k in vertices:new_vertex._kin += v / 2.0# 新的社区与节点编号cid_vertices[cid] = {cid}vid_vertex[cid] = new_vertexG = collections.defaultdict(dict)# 遍历现在不为空的社区编号 求社区之间边的权重for cid1, vertices1 in self._cid_vertices.items():if len(vertices1) == 0:continuefor cid2, vertices2 in self._cid_vertices.items():# 找到cid后另一个不为空的社区if cid2 <= cid1 or len(vertices2) == 0:continueedge_weight = 0.0# 遍历 cid1社区中的点for vid in vertices1:# 遍历该点在社区2的邻居已经之间边的权重(即两个社区之间边的总权重  将多条边看做一条边)for k, v in self._G[vid].items():if k in vertices2:edge_weight += vif edge_weight != 0:G[cid1][cid2] = edge_weightG[cid2][cid1] = edge_weight# 更新社区和点 每个社区看做一个点self._cid_vertices = cid_verticesself._vid_vertex = vid_vertexself._G = Gdef get_communities(self):communities = []for vertices in self._cid_vertices.values():if len(vertices) != 0:c = set()for vid in vertices:c.update(self._vid_vertex[vid]._nodes)communities.append(list(c))return communitiesdef execute(self):iter_time = 1while True:iter_time += 1# 反复迭代,直到网络中任何节点的移动都不能再改善总的 modularity 值为止mod_inc = self.first_stage()if mod_inc:self.second_stage()else:breakreturn self.get_communities()# 可视化划分结果
def showCommunity(G, partition, pos):# 划分在同一个社区的用一个符号表示,不同社区之间的边用黑色粗体cluster = {}labels = {}for index, item in enumerate(partition):for nodeID in item:labels[nodeID] = r'$' + str(nodeID) + '$'  # 设置可视化labelcluster[nodeID] = index  # 节点分区号# 可视化节点colors = ['r', 'g', 'b', 'y', 'm']shapes = ['v', 'D', 'o', '^', '<']for index, item in enumerate(partition):nx.draw_networkx_nodes(G, pos, nodelist=item,node_color=colors[index],node_shape=shapes[index],node_size=350,alpha=1)# 可视化边edges = {len(partition): []}for link in G.edges():# cluster间的linkif cluster[link[0]] != cluster[link[1]]:edges[len(partition)].append(link)else:# cluster内的linkif cluster[link[0]] not in edges:edges[cluster[link[0]]] = [link]else:edges[cluster[link[0]]].append(link)for index, edgelist in enumerate(edges.values()):# cluster内if index < len(partition):nx.draw_networkx_edges(G, pos,edgelist=edgelist,width=1, alpha=0.8, edge_color=colors[index])else:# cluster间nx.draw_networkx_edges(G, pos,edgelist=edgelist,width=3, alpha=0.8, edge_color=colors[index])# 可视化labelnx.draw_networkx_labels(G, pos, labels, font_size=12)plt.axis('off')plt.show()def cal_Q(partition, G):  # 计算Q# 如果为真,则返回3元组(u、v、ddict)中的边缘属性dict。如果为false,则返回2元组(u,v)m = len(G.edges(None, False))# print(G.edges(None,False))# print("=======6666666")a = []e = []for community in partition:  # 把每一个联通子图拿出来t = 0.0for node in community:  # 找出联通子图的每一个顶点# G.neighbors(node)找node节点的邻接节点t += len([x for x in G.neighbors(node)])a.append(t / (2 * m))#             self.zidian[t/(2*m)]=communityfor community in partition:t = 0.0for i in range(len(community)):for j in range(len(community)):if (G.has_edge(community[i], community[j])):t += 1.0e.append(t / (2 * m))q = 0.0for ei, ai in zip(e, a):q += (ei - ai ** 2)return qclass Graph:graph = nx.DiGraph()def __init__(self):self.graph = nx.DiGraph()def createGraph(self, filename):file = open(filename, 'r')for line in file.readlines():nodes = line.split()edge = (int(nodes[0]), int(nodes[1]))self.graph.add_edge(*edge)return self.graphif __name__ == '__main__':G = load_graph('data/club.txt')G1 = nx.karate_club_graph()pos = nx.spring_layout(G1)start_time = time.time()algorithm = Louvain(G)communities = algorithm.execute()end_time = time.time()# 按照社区大小从大到小排序输出communities = sorted(communities, key=lambda b: -len(b))  # 按社区大小排序count = 0for communitie in communities:count += 1print("社区", count, " ", communitie)print(cal_Q(communities, G1))print(f'算法执行时间{end_time - start_time}')# 可视化结果showCommunity(G1, communities, pos)     

其他

另外,Louvain作者在2023年11月又写了一篇论文《Fast unfolding of communities in large networks: 15 years later》,介绍Louvain算法的实现与相关改进方法。

参考资料

  1. Fast unfolding of communities in large networks

  2. csdn blog, blog对应的python实现

  3. networkx里实现的Louvain

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/33991.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

Elasticsearch之索引的增删改查(6.x版本)-yellowcong

1. 节点信息查看 #查看集群健康情况 curl -X GET localhost:9200/_cat/health?v&pretty#查看节点信息 curl -X GET localhost:9200/_cat/nodes?v&pretty 2. 索引管理 在es中&#xff0c;索引就相当于是mysql中的库了。 #查看索引列表 curl -X GET localhost:9200/…

技术栈4:Docker入门 Linux入门指令

目录 1.Linux系统目录结构 2.处理目录的常用命令 3.Docker概述 4.Docker历史 5.Docker基本组成 6.Docker底层原理 7.Docker修改镜像源 8.Docker基本命令 在学习docker之前我们先要熟悉Linux系统&#xff0c;推荐阅读&#xff1a;Linux笔记&#xff08;狂神说&#xff0…

UFS文档导航

目录 1、UFS系统模型 2、UFS子系统实现架构 3、Host Controller 4、M-PHY 5、UFS Device 1、UFS系统模型 2、UFS子系统实现架构 3、Host Controller 模块 文档名称 文档描述 Host Controller JESD223D 协议文档&#xff0c;UFS Host Controller Interface DWC_ufshc…

北斗系统:构建天地一体化的高精度定位服务

随着北斗卫星导航系统的全面建成&#xff0c;中国在全球卫星导航领域迈出了坚实的一步。北斗系统不仅提供了全天候、全天时的全球覆盖服务能力&#xff0c;更通过天地一体化的高精度增强服务系统技术&#xff0c;将民用定位精度提升到了新的高度。 北斗系统的高精度服务 北斗…

论文阅读:Omnidirectional Image Super-resolution via Bi-projection Fusion

对于全景图像&#xff08;ODIs&#xff09;的超分辨率的技术有&#xff1a;等矩投影&#xff08;ERP&#xff09;但是这个没有利用 ODIs 的独特任何特性。ERP提供了完整的视场但引入了显著的失真&#xff0c;而立方体映射投影&#xff08;CMP&#xff09;可以减少失真但视场有限…

汽车总线协议分析-FlexRay总线

随着汽车智能化发展&#xff0c;汽车增加安全性和舒适体验的功能增多&#xff0c;用于实现这些功能的传感器、ECU的数量也在持续上升&#xff0c;严重阻碍了线控技术的发展。常用的CAN、LIN等总线由于缺少同步性、确定性和容错性不能满足汽车线控系统(X-by-Wire)的要求。因此&a…

《算法导论》英文版前言To the teacher第4段研习录:有答案不让用

【英文版】 Departing from our practice in previous editions of this book, we have made publicly available solutions to some, but by no means all, of the problems and exercises. Our Web site, http://mitpress.mit.edu/algorithms/, links to these solutions. Y…

AI Agent工作流程:关于是使用 LangGraph 还是 LangChain 进行构建的完整指南

深入了解同一创建者 LangChain 和 LangGraph 的两个库&#xff1a;它们的关键构建块、它们如何处理其功能的核心部分&#xff0c;以及为您的用例在它们之间做出决定 语言模型为用户如何与 AI 系统交互以及这些系统如何通过自然语言相互通信开启了可能性。 在本文中&#xff0c…

qt QPrinter详解

1、概述 QPrinter类是Qt框架中用于打印输出的绘图设备。它表示打印出来的一系列页面&#xff0c;并提供了一组附加功能来管理特定于设备的特性&#xff0c;比如方向和分辨率。QPrinter可以生成PDF文档&#xff0c;也可以将内容发送到打印机进行实际打印。它继承自QPagedPaintD…

腾讯面试:如何解决哈希冲突?

我们面试时经常被问到HashMap是怎么解决哈希冲突的&#xff0c;很多同学对其含糊其词、一知半解。因此小编对相关知识进行了总结&#xff0c;希望帮助读者加深对其理解。 哈希表就是通过散列函数将键映射到定值&#xff0c;简单来说就是一个键对应一个值。 而通过散列函数映射…

数组中的四个函数(数组实现)

strlen&#xff08;输出长度&#xff09; #include <stdio.h> #include <string.h> #include <stdlib.h> int main(int argc, const char *argv[]) { char str[100]; int count 0; // 提示用户输入字符串 printf("请输入一个字符串: &qu…

大数据-241 离线数仓 - 电商核心交易 业务数据表结构 订单、产品、分类、店铺、支付表

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; Java篇开始了&#xff01; 目前开始更新 MyBatis&#xff0c;一起深入浅出&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff0…

Linux-命令

文章目录 一. Linux的目录1. Linux的目录结构2. Linux的路径的描述方式3. home目录,当前工作目录4. 栗子 二. Linux命令入门1. 什么是命令,命令行2. Linux命令基础格式 三. 目录相关命令1. ls:展示当前工作目录下的内容2. cd:切换工作目录3. pwd:输出当前所在的工作目录4. 相对…

SpringBoot该怎么使用Neo4j - 优化篇

文章目录 前言实体工具使用 前言 上一篇中&#xff0c;我们的Cypher都用的是字符串&#xff0c;字符串拼接简单&#xff0c;但存在写错的风险&#xff0c;对于一些比较懒的开发者&#xff0c;甚至觉得之间写字符串还更自在快速&#xff0c;也确实&#xff0c;但如果在后期需要…

旋转图像

旋转图像 ​ 给定一个 n n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。 ​ 你必须在** 原地** 旋转图像&#xff0c;这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像 示例 1&#xff1a; 输入&#xff1a;matrix [[1,2,3],[4,…

3D数据大屏实现过程,使用echarts、Next.js

&#x1f4dc; 本文主要内容 数据大屏自适应方案动效 echarts&#xff1a; 3D 立体柱状图动态流光折线图 3D 地球&#xff08;飞线、柱状图&#xff09;无限滚动列表 &#x1f50d; 大屏效果 数据大屏&#xff1a; 点击预览 &#x1f579; 运行条件 next 12.3.4echarts 5.4…

长文 | RAG的实战指南及探索之路

今天给大家带来一篇知乎孙鹏飞 的关于RAG实战的文章。 作者&#xff1a;孙鹏飞 知乎&#xff1a;https://zhuanlan.zhihu.com/p/6822534961. 背景介绍 RAG&#xff08;Retrieval Augmented Generation&#xff0c;检索增强生成 &#xff09;方法是指结合了基于检索的模型和生…

LeetCode—11. 盛最多水的容器(中等)

题目描述&#xff1a; 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;…

leetcode 63.不同路径||

1.题目要求: 2.题目代码: class Solution { public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {//创建dp数组的含义vector<vector<int>> dp;dp.resize(obstacleGrid.size());for(int i 0;i < dp.size();i){dp[i].…

C++:std::deque简介

std::deque 是 C 标准模板库&#xff08;STL&#xff09;中的一个双端队列&#xff08;Double-ended Queue&#xff09;容器。它是一种动态数组&#xff0c;允许快速地在序列的两端插入和删除元素&#xff0c;同时支持随机访问。 特点 双端操作 支持在队列头部和尾部快速插入和…