Example03 / 09
社团发现、模块度与结果稳定性
在空手道俱乐部网络上运行 Louvain(固定种子)与贪心模块度算法,比较社团数和模块度 Q; 再用 10 个不同种子运行 Louvain,报告 Q 的范围;最后用归一化互信息(NMI) 把 Louvain 划分与俱乐部的真实分裂(节点属性 ‘club’)对照,并按社团着色画图。 解读:Louvain 有随机性,应固定种子以便复算,并报告多次运行的范围;NMI = 1 表示两种划分完全一致。
Output输出图

Output文字输出
Louvain 社团数 = 4, Q = 0.444 Greedy 社团数 = 3, Q = 0.411 Louvain 10 个种子:Q 范围 0.427 – 0.445 Louvain 与真实分裂的 NMI = 0.600
Code代码
# 在空手道俱乐部网络上运行 Louvain(固定种子)与贪心模块度算法,比较社团数和模块度 Q;
# 再用 10 个不同种子运行 Louvain,报告 Q 的范围;最后用归一化互信息(NMI)
# 把 Louvain 划分与俱乐部的真实分裂(节点属性 'club')对照,并按社团着色画图。
# 解读:Louvain 有随机性,应固定种子以便复算,并报告多次运行的范围;NMI = 1 表示两种划分完全一致。
import numpy as np
import networkx as nx
import matplotlib.pyplot as plt
G = nx.karate_club_graph()
truth = [G.nodes[v]["club"] for v in G]
def nmi(labels_a, labels_b):
"""归一化互信息(NMI),1 表示两种划分完全一致。"""
a = np.unique(labels_a, return_inverse=True)[1]
b = np.unique(labels_b, return_inverse=True)[1]
n = len(a)
C = np.zeros((a.max() + 1, b.max() + 1))
for i, j in zip(a, b):
C[i, j] += 1
P = C / n; pa = P.sum(1); pb = P.sum(0)
nz = P > 0
I = (P[nz] * np.log(P[nz] / np.outer(pa, pb)[nz])).sum()
Ha = -(pa[pa > 0] * np.log(pa[pa > 0])).sum(); Hb = -(pb[pb > 0] * np.log(pb[pb > 0])).sum()
return 2 * I / (Ha + Hb)
comms_louvain = nx.community.louvain_communities(G, seed=1)
comms_greedy = nx.community.greedy_modularity_communities(G)
for name, cs in [("Louvain", comms_louvain), ("Greedy", comms_greedy)]:
print("%-8s 社团数 = %d, Q = %.3f" % (name, len(cs), nx.community.modularity(G, cs)))
Qs = [nx.community.modularity(G, nx.community.louvain_communities(G, seed=s)) for s in range(10)]
print("Louvain 10 个种子:Q 范围 %.3f – %.3f" % (min(Qs), max(Qs)))
label = {v: i for i, c in enumerate(comms_louvain) for v in c}
print("Louvain 与真实分裂的 NMI = %.3f" % nmi([label[v] for v in G], truth))
pos = nx.spring_layout(G, seed=3)
nx.draw_networkx(G, pos, node_color=[label[v] for v in G], cmap="tab10", node_size=200, font_size=7)
plt.axis("off"); plt.title("Louvain communities"); plt.show()Run在浏览器中运行
首次加载 Python 环境约需 20–40 秒;可以修改参数后重新运行。