Example09 / 15
Infomap 的目标函数——两层映射方程(map equation)
对无向无权网络,随机游走的稳态访问概率 p_α = k_α / 2m;模块 i 的退出概率 q_i = (i 的外连边数)/2m。 两层映射方程(Rosvall 与 Bergstrom 2008)给出描述一步游走所需的平均码长(bit): L(M) = plogp(Σq_i) - 2 Σ plogp(q_i) - Σ_α plogp(p_α) + Σ_i plogp(q_i + Σ_{α∈i} p_α),plogp(x)=x·log2 x。 (a) 比较空手道网络的几种划分(单一模块、真实两派、Louvain 划分)的 L; (b) 在“K5 团组成的环”上比较“每个团一个模块”与“相邻两团合并”两种划分的 Q 与 L。 输出:(a) L 越小代表划分越能压缩游走的描述;(b) 团足够多时 Q 偏好合并(分辨率极限), 而映射方程在这一构造下仍偏好把每个团分开。
Output输出图

Output文字输出
(a) 单一模块 Q = 0.000 L = 4.704 bit (a) 真实两派 Q = 0.358 L = 4.462 bit (a) Louvain(4 个) Q = 0.419 L = 4.336 bit (b) 10 个 K5: 每团一模块 Q=0.8091 L=3.067 | 两两合并 Q=0.7545 L=3.688 (b) 30 个 K5: 每团一模块 Q=0.8758 L=3.211 | 两两合并 Q=0.8879 L=3.760 (b) 60 个 K5: 每团一模块 Q=0.8924 L=3.302 | 两两合并 Q=0.9212 L=3.806
Code代码
# 对无向无权网络,随机游走的稳态访问概率 p_α = k_α / 2m;模块 i 的退出概率 q_i = (i 的外连边数)/2m。
# 两层映射方程(Rosvall 与 Bergstrom 2008)给出描述一步游走所需的平均码长(bit):
# L(M) = plogp(Σq_i) - 2 Σ plogp(q_i) - Σ_α plogp(p_α) + Σ_i plogp(q_i + Σ_{α∈i} p_α),plogp(x)=x·log2 x。
# (a) 比较空手道网络的几种划分(单一模块、真实两派、Louvain 划分)的 L;
# (b) 在“K5 团组成的环”上比较“每个团一个模块”与“相邻两团合并”两种划分的 Q 与 L。
# 输出:(a) L 越小代表划分越能压缩游走的描述;(b) 团足够多时 Q 偏好合并(分辨率极限),
# 而映射方程在这一构造下仍偏好把每个团分开。
import networkx as nx
import numpy as np
def plogp(x):
x = np.asarray(x, float)
return np.where(x > 0, x * np.log2(np.where(x > 0, x, 1)), 0.0)
def map_equation(G, communities):
m2 = 2 * G.number_of_edges()
label = {v: i for i, c in enumerate(communities) for v in c}
p = np.array([G.degree(v) / m2 for v in G])
q = np.zeros(len(communities))
pin = np.zeros(len(communities))
for v in G:
pin[label[v]] += G.degree(v) / m2
for u, v in G.edges():
if label[u] != label[v]:
q[label[u]] += 1 / m2
q[label[v]] += 1 / m2
return float(plogp(q.sum()) - 2 * plogp(q).sum() - plogp(p).sum() + plogp(q + pin).sum())
K = nx.karate_club_graph()
G = nx.Graph(K.edges())
club = [{v for v in K if K.nodes[v]["club"] == "Mr. Hi"}, {v for v in K if K.nodes[v]["club"] != "Mr. Hi"}]
louv = nx.community.louvain_communities(G, seed=1)
for name, part in [("单一模块", [set(G)]), ("真实两派", club), ("Louvain(%d 个)" % len(louv), louv)]:
print("(a) %-12s Q = %.3f L = %.3f bit" % (name, nx.community.modularity(G, part), map_equation(G, part)))
ring = [] # 存下结果用于作图
for n_c in [10, 30, 60]:
R = nx.ring_of_cliques(n_c, 5) # 第 j 个团为节点 5j..5j+4
single = [set(range(5 * j, 5 * j + 5)) for j in range(n_c)]
pairs = [single[j] | single[j + 1] for j in range(0, n_c, 2)]
print("(b) %2d 个 K5: 每团一模块 Q=%.4f L=%.3f | 两两合并 Q=%.4f L=%.3f"
% (n_c, nx.community.modularity(R, single), map_equation(R, single),
nx.community.modularity(R, pairs), map_equation(R, pairs)))
ring.append((n_c, nx.community.modularity(R, single), nx.community.modularity(R, pairs),
map_equation(R, single), map_equation(R, pairs)))
# 作图:团环上“每团一模块”与“两两合并”的 Q(越大越好)和 L(越小越好)
import matplotlib.pyplot as plt
r = np.array(ring)
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4))
for ax, j, lab in [(ax1, 1, "modularity Q (higher is better)"), (ax2, 3, "map equation L, bits (lower is better)")]:
ax.plot(r[:, 0], r[:, j], "o-", label="one module per clique")
ax.plot(r[:, 0], r[:, j + 1], "s--", label="adjacent cliques merged")
ax.set_xlabel("number of K5 cliques in the ring"); ax.set_ylabel(lab); ax.legend(fontsize=8)
ax1.set_title("Q prefers merging for many cliques (resolution limit)", fontsize=10)
ax2.set_title("Map equation keeps cliques separate", fontsize=10)
plt.tight_layout(); plt.show()Run在浏览器中运行
首次加载 Python 环境约需 20–40 秒;可以修改参数后重新运行。