torch-geometric について
PyTorchでグラフニューラルネットワークを構築する開発者向けのガイドです。グラフデータ構造、GNNレイヤー、学習ツールに加え、異種グラフ、ミニバッチ処理、近傍サンプリング、予測の解釈方法を扱います。
主な機能
- 01DataとHeteroDataのグラフ構造
- 02GNNレイヤーとメッセージパッシング
- 03グラフデータセットと変換
- 04ミニバッチ処理と近傍サンプリング
- 05異種グラフのモデリングと説明
活用シーン
- 01ノード分類・グラフ分類モデルの学習
- 02近傍サンプリングによる大規模グラフ学習
- 03複数のノード型・エッジ型を持つモデルの構築
- 04GNNの予測結果の解釈
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ crazymsn/ academic-skills --skill torch-geometricインストール後、Claude Code で/torch-geometricを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/custom_datasets.mdexplainability.mdheterogeneous.mdlink_prediction.mdmessage_passing.mdscaling.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 をサブクラス化します。フレームワークは次のとおりです。
propagate()がメッセージパッシングを統括しますmessage()が各エッジに沿って流れる情報(関数 phi)を定義しますaggregate()が各ノードのメッセージを結合します(sum/mean/max)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 を参照してください。
よくある落とし穴
- edge_index の形状:
[2, num_edges]にする必要があります。[num_edges, 2]ではありません。必要に応じて転置してください。 - 活性化関数の付け忘れ:Conv レイヤーには ReLU などが含まれていないため、手動で追加してください。
- 異種二部グラフでの自己ループ:ソースノード型と宛先ノード型が異なる場合は、
add_self_loops=Trueを使用しないでください。代わりにスキップ接続を使用してください。 - NeighborLoader によるスライス:シードノードは
batch.batch_size個の先頭ノードだけです。それに応じて予測値とラベルをスライスしてください。 - 無向グラフ:グラフが無向の場合は、
edge_indexに両方向のエッジを含めるか、T.ToUndirected()を使用してください。 - 遅延初期化:入力チャネル数が
-1のモデルでは、パラメーターを初期化するため、学習前にtorch.no_grad()を使って 1 回フォワードパスを実行する必要があります。 - グラフタスクでのグローバルプーリング:ノード特徴量をグラフレベルに集約するには、手動で reshape するのではなく、
global_mean_pool(x, batch)を使用してください。 - num_neighbors の整合性:
len(num_neighbors)を GNN レイヤー数と一致させてください。レイヤー数よりホップ数が多いと計算が無駄になり、少ないとモデルの容量が活用されません。