Upgrade to Pro — share decks privately, control downloads, hide ads and more …

NetworkXとGNNで学ぶグラフデータ分析入門〜複雑な関係性を解き明かすPythonの力〜

 NetworkXとGNNで学ぶグラフデータ分析入門〜複雑な関係性を解き明かすPythonの力〜

PyConJP2025発表資料

More Decks by みずほリサーチ&テクノロジーズ株式会社 先端技術研究部

Other Decks in Programming

Transcript

  1. 自己紹介 所属 みずほリサーチ&テクノロジーズ株式会社 先端技術研究部 データプラットフォームの設計・運用に関する研究開発に従事 社外発表アカウント Qiita: fujine(mhrt-adv) SpeakerDeck: mhrtech

    過去のPyCon JP発表 PyConJP 2021: scikit-learnの新機能を紹介します PyConJP 2022: Pandas卒業?大規模データを様々なパッケージで高速処理してみる PyConJP 2024: あなたのアプリケーションをレガシーコードにしないための実践Pytest入門 2
  2. なぜ「グラフ」で表現するのか? 表構造データ 個々の関係性はわかるが、全体の構造は 把握しにくい UserID FriendID A B A C

    B C B D グラフデータ 関係性や構造を直感的に可視化・分析 できるのが最大のメリット 7
  3. グラフを構成する3つの要素 ノード (Node) グラフの「点」 モノやエンティティを表す 例: 人、駅、商品 エッジ (Edge) ノード間をつなぐ「線」

    関係や接続を表す 例: 友達、路線、購買 属性 (Attribute) ノードやエッジが持つ追加情報 例: 年齢、距離、評価 8
  4. グラフの種類 無向グラフと有向グラフ 無向: 関係に方向が無い(例: 資産の共 有) 有向: 関係に方向がある(例: 資金移動、 受発注取引)

    重みの有無 重みなし: つながりの有無が重要(例:取 引の有無) 重み付き: つながりの強さや大きさが重要 (例: 取引の金額や頻度) 9
  5. 環境準備 パッケージのインストール pip install networkx scipy matplotlib # NetworkXとグラフ可視化に使用 pip

    install torch torch_geometric # GNNで使用 パッケージのインポート import networkx as nx import matplotlib.pyplot as plt 12
  6. NetworkXによるグラフの作成 # 1. 空のグラフを作成 G = nx.Graph() # 2. ノードを追加

    G.add_node("A") G.add_nodes_from(["B", "C"]) # 複数ノードを一括追加 G.add_node("D", role="User") # 属性付きノードを追加 # 3. エッジを追加 G.add_edge("A", "B") G.add_edges_from([("A", "C"), ("B", "C")]) # 複数エッジを一括追加 G.add_weighted_edges_from([("B", "D", 5)]) # 重み付きエッジを追加 13
  7. グラフ情報の取得 # グラフのノード数とエッジ数を取得 print(G.number_of_nodes()) # -> 4 print(G.number_of_edges()) # ->

    4 # ノードリストとエッジリストを取得(ビューオブジェクトを返すため、リストに変換) print(list(G.nodes)) # -> ['A', 'B', 'C', 'D'] print(list(G.edges)) # -> [('A', 'B'), ('A', 'C'), ('B', 'C'), ('B', 'D')] # ノード"A"の隣接ノードを取得 print(list(G.neighbors("A"))) # -> ['B', 'C'] # ノード"A"の次数(Aに接続しているノード数)を取得 print(G.degree("A")) -> 2 14
  8. サンプルデータ(友人のコミュニティ) G = nx.Graph() G.add_weighted_edges_from([ ('Alice', 'Bob', 2), ('Alice', 'Carol',

    1), ('Bob', 'Carol', 1), ('Bob', 'Dave', 1), ('Bob', 'Frank', 2), ('Carol', 'Dave', 1), ('Carol', 'Frank', 1), ('Dave', 'Frank', 1), ('Eve', 'Frank', 1), ('Eve', 'George', 1), ('George', 'Smith', 1), ('Frank', 'George', 1), ('Henry', 'Isla', 2), ('Henry', 'Jacob', 1), ('Isla', 'Jacob', 1), ('Isla', 'Smith', 1), ('Frank', 'Henry', 2) ]) 19
  9. 中心性分析 次数中心性:グラフにおける次数(隣接ノード数)の高さ # 次数中心性 centrality = nx.degree_centrality(G) # 中心性の値に応じて、各ノードのサイズを設定 node_size

    = [v * 10000 for v in centrality.values()] # ノード、エッジ、ノードラベルを描画 nx.draw_networkx_nodes(G, pos, node_color="skyblue", node_size=node_size) nx.draw_networkx_edges(G, pos, width=1.0) nx.draw_networkx_labels(G, pos, font_size=10) 20
  10. 最短経路探索 2つのノード間の最短経路を、重みなし、重みありの2パターンで探索 # 始点と終点を設定 source_node, target_node = "Alice", "Isla" #

    重みなし最短経路(経由するエッジの数が最も少ない経路) shortest_path = nx.shortest_path(G, source_node, target_node) shortest_edges = list(zip(shortest_path, shortest_path[1:])) # 重みあり最短経路(エッジの重みの合計が最小になる経路) dijkstra_path = nx.dijkstra_path(G, source_node, target_node, weight="weight") dijkstra_edges = list(zip(dijkstra_path, dijkstra_path[1:])) # 重みなし最短経路を赤、重みあり最短経路を緑、に強調してグラフ表示 nx.draw_networkx(G, pos, with_labels=True, node_color='skyblue', node_size=2000) nx.draw_networkx_edges(G, pos, edgelist=shortest_edges, edge_color="red", width=3) nx.draw_networkx_edges(G, pos, edgelist=dijkstra_edges, edge_color="green", width=3) 22
  11. コミュニティ検出 グラフ内で密に結合しているノードのグループ(コミュニティ)を分類 # Girvan–Newmanアルゴリズムでコミュニティを2分割 # 媒介中心性が高いノードでグラフを階層的に分割 groups = next(nx.community.girvan_newman(G)) #

    1番目のノードグループを赤で描画 nx.draw_networkx_nodes(G, pos, node_size=2000, nodelist=list(groups[0]), node_color="lightcoral") # 2番目のノードグループを緑で描画 nx.draw_networkx_nodes(G, pos, node_size=2000, nodelist=list(groups[1]), node_color="lightgreen") 24
  12. GNNの実装例(データ準備) Coraデータセット(論文の引用・被引用関係を集めたデータセット)を使用して、論文 のカテゴリを予測する ノード: 論文(計2,708ノード) エッジ: 論文同士の引用関係(計10,556エッジ) ノード特徴量: 各論文が特定の単語を含むか否か(1,433次元ベクトル) ラベル:

    論文のカテゴリ(7種類) import torch import torch.nn as nn import torch.nn.functional as F from torch_geometric.datasets import Planetoid from torch_geometric.nn import GCNConv dataset = Planetoid(root="dataset", name="Cora") data = dataset[0] # >> Data(x=[2708, 1433], edge_index=[2, 10556], y=[2708], train_mask=[2708], ...) 28
  13. GNNの実装例(モデル定義) PyG(PyTorch Geometric) を使い、2層のシンプルなGNNモデルを定義 GCNレイヤーにより、自身のノードだけでなく隣接ノードの特徴量も畳み込むことが可能 class GCN(nn.Module): def __init__(self, in_channels,

    hidden_channels, out_channels): super(GCN, self).__init__() self.conv1 = GCNConv(in_channels, hidden_channels) self.conv2 = GCNConv(hidden_channels, out_channels) def forward(self, x, edge_index): # 最初のGCN層 x = self.conv1(x, edge_index) x = F.relu(x) x = F.dropout(x, p=0.5, training=self.training) # 2番目のGCN層 x = self.conv2(x, edge_index) return F.log_softmax(x, dim=1) 29
  14. GNNの実装例(モデルの初期化、学習処理) # モデルの初期化 model = GCN(in_channels=num_features, hidden_channels=16, out_channels=num_classes) # 損失関数と最適化手法の定義

    criterion = nn.NLLLoss() optimizer = torch.optim.Adam(model.parameters(), lr=0.01, weight_decay=5e-3) # 訓練処理の実装 def train(): model.train() optimizer.zero_grad() out = model(data.x, data.edge_index) loss = criterion(out[data.train_mask], data.y[data.train_mask]) loss.backward() optimizer.step() return loss.item() 30
  15. 実装例(推論処理) def test(): model.eval() out = model(data.x, data.edge_index) pred =

    out.argmax(dim=1) correct_test = pred[data.test_mask] == data.y[data.test_mask] accuracy_test = int(correct_test.sum()) / int(data.test_mask.sum()) return accuracy_test # 500エポックで学習 for epoch in range(500): loss = train() # 最終的なテスト精度を出力 test_acc = test() print(f"最終的なテスト精度: {test_acc:.4f}") -> 0.8160 31