当前位置: 首页 > news >正文

图结构使用 Louvain 社区检测算法进行分组

图结构使用 Louvain 社区检测算法进行分组

flyfish

Louvain 算法是一种基于模块度最大化的社区检测算法,核心目标是在复杂网络中找到“内部连接紧密、外部连接稀疏”的社区结构。它的优势在于高效性(可处理百万级节点的大规模网络)和近似最优解,通过“局部优化→社区聚合”的迭代流程实现模块度的逐步提升,最终得到稳定的社区划分。

在这里插入图片描述
在这里插入图片描述

一、目标:最大化“模块度(Modularity, Q)”

要理解 Louvain 算法,首先必须明确其优化目标——模块度 Q。模块度是衡量“社区划分质量”的量化指标,它描述了“网络实际社区内的边密度”与“随机网络中相同社区内的边密度”的差异:

  • 若 Q > 0:实际社区内边更密集,划分有效;
  • Q 越大(最大值约 0.7):社区结构越显著。
1. 模块度 Q 的数学定义(无向无权网络)

对于无向无权网络,模块度公式表示为:
Q=12m∑i,j(Aij−kikj2m)δ(ci,cj) Q = \frac{1}{2m} \sum_{i,j} \left( A_{ij} - \frac{k_i k_j}{2m} \right) \delta(c_i, c_j) Q=2m1i,j(Aij2mkikj)δ(ci,cj)

各符号含义:

符号含义解释
AijA_{ij}Aij网络的邻接矩阵元素:若节点 iiijjj 之间有边,Aij=1A_{ij}=1Aij=1;否则 Aij=0A_{ij}=0Aij=0
kik_iki节点 iii(即与节点 iii 直接相连的边数)。
mmm网络的总边数m=12∑i,jAijm = \frac{1}{2}\sum_{i,j} A_{ij}m=21i,jAij,无向图边计数不重复)。
cic_ici节点 iii 所属的社区标签(如 ci=0c_i=0ci=0 表示节点 iii 在社区 0 中)。
δ(x,y)\delta(x,y)δ(x,y)克罗内克函数:若 x=yx=yx=y(节点 iiijjj 同属一个社区),δ=1\delta=1δ=1;否则 δ=0\delta=0δ=0
2. 模块度变化量 ΔQ:节点移动的判断依据

Louvain 算法不直接计算全局 Q,而是通过局部模块度变化量 ΔQ 决定节点的移动——即“将某个节点 uuu 从当前社区移动到相邻节点 vvv 的社区后,模块度的变化值”。只有当 ΔQ > 0 时,移动才会提升全局模块度,才会执行。

节点 uuu 移动到社区 CCC 的 ΔQ 公式(简化版,核心逻辑):
ΔQ=(∑in+ku,C2m−(∑tot+ku2m)2)−(∑in2m−(∑tot2m)2−(ku2m)2) \Delta Q = \left( \frac{\sum_{in} + k_{u,C}}{2m} - \left( \frac{\sum_{tot} + k_u}{2m} \right)^2 \right) - \left( \frac{\sum_{in}}{2m} - \left( \frac{\sum_{tot}}{2m} \right)^2 - \left( \frac{k_u}{2m} \right)^2 \right) ΔQ=(2min+ku,C(2mtot+ku)2)(2min(2mtot)2(2mku)2)

各符号含义(聚焦局部社区 CCC 和节点 uuu):

符号含义解释
∑in\sum_{in}in社区 CCC 内部所有边的总权重(无权网络中即边数)。
ku,Ck_{u,C}ku,C节点 uuu 与社区 CCC 内所有节点之间的边的总权重(即 uuuCCC 的“连接强度”)。
∑tot\sum_{tot}tot社区 CCC 内所有节点的度的总和(即社区 CCC 与外部节点连接的“总能力”)。
kuk_uku节点 uuu 的总度(同前)。

二、Louvain 算法的核心流程:两阶段迭代

Louvain 算法通过反复执行两个阶段,逐步聚合社区、提升模块度,直到模块度无法再优化为止。整个过程是“自下而上”的社区合并(从每个节点单独为一个社区,到最终聚合为少数几个大社区)。

阶段 1:局部移动(Local Moving Phase)—— 优化单个节点的社区归属

目标:对每个节点单独调整社区,最大化局部 ΔQ,直到网络中所有节点都无法通过移动提升模块度。
具体步骤:

  1. 初始状态:将网络中每个节点都视为一个独立的社区(即初始社区数 = 节点数)。
  2. 遍历节点:按随机顺序(或固定顺序)遍历每个节点 uuu
  3. 尝试移动:对节点 uuu 的每个相邻节点 vvv(即与 uuu 有边相连的节点),计算“将 uuu 移动到 vvv 所属社区 CCC”的 ΔQ。
  4. 选择最优移动
    • 若所有 ΔQ 均 ≤ 0:节点 uuu 保持在原社区;
    • 若存在 ΔQ > 0:选择 ΔQ 最大的社区,将 uuu 移动过去。
  5. 重复迭代:重复步骤 2-4,直到遍历所有节点后,没有任何节点的移动能提升模块度(此阶段终止)。
阶段 2:社区聚合(Community Aggregation Phase)—— 构建“超级节点”网络

目标:将阶段 1 得到的每个社区,聚合为一个“超级节点”,缩小网络规模,为下一轮迭代做准备。
具体步骤:

  1. 创建超级节点:每个社区对应一个新的“超级节点”(例如,社区 0 聚合为超级节点 C0C_0C0,社区 1 聚合为 C1C_1C1 等)。
  2. 计算超级节点间的边权重
    • 若原网络中,社区 CaC_aCa 的节点与社区 CbC_bCb 的节点之间有 www 条边(无权网络中 www 是边数,加权网络中 www 是边权重总和),则在新网络中,超级节点 CaC_aCaCbC_bCb 之间添加一条权重为 www 的边。
    • 注意:社区内部的边(同一社区节点间的边)会被“折叠”到超级节点内部,不计入超级节点间的边(因为后续迭代只关注社区间的连接)。
  3. 生成新网络:新网络的节点是超级节点,边是超级节点间的聚合边权重,网络规模大幅缩小。
迭代终止条件

将阶段 2 生成的新网络,再次代入阶段 1(局部移动)阶段 2(社区聚合),反复迭代。直到某一轮迭代后,模块度不再提升(即阶段 1 无法通过移动节点优化 ΔQ,阶段 2 也无法生成更优的超级节点网络),算法终止。

import networkx as nx
import community as community_louvain
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors# 设置中文字体
plt.rcParams["font.family"] = ["SimHei"]  # Windows系统# 1. 构建水果相关的有向无环图(DAG)
G = nx.DiGraph()# 添加节点
nodes = ["苹果", "香蕉", "橙子", "草莓",  # 水果"红色", "黄色", "橙色", "甜味", "酸味",  # 属性"颜色", "味道", "浆果", "核果"  # 类别
]
G.add_nodes_from(nodes)# 添加有向边(确保无环)
edges = [("苹果", "红色"), ("苹果", "甜味"), ("苹果", "核果"),("香蕉", "黄色"), ("香蕉", "甜味"),("橙子", "橙色"), ("橙子", "酸味"),("草莓", "红色"), ("草莓", "甜味"), ("草莓", "浆果"),("红色", "颜色"), ("黄色", "颜色"), ("橙色", "颜色"),("甜味", "味道"), ("酸味", "味道")
]
G.add_edges_from(edges)# 可视化原始有向图
plt.figure(figsize=(10, 6))
pos = nx.spring_layout(G, seed=42)
# 原始图使用红色系为主的颜色
nx.draw_networkx_nodes(G, pos, node_size=800, node_color='lightcoral')
nx.draw_networkx_labels(G, pos, font_size=12, font_family=plt.rcParams["font.family"], font_color='white')
nx.draw_networkx_edges(G, pos, arrowstyle="->", arrowsize=10)
plt.title("水果相关的有向无环图(DAG)")
plt.show()# 2. 转换为无向图以适配Louvain算法
G_undirected = G.to_undirected()# 3. 计算社区划分
partition = community_louvain.best_partition(G_undirected)# 输出分组结果
print("社区划分结果:")
for community_id in set(partition.values()):members = [node for node, id in partition.items() if id == community_id]print(f"社区 {community_id}:{members}")# 4. 可视化社区划分 - 使用自定义颜色列表(避免黄色,增加红色系)
plt.figure(figsize=(10, 6))# 自定义颜色列表(选择与白色文字对比度高的颜色,替换黄色为红色系)
custom_colors = ['#FF6B6B',  # 浅红色'#4ECDC4',  # 青绿色'#45B7D1',  # 天蓝色'#FFA07A',  # 浅橙色'#98D8C8'   # 淡青色
]# 根据社区数量选择合适的颜色
num_communities = max(partition.values()) + 1
used_colors = custom_colors[:num_communities]# 绘制节点(使用自定义颜色)
nx.draw_networkx_nodes(G_undirected, pos, partition.keys(),node_size=800, node_color=[used_colors[id] for id in partition.values()])# 绘制标签(白色文字)
nx.draw_networkx_labels(G_undirected, pos, font_size=12, font_family=plt.rcParams["font.family"],font_color='white')# 绘制边
nx.draw_networkx_edges(G_undirected, pos, alpha=0.5)plt.title("Louvain算法社区划分结果")
plt.show()

为何 Louvain 算法高效且实用?

  1. 低时间复杂度:每轮迭代的时间复杂度约为 O(n)O(n)O(n)nnn 是节点数),即使是百万级节点的网络,也能快速运行(这是它比传统全局优化算法(如谱聚类)更实用的核心原因)。
  2. 近似最优解:虽然是局部优化,但通过多轮迭代的社区聚合,最终能逼近全局最大模块度(在大多数真实网络中,效果接近最优)。
  3. 适配无向网络:标准 Louvain 算法仅支持无向网络(加权/无权均可);若处理有向网络(水果 DAG),需先将其转换为无向网络(如 G.to_undirected())。

结合代码,对应算法原理

import networkx as nx
import community as community_louvain
import matplotlib.pyplot as plt# 1. 构建水果DAG(有向)
G = nx.DiGraph()
nodes = ["苹果", "香蕉", "橙子", "草莓", "红色", "黄色", "橙色", "甜味", "酸味", "颜色", "味道", "浆果", "核果"]
G.add_nodes_from(nodes)
edges = [("苹果", "红色"), ("苹果", "甜味"), ("苹果", "核果"), ...]  # 省略部分边
G.add_edges_from(edges)# 2. 转换为无向图 → 适配标准Louvain算法(仅支持无向网络)
G_undirected = G.to_undirected()  # 关键:有向边转为无向边,确保A_ij=A_ji# 3. Louvain算法核心:community_louvain.best_partition()
partition = community_louvain.best_partition(G_undirected)  # 内部执行完整的两阶段迭代
代码与算法原理的对应:
  1. 无向图转换(G.to_undirected())
    标准 Louvain 算法基于无向图的邻接矩阵 AijA_{ij}Aij(满足 Aij=AjiA_{ij}=A_{ji}Aij=Aji),因此必须将你的有向 DAG 转为无向图,否则无法计算节点度 kik_iki 和总边数 mmm

  2. community_louvain.best_partition() 内部逻辑
    这个函数封装了 Louvain 算法的完整迭代流程:

    • 初始状态:为每个水果/属性节点(如“苹果”“红色”“颜色”)分配独立社区 ID(例如,“苹果”=0,“香蕉”=1,…,“核果”=12)。
    • 阶段 1(局部移动)
      遍历每个节点(如“苹果”),计算其相邻节点(“红色”“甜味”“核果”)所属社区的 ΔQ,选择 ΔQ 最大的社区移动。例如:
      • “苹果”与“红色”相连,计算将“苹果”移入“红色”社区的 ΔQ;
      • 若 ΔQ > 0,且是所有相邻社区中最大的,则“苹果”与“红色”归为同一社区。
    • 阶段 2(社区聚合)
      将阶段 1 得到的社区(如“苹果-红色-草莓”为一个社区,“香蕉-黄色”为另一个社区)聚合为超级节点,构建新的小网络,再次执行阶段 1。
    • 迭代终止:直到模块度不再提升,输出最终的社区划分(即 partition 字典,键是节点,值是社区 ID)。
  3. 输出结果(社区划分)
    打印的“社区 0:[苹果, 红色, 草莓],社区 1:[香蕉, 黄色]…”,正是 Louvain 算法通过多轮两阶段迭代后,最大化模块度得到的结果——这些社区内部的节点连接更紧密(如“苹果-红色”“草莓-红色”有边),外部连接更稀疏。

http://www.dtcms.com/a/347451.html

相关文章:

  • P9246 [蓝桥杯 2023 省 B] 砍树
  • 蓝桥杯算法之基础知识(2)——Python赛道
  • ComfyUI Portrait Master肖像大师中文版
  • TopK问题(堆排序)-- go
  • 牛客面经2 京东社招-002
  • 知识蒸馏 Knowledge Distillation 序列的联合概率 分解成 基于历史的条件概率的连乘序列
  • 基于SpringBoot的招聘系统源码
  • 破解秘籍——hashcat使用宝典
  • 商超场景徘徊识别误报率↓79%!陌讯多模态时序融合算法落地优化
  • 书写本体论视域下的文字学理论重构
  • 防范瓦斯爆炸:AI摄像机如何杜绝煤矿井下违规抽烟?
  • 复杂工业场景误报率↓85%!陌讯多模态火焰识别算法实战解析
  • ⸢ 零 ⸥ ⤳ 安全体系构建内容结构
  • 卷积神经网络的基本概念
  • 【码蹄杯】2025年本科组省赛第一场
  • 解决Node.js安装与npx命令问题
  • Stm32通过ESP8266 WiFi连接阿里云平台
  • 使用Lora微调大模型介绍篇
  • 【Python】新手入门:Python标准库有哪些常用模块?
  • 使用Screenpipe+本地大模型实现私人助手Agent
  • 【小宁学习日记4 Python】Python 变量与标识符全解:编程入门的基石
  • 简单AI:搜狐公司旗下AI绘画产品
  • 最新短网址源码,防封。支持直连、跳转。 会员无广
  • 部署PG一主一从
  • 使用Proxifier+vmware碰到的一些问题
  • GEO优化服务商:AI时代数字经济的新引擎——解码行业发展与技术创新实践
  • 蔬菜溯源系统的开发与设计小程序
  • 【基础-判断】使用http模块发起网络请求时,必须要使用on(‘headersReceive’)订阅请求头,请求才会成功。
  • 算法第五十五天:图论part05(第十一章)
  • 微服务架构中服务发现机制的实现与优化 NO.3(回复征途黯然.)