mcpField
T
torch-geometric

torch-geometric

github.com/crazymsnデータ・データベース2336 回インストール22 週間前に更新
リポジトリ

torch-geometric について

PyTorchでグラフニューラルネットワークを構築する開発者向けのガイドです。グラフデータ構造、GNNレイヤー、学習ツールに加え、異種グラフ、ミニバッチ処理、近傍サンプリング、予測の解釈方法を扱います。

主な機能

  1. 01DataとHeteroDataのグラフ構造
  2. 02GNNレイヤーとメッセージパッシング
  3. 03グラフデータセットと変換
  4. 04ミニバッチ処理と近傍サンプリング
  5. 05異種グラフのモデリングと説明

活用シーン

  1. 01ノード分類・グラフ分類モデルの学習
  2. 02近傍サンプリングによる大規模グラフ学習
  3. 03複数のノード型・エッジ型を持つモデルの構築
  4. 04GNNの予測結果の解釈

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/crazymsn/academic-skills --skill torch-geometric

インストール後、Claude Code で/torch-geometricを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
custom_datasets.md
explainability.md
heterogeneous.md
link_prediction.md
message_passing.md
scaling.md

詳細

PyTorch Geometric (PyG)

PyG は、PyTorch 上に構築されたグラフニューラルネットワーク向けの標準ライブラリです。グラフ用のデータ構造、60 種類以上の GNN レイヤーの実装、スケーラブルなミニバッチ学習、異種グラフのサポートを提供します。

インストール:uv add torch_geometric(または uv pip install torch_geometric。PyTorch が必要です)。オプション:高速化された演算には pyg-lib、torch-scatter、torch-sparse、torch-cluster を使用できます。

コアコンセプト

グラフデータ:Data と HeteroData

グラフは Data オブジェクトに格納されます。主な属性は次のとおりです。

from torch_geometric.data import Data

data = Data(
    x=node_features,          # [num_nodes, num_node_features]
    edge_index=edge_index,     # [2, num_edges] — COO format, dtype=torch.long
    edge_attr=edge_features,   # [num_edges, num_edge_features]
    y=labels,                  # node-level [num_nodes, *] or graph-level [1, *]
    pos=positions,             # [num_nodes, num_dimensions] (for point clouds/spatial)
)

edge_index の形式は重要です。[2, num_edges] のテンソルで、edge_index[0] はソースノード、edge_index[1] はターゲットノードです。タプルのリストではありません。エッジのペアが行として並んでいる場合は、転置して .contiguous() を呼び出します。

# If edges are [[src1, dst1], [src2, dst2], ...] — transpose first:
edge_index = edge_pairs.t().contiguous()

無向グラフでは、両方向を含めます。エッジ (0,1) の場合、edge_index に [0,1] と [1,0] の両方が必要です。

異種グラフには HeteroData を使用します。詳しくは、以下の「異種グラフ」セクションを参照してください。

データセット

PyG には、ダウンロードと前処理が自動で行われる標準データセットが多数バンドルされています。

from torch_geometric.datasets import Planetoid, TUDataset

# Single-graph node classification (Cora, Citeseer, Pubmed)
dataset = Planetoid(root='./data', name='Cora')
data = dataset[0]  # single graph with train/val/test masks

# Multi-graph classification (ENZYMES, MUTAG, IMDB-BINARY, etc.)
dataset = TUDataset(root='./data', name='ENZYMES')
# dataset[0], dataset[1], ... are individual graphs

タスク別の代表的なデータセット:

  • ノード分類:Planetoid(Cora/Citeseer/Pubmed)、OGB(ogbn-arxiv、ogbn-products、ogbn-mag)
  • グラフ分類:TUDataset(MUTAG、ENZYMES、PROTEINS、IMDB-BINARY)、OGB(ogbg-molhiv)
  • リンク予測:OGB(ogbl-collab、ogbl-citation2)
  • 分子:QM7、QM9、MoleculeNet
  • 点群/メッシュ:ShapeNet、ModelNet10/40、FAUST
変換

変換は、torchvision の変換と同様に、グラフデータを前処理または拡張します。

import torch_geometric.transforms as T

# Common transforms
T.NormalizeFeatures()    # Row-normalize node features to sum to 1
T.ToUndirected()         # Add reverse edges to make graph undirected
T.AddSelfLoops()         # Add self-loop edges
T.KNNGraph(k=6)          # Build k-NN graph from point cloud positions
T.RandomJitter(0.01)     # Random noise augmentation on positions
T.Compose([...])         # Chain multiple transforms

# Apply as pre_transform (once, saved to disk) or transform (every access)
dataset = ShapeNet(root='./data', pre_transform=T.KNNGraph(k=6),
                   transform=T.RandomJitter(0.01))

GNN モデルの構築

クイックスタート:組み込みレイヤーの使用

GNN を最も手早く構築するには、torch_geometric.nn の畳み込みレイヤーを積み重ねます。

import torch
import torch.nn.functional as F
from torch_geometric.nn import GCNConv

class GCN(torch.nn.Module):
    def __init__(self, in_channels, hidden_channels, out_channels):
        super().__init__()
        self.conv1 = GCNConv(in_channels, hidden_channels)
        self.conv2 = GCNConv(hidden_channels, out_channels)

    def forward(self, x, edge_index):
        x = self.conv1(x, edge_index).relu()
        x = F.dropout(x, p=0.5, training=self.training)
        x = self.conv2(x, edge_index)
        return x

重要:PyG の畳み込みレイヤーには活性化関数が含まれていません。柔軟性を保つための設計です。各レイヤーの後に、自分で適用してください。

畳み込みレイヤーの選択

タスクとグラフ構造に応じて選択します。

レイヤー最適な用途主なアイデア
GCNConv同種グラフ、半教師ありノード分類スペクトル理論に着想を得た、次数正規化による集約
GATConv / GATv2Conv近傍ノードの重要度が異なる場合注意機構で重み付けされたメッセージ
SAGEConv大規模グラフ、帰納的設定サンプリングに適した、学習可能な集約
GINConvグラフ分類、表現力の最大化WL テストと同等の表現力
TransformerConv豊富なエッジ特徴、複雑な相互作用エッジ特徴を用いたマルチヘッド注意機構
EdgeConv点群、動的グラフエッジ特徴(x_i, x_j - x_i)に対する MLP
RGCNConv多数の関係タイプを持つ異種グラフ関係ごとの重み行列
HGTConv異種グラフタイプごとの注意機構

すべての畳み込みレイヤーは、最低限 (x, edge_index) を受け取ります。また、多くのレイヤーはエッジ特徴用の edge_attr も受け取ります。

遅延初期化

入力チャンネル数に -1 を指定すると、PyG が次元を自動的に推定します。異種モデルで特に便利です。

conv = SAGEConv((-1, -1), 64)  # Input dims inferred on first forward pass
# Initialize lazy modules:
with torch.no_grad():
    out = model(data.x, data.edge_index)
高水準モデル API

よく使われるアーキテクチャ向けに、PyG ではすぐに使えるモデルクラスを提供しています。

from torch_geometric.nn import GraphSAGE, GCN, GAT, GIN

model = GraphSAGE(
    in_channels=dataset.num_features,
    hidden_channels=64,
    out_channels=dataset.num_classes,
    num_layers=2,
)
MessagePassing によるカスタムレイヤー

新しい GNN レイヤーを実装するには、MessagePassing をサブクラス化します。フレームワークは次のとおりです。

  1. propagate() がメッセージパッシングを統括します
  2. message() が各エッジに沿って流れる情報(関数 phi)を定義します
  3. aggregate() が各ノードのメッセージを結合します(sum/mean/max)
  4. update() が集約結果を変換します(関数 gamma)
from torch_geometric.nn import MessagePassing
from torch_geometric.utils import add_self_loops, degree

class MyConv(MessagePassing):
    def __init__(self, in_channels, out_channels):
        super().__init__(aggr='add')  # "add", "mean", or "max"
        self.lin = torch.nn.Linear(in_channels, out_channels)

    def forward(self, x, edge_index):
        # Pre-processing before message passing
        x = self.lin(x)
        # Start message passing
        return self.propagate(edge_index, x=x)

    def message(self, x_j):
        # x_j: features of source nodes for each edge [num_edges, features]
        # The _j suffix auto-indexes source nodes, _i indexes target nodes
        return x_j

_i / _j の規則:propagate() に渡すテンソルには、message() のシグネチャで _i(ターゲット/中心ノード)または _j(ソース/近傍ノード)を追加することで、自動的にインデックスを付けられます。そのため、propagate に x=... を渡すと、message() 内で x_i と x_j にアクセスできます。

GCN と EdgeConv の完全な実装例については、references/message_passing.md を参照してください。

タスク別のパターン

ノード分類
# Full-batch training on a single graph (e.g., Cora)
model.train()
for epoch in range(200):
    optimizer.zero_grad()
    out = model(data.x, data.edge_index)
    loss = F.cross_entropy(out[data.train_mask], data.y[data.train_mask])
    loss.backward()
    optimizer.step()

# Evaluation
model.eval()
pred = model(data.x, data.edge_index).argmax(dim=1)
acc = (pred[data.test_mask] == data.y[data.test_mask]).float().mean()
グラフ分類

複数のグラフには、ミニバッチ処理に DataLoader を使用し、グローバルプーリングでグラフレベルの表現を取得します。

from torch_geometric.loader import DataLoader
from torch_geometric.nn import GCNConv, global_mean_pool

loader = DataLoader(dataset, batch_size=32, shuffle=True)

class GraphClassifier(torch.nn.Module):
    def __init__(self, in_ch, hidden_ch, out_ch):
        super().__init__()
        self.conv1 = GCNConv(in_ch, hidden_ch)
        self.conv2 = GCNConv(hidden_ch, hidden_ch)
        self.lin = torch.nn.Linear(hidden_ch, out_ch)

    def forward(self, x, edge_index, batch):
        x = self.conv1(x, edge_index).relu()
        x = self.conv2(x, edge_index).relu()
        x = global_mean_pool(x, batch)  # [num_graphs_in_batch, hidden_ch]
        return self.lin(x)

# Training loop
for data in loader:
    out = model(data.x, data.edge_index, data.batch)
    loss = F.cross_entropy(out, data.y)

PyG の DataLoader は、ブロック対角隣接行列を作成して複数のグラフをバッチ化します。batch テンソルは、各ノードが属するグラフのインデックスを示します。プーリング演算(global_mean_pool、global_max_pool、global_add_pool)は、これを使用してグラフごとに集約します。

リンク予測

エッジを train/val/test に分割し、負例サンプリングを使用します。

from torch_geometric.transforms import RandomLinkSplit

transform = RandomLinkSplit(
    num_val=0.1,
    num_test=0.1,
    is_undirected=True,
    add_negative_train_samples=False,
)
train_data, val_data, test_data = transform(data)

# Encode nodes, then score edges
z = model.encode(train_data.x, train_data.edge_index)
# Positive edges
pos_score = (z[train_data.edge_label_index[0]] * z[train_data.edge_label_index[1]]).sum(dim=1)

リンク予測の完全なガイド(GAE/VGAE オートエンコーダー、完全な学習ループ、大規模グラフ向けの LinkNeighborLoader、異種リンク予測、評価指標)については、references/link_prediction.md を参照してください。

大規模グラフへのスケーリング

GPU メモリに収まらないグラフには、NeighborLoader による近傍サンプリングを使用します。

from torch_geometric.loader import NeighborLoader

train_loader = NeighborLoader(
    data,
    num_neighbors=[15, 10],     # Sample 15 neighbors in hop 1, 10 in hop 2
    batch_size=128,              # Number of seed nodes per batch
    input_nodes=data.train_mask, # Which nodes to sample from
    shuffle=True,
)

for batch in train_loader:
    batch = batch.to(device)
    out = model(batch.x, batch.edge_index)
    # Only use first batch_size nodes for loss (these are the seed nodes)
    loss = F.cross_entropy(out[:batch.batch_size], batch.y[:batch.batch_size])

NeighborLoader に関する重要なポイント:

  • num_neighbors リストの長さは、GNN の深さ(メッセージパッシング層の数)に合わせる必要があります
  • シードノードは、出力内で常に最初の batch.batch_size 個のノードになります
  • batch.n_id は、再ラベル付けされたインデックスを元のノード ID に対応付けます
  • Data と HeteroData の両方で動作します
  • リンク予測には、代わりに LinkNeighborLoader を使用します
  • 2~3 ホップを超えるサンプリングは、一般に実行不可能です(指数関数的に増大するため)

その他のスケーラビリティ向けオプションには、ClusterLoader(ClusterGCN)、GraphSAINTSampler、ShaDowKHopSampler があります。マルチ GPU 学習、DDP、PyTorch Lightning との統合、torch.compile のサポートについては、references/scaling.md を参照してください。

異種グラフ

複数のノードタイプとエッジタイプを持つグラフ(ソーシャルネットワーク、ナレッジグラフ、レコメンデーション)では、次のようにします。

from torch_geometric.data import HeteroData

data = HeteroData()

# Node features — indexed by node type string
data['user'].x = torch.randn(1000, 64)
data['movie'].x = torch.randn(500, 128)

# Edge indices — indexed by (src_type, edge_type, dst_type) triplet
data['user', 'rates', 'movie'].edge_index = torch.randint(0, 500, (2, 3000))
data['user', 'follows', 'user'].edge_index = torch.randint(0, 1000, (2, 5000))

# Access convenience dicts
data.x_dict        # {'user': tensor, 'movie': tensor}
data.edge_index_dict  # {('user','rates','movie'): tensor, ...}
data.metadata()    # ([node_types], [edge_types])
異種 GNN を構築する 3 つの方法

1. to_hetero() で自動変換 — 同種モデルを記述し、自動的に変換します。

from torch_geometric.nn import SAGEConv, to_hetero

class GNN(torch.nn.Module):
    def __init__(self, hidden_channels, out_channels):
        super().__init__()
        self.conv1 = SAGEConv((-1, -1), hidden_channels)
        self.conv2 = SAGEConv((-1, -1), out_channels)

    def forward(self, x, edge_index):
        x = self.conv1(x, edge_index).relu()
        x = self.conv2(x, edge_index)
        return x

model = GNN(64, dataset.num_classes)
model = to_hetero(model, data.metadata(), aggr='sum')

# Now accepts dicts:
out = model(data.x_dict, data.edge_index_dict)

二部グラフ入力チャネル(ソースとターゲットで異なる場合があります)には (-1, -1) を使用します。残りは遅延初期化で処理されます。

2. HeteroConv ラッパー — エッジタイプごとに異なる畳み込みを使用します。

from torch_geometric.nn import HeteroConv, GCNConv, SAGEConv, GATConv

conv = HeteroConv({
    ('paper', 'cites', 'paper'): GCNConv(-1, 64),
    ('author', 'writes', 'paper'): SAGEConv((-1, -1), 64),
    ('paper', 'rev_writes', 'author'): GATConv((-1, -1), 64, add_self_loops=False),
}, aggr='sum')

3. HGTConv のようなネイティブの異種演算子

from torch_geometric.nn import HGTConv
conv = HGTConv(hidden_channels, hidden_channels, data.metadata(), num_heads=4)

異種グラフでの重要事項:

  • 双方向のメッセージフローを実現するには、T.ToUndirected() を使用して逆向きのエッジタイプを追加します
  • 二部グラフの畳み込み層(ソースタイプと宛先タイプが異なる場合)では、add_self_loops を無効にし、代わりにスキップ接続を使用します:conv(x, edge_index) + lin(x)
  • HeteroData に対して NeighborLoader を使う場合は、input_nodes に ('node_type', mask) タプルを指定します
  • 細かな制御が必要な場合、num_neighbors はエッジタイプをキーとする辞書にできます

異種グラフでの学習ループや NeighborLoader の使用方法を含む完全な例については、references/heterogeneous.md を参照してください。

カスタムデータセット

独自のデータを PyG に読み込む方法:

  • 手軽(クラス不要):Data オブジェクトを直接作成し、リストを DataLoader に渡します
  • 再利用可能(RAM に収まる場合):InMemoryDataset をサブクラス化し、raw_file_names、processed_file_names、download()、process() をオーバーライドします
  • 大規模(ディスクベース):Dataset をサブクラス化し、さらに len() と get() をオーバーライドします
  • CSV から読み込む場合:pandas でノード/エッジテーブルを読み込み、連続したインデックスへのマッピングを作成して、Data または HeteroData にまとめます
  • NetworkX から読み込む場合:from_networkx(G) で NetworkX グラフを直接変換します
  • scipy 疎行列から読み込む場合:from_scipy_sparse_matrix(adj) で edge_index を抽出します

すべてのパターンの完全な例、エンコーダーを使った CSV の読み込み、MovieLens の手順については、references/custom_datasets.md を参照してください。

説明可能性

PyG は、GNN の予測を解釈するための torch_geometric.explain を提供します。

from torch_geometric.explain import Explainer, GNNExplainer

explainer = Explainer(
    model=model,
    algorithm=GNNExplainer(epochs=200),
    explanation_type='model',
    node_mask_type='attributes',
    edge_mask_type='object',
    model_config=dict(
        mode='multiclass_classification',
        task_level='node',
        return_type='log_probs',
    ),
)

explanation = explainer(data.x, data.edge_index, index=10)
explanation.visualize_graph()           # Important subgraph
explanation.visualize_feature_importance(top_k=10)  # Feature importance

利用可能なアルゴリズム:GNNExplainer(最適化ベース)、PGExplainer(パラメトリック、学習済み)、CaptumExplainer(Captum を介した勾配ベース)、AttentionExplainer(アテンション重み)。同種グラフと異種グラフの両方で使用できます。

すべてのアルゴリズム、異種グラフの説明、評価指標、PGExplainer の学習については、references/explainability.md を参照してください。

よくある落とし穴

  1. edge_index の形状:[2, num_edges] にする必要があります。[num_edges, 2] ではありません。必要に応じて転置してください。
  2. 活性化関数の付け忘れ:Conv レイヤーには ReLU などが含まれていないため、手動で追加してください。
  3. 異種二部グラフでの自己ループ:ソースノード型と宛先ノード型が異なる場合は、add_self_loops=True を使用しないでください。代わりにスキップ接続を使用してください。
  4. NeighborLoader によるスライス:シードノードは batch.batch_size 個の先頭ノードだけです。それに応じて予測値とラベルをスライスしてください。
  5. 無向グラフ:グラフが無向の場合は、edge_index に両方向のエッジを含めるか、T.ToUndirected() を使用してください。
  6. 遅延初期化:入力チャネル数が -1 のモデルでは、パラメーターを初期化するため、学習前に torch.no_grad() を使って 1 回フォワードパスを実行する必要があります。
  7. グラフタスクでのグローバルプーリング:ノード特徴量をグラフレベルに集約するには、手動で reshape するのではなく、global_mean_pool(x, batch) を使用してください。
  8. num_neighbors の整合性:len(num_neighbors) を GNN レイヤー数と一致させてください。レイヤー数よりホップ数が多いと計算が無駄になり、少ないとモデルの容量が活用されません。

コメント