Mímisbrunnr知恵の泉

← ネットワーク科学 一覧

🎓 レベル:標準 | 重要度:A(必須)

📎 前提:コミュニティ検出とは | 関連:Louvain法・Leiden法configurationモデル・次数保存ヌルモデル

要点(BLUF)

概念:ランダムより密ならコミュニティ

「内部エッジが多い=良いコミュニティ」と言いたいけれど、密なネットワークなら何で割ってもエッジは多い。だから基準が要ります。モジュラリティは「同じ次数列を持つランダムグラフ(ヌルモデル、configurationモデル・次数保存ヌルモデル)と比べて、内部エッジがどれだけ余計に多いか」で良さを測ります。偶然では説明できない密さこそがコミュニティの証拠だ、という発想です。

数式による定義

エッジ総数を mm、ノード i,ji,j が同じコミュニティなら δ(ci,cj)=1\delta(c_i,c_j)=1 として、

Q=12mi,j(Aijkikj2m)δ(ci,cj)Q = \frac{1}{2m} \sum_{i,j} \left( A_{ij} - \frac{k_i k_j}{2m} \right) \delta(c_i, c_j)

つまり QQ は「同じコミュニティ内で、実際のエッジ − ランダム期待値」を全ペアで足したもの。QQ[1/2,1][-1/2, 1] の範囲をとり、経験的に 0.30.3 を超えると有意なコミュニティ構造があると見なされます。

コードで確認

import networkx as nx
import numpy as np
from networkx.algorithms.community import modularity, greedy_modularity_communities

G = nx.karate_club_graph()
clubs = nx.get_node_attributes(G, "club")
mr = set(n for n in G if clubs[n] == "Mr. Hi")

# 実際の2派閥分割
part_true = [mr, set(G.nodes()) - mr]
print("実派閥分割の Q =", round(modularity(G, part_true), 4))

# でたらめな2分割
rng = np.random.default_rng(0)
nodes = list(G.nodes()); rng.shuffle(nodes)
rand_part = [set(nodes[:17]), set(nodes[17:])]
print("ランダム2分割の Q =", round(modularity(G, rand_part), 4))

# 貪欲にQを最大化
greedy = greedy_modularity_communities(G)
print("貪欲最大化 コミュニティ数:", len(greedy), " Q =", round(modularity(G, greedy), 4))

実行結果:

実派閥分割の Q = 0.3914
ランダム2分割の Q = -0.0965
貪欲最大化 コミュニティ数: 3  Q = 0.411

実際の派閥分割は Q=0.39Q=0.39(有意な構造)。でたらめな分割は Q<0Q<0(ランダム期待より内部エッジが少ない=コミュニティになっていない)。貪欲最大化は3コミュニティに分け Q=0.41Q=0.41 とさらに高い — 最大化は必ずしも「真の」2派閥を返さない点に注目してください。

数式の直観的意味

Aijkikj/(2m)A_{ij} - k_i k_j/(2m) は「実際の親密さ − たまたまの親密さ」です。次数の高いノードどうしは偶然でも繋がりやすい(kikjk_i k_j が大きい)ので、その分を割り引く。割り引いてもなお内部エッジが多ければ、それは偶然でなく「集団としてまとまっている」証拠。QQ の最大化とは、この「偶然を超えた密さ」が最大になる分割を探すことです。

⚠️ よくある誤解・落とし穴

対応シミュレーション

本文のコードがそのまま検証用です。最大化アルゴリズムは Louvain法・Leiden法、ヌルモデルは configurationモデル・次数保存ヌルモデル

関連