生物医学网络科学Biomedical Network Science
Example08 / 15

标准 SBM 与度修正 SBM(DC-SBM)在空手道网络上的最大似然划分

所属专题
社团结构与社区发现
下载
Notebook · .py

Karrer 与 Newman(2011)把块模型参数用最大似然估计消去后,得到只依赖划分的目标函数: 标准 SBM: L = Σ_rs m_rs log[ m_rs / (n_r n_s) ] DC-SBM: L = Σ_rs m_rs log[ m_rs / (κ_r κ_s) ] m_rs 为组 r、s 之间的边数(r=s 时计两次),n_r 为组内节点数,κ_r 为组内度之和。 用“随机初始化 + 逐点移动的贪心爬山”在 K=2 下各做 200 次重启,取似然最大的划分,与真实两派比较。 输出:DC-SBM 的最优划分与真实两派基本一致;标准 SBM 的最优划分倾向于把高度节点与低度节点分开。

Output输出图

标准 SBM 与度修正 SBM(DC-SBM)在空手道网络上的最大似然划分:输出图 1

Output文字输出

标准 SBM: logL = -233.53,与真实两派一致的比例 = 0.53,两组大小 [5, 29],两组平均度 12.8 / 3.2
DC-SBM: logL = -739.39,与真实两派一致的比例 = 0.94,两组大小 [17, 17],两组平均度 4.6 / 4.6
真实两派在两种模型下的 logL:SBM -267.65,DC-SBM -743.21

Code代码

# Karrer 与 Newman(2011)把块模型参数用最大似然估计消去后,得到只依赖划分的目标函数:
#   标准 SBM:  L = Σ_rs m_rs log[ m_rs / (n_r n_s) ]
#   DC-SBM:    L = Σ_rs m_rs log[ m_rs / (κ_r κ_s) ]
# m_rs 为组 r、s 之间的边数(r=s 时计两次),n_r 为组内节点数,κ_r 为组内度之和。
# 用“随机初始化 + 逐点移动的贪心爬山”在 K=2 下各做 200 次重启,取似然最大的划分,与真实两派比较。
# 输出:DC-SBM 的最优划分与真实两派基本一致;标准 SBM 的最优划分倾向于把高度节点与低度节点分开。
import networkx as nx
import numpy as np

K = nx.karate_club_graph()
G = nx.Graph(K.edges())
n = G.number_of_nodes()
A = nx.to_numpy_array(G, nodelist=range(n))
deg = A.sum(1)
truth = np.array([0 if K.nodes[i]["club"] == "Mr. Hi" else 1 for i in range(n)])


def xlogy(x, y):
    with np.errstate(divide="ignore", invalid="ignore"):
        return np.where(x > 0, x * np.log(x / y), 0.0)


def loglik(z, kind, K=2):
    Z = np.eye(K)[z]
    M = Z.T @ A @ Z                                # m_rs,对角线为组内边数的 2 倍
    size = Z.T @ (deg if kind == "dc" else np.ones(n))
    return xlogy(M, np.outer(size, size)).sum()


def fit(kind, restarts=200, K=2, seed=0):
    rng = np.random.default_rng(seed)
    best_L, best_z = -np.inf, None
    for _ in range(restarts):
        z = rng.integers(0, K, n)
        L = loglik(z, kind, K)
        improved = True
        while improved:
            improved = False
            for i in rng.permutation(n):
                old = z[i]
                for r in range(K):
                    if r == old:
                        continue
                    z[i] = r
                    L_new = loglik(z, kind, K)
                    if L_new > L + 1e-10:
                        L, old, improved = L_new, r, True
                z[i] = old
        if L > best_L:
            best_L, best_z = L, z.copy()
    return best_L, best_z


fits = {}                                       # 存下结果用于作图
for kind, name in [("sbm", "标准 SBM"), ("dc", "DC-SBM")]:
    L, z = fit(kind)
    fits[kind] = z
    agree = max((z == truth).mean(), (z != truth).mean())
    mean_deg = [deg[z == r].mean() for r in range(2)]
    print("%s: logL = %.2f,与真实两派一致的比例 = %.2f,两组大小 %s,两组平均度 %.1f / %.1f"
          % (name, L, agree, np.bincount(z).tolist(), *mean_deg))
print("真实两派在两种模型下的 logL:SBM %.2f,DC-SBM %.2f" % (loglik(truth, "sbm"), loglik(truth, "dc")))

# 作图:两种模型的最优划分(颜色 = 推断的组,节点大小 ∝ 度,红框 = 与真实派别不一致)
import matplotlib.pyplot as plt

pos = nx.spring_layout(G, seed=4)
fig, axes = plt.subplots(1, 2, figsize=(10, 4))
for ax, (kind, name) in zip(axes, [("sbm", "standard SBM"), ("dc", "degree-corrected SBM")]):
    z = fits[kind]
    zz = z if (z == truth).mean() >= 0.5 else 1 - z         # 对齐标签
    nx.draw_networkx_edges(G, pos, ax=ax, edge_color="0.7", alpha=0.4)
    nx.draw_networkx_nodes(G, pos, ax=ax, nodelist=range(n), node_size=20 + 8 * deg,
                           node_color=["#fdb863" if g == 0 else "#8073ac" for g in zz],
                           edgecolors=["red" if a != b else "0.4" for a, b in zip(zz, truth)])
    ax.set_title("%s: agreement with factions = %.2f" % (name, (zz == truth).mean()), fontsize=10)
    ax.axis("off")
plt.tight_layout(); plt.show()

Run在浏览器中运行

首次加载 Python 环境约需 20–40 秒;可以修改参数后重新运行。

标准 SBM 与度修正 SBM(DC-SBM)在空手道网络上的最大似然划分