本文へスキップ
コミュニティ

PyTorch Geometric による QM9 双極子 GNN の学習用ベースライン

QM9 分子グラフから、小規模な CPU GCN を訓練データだけの標準化で学習し、検証集合で選択したモデルのテスト MAE を Debye で報告します。

難易度: 中級 費用: 無料 プライバシー: ローカル ~60 分
セットアップ開始

できるようになること

  • 追跡可能な計算候補の成果物を作成し、文書化した確認基準を点検します。

構築するもの

公式文書に基づくレビュー原稿です。本バッチでは実行しておらず、インストール、推論、最適化、実験結果は未検証です。

対象と範囲

分子グラフ回帰の学習者向けです。ターゲット0は QM9 の双極子モーメントで、単位は Debye(D)です。2層 GCN、原子特徴、グラフ平均プーリングを使い、結合種の辺特徴と3D座標は使用しません。最先端モデルや幾何情報を扱う推定器ではありません。

データと利用条件

元の Figshare データ記事1057646には CC0 が明記されています。論文の条件や PyG コードの MIT とは別に確認しています。元 DOI、記事の版、メタデータを保存します。固定版 PyG QM9 は RDKit がある場合 DeepChem の gdb9.zip と Figshare ファイル3195404の未同定分子リストを使い、そのリストの分子を除外して19ターゲットを提供します。RDKit と空の保存先を必須にし、別の処理済みアーカイブを無言で再利用しません。実際の件数と取得・処理ファイルの SHA-256 を保存します。本バッチではデータ取得や元 XYZ アーカイブとのバイト一致を確認していません。追加フィルターを無言で適用しません。

学習条件と成果物

シード37の順列から8000/1000/1000の排他的な学習・検証・テスト集合を作ります。未使用サンプルは意図的で、完全なベンチマークではありません。ラベルの平均と標準偏差は学習集合だけから求めます。CPU で Adam と標準化 MSE を10エポック実行し、検証 MAE で重みを選び、テスト MAE を D 単位で最後に一度評価します。上流の NNConv 例は分割前に全ラベルを標準化するため、そのまま複製しません。期待する成果物は分割インデックス、重み、データハッシュ、各エポックの記録、results.json です。実行前のスコアは主張しません。

限界と費用

同一分布内のランダム分割は骨格や外部への汎化を示しません。テスト集合で調整しません。学習集合だけの定数ベースラインと比較し、骨格・OOD 評価は別に行います。上流は Python >=3.10 を要求し、本稿は Python 3.11/Linux/CPU を選びますが、検証済みの依存固定情報はありません。導入、取得、メモリー、学習には資源が必要です。取得時は公開ホストに接続し、分子データと学習はローカルです。MIT 表示と原データへの引用を維持します。

公式資料

分子グラフデータと GCN 学習

PyTorch Geometric

構成リソース

PyTorch Geometric

分子グラフデータと GCN 学習 · source 79d33965a40b7fa83616a9f598a0f8619f25d939; Python 3.11; RDKit raw route

公式文書に基づくレビュー原稿です。本バッチでは実行しておらず、インストール、推論、最適化、実験結果は未検証です。

コードは公開されていますが、計算・保存費用と外部サービスの条件は別途確認してください。

リソースを見る

互換性

クライアントOSアーキテクチャバージョン要件
Python Linux指定なし>= 3.11

セットアップとテスト

1. 双極子タスクと利用条件を定義

Linux

D 単位のターゲット0を使い、CC0 データのメタデータと固定版 QM9 ソースを保存し、取得と除外ルールを確認します。

公式出典

期待される結果

ターゲット・由来・処理方針が記録されています。

2. 版を記録した CPU 環境を準備

Linux

Python 3.11、互換 CPU PyTorch、NumPy、RDKit を準備します。公式導入手順に従って確認した commit を導入し、依存情報を保存します。検証済みロックは主張しません。

git clone https://github.com/pyg-team/pytorch_geometric.git
git -C pytorch_geometric checkout 79d33965a40b7fa83616a9f598a0f8619f25d939
python -m pip install ./pytorch_geometric
公式出典

期待される結果

環境を記録し、QM9/GCNConv/DataLoader の import を確認します。

3. 学習集合だけの前処理と分割を確認

Linux

付属の作成例を qm9_gcn_baseline.py として保存します。RDKit と空のデータ先を必須にし、ハッシュとシード・分割を保存して、平均と標準偏差を学習集合だけから算出します。公式 API の組合せであり、上流の再現結果ではありません。

"""Review-only authored baseline using documented PyG APIs; never run in this batch."""
import argparse
import copy
import hashlib
import json
from pathlib import Path
import random
import numpy as np
import rdkit  # Require the inspected raw-processing route, not the no-RDKit archive.
import torch
from torch import nn
import torch_geometric
from torch_geometric.datasets import QM9
from torch_geometric.loader import DataLoader
from torch_geometric.nn import GCNConv, global_mean_pool

p = argparse.ArgumentParser()
p.add_argument('data_root')
p.add_argument('output')
a = p.parse_args()
out = Path(a.output)
out.mkdir(parents=True, exist_ok=False)
root = Path(a.data_root)
if root.exists() and any(root.iterdir()):
    raise ValueError('Use a fresh data root to prevent an undocumented processed cache')
seed = 37
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
torch.use_deterministic_algorithms(True)
torch.set_num_threads(1)
dataset = QM9(str(root))
# The inspected raw route removes uncharacterized molecules; record actual count.
if len(dataset) < 10000:
    raise ValueError('Unexpectedly small QM9 dataset')
ids = torch.randperm(len(dataset), generator=torch.Generator().manual_seed(seed))
# A deliberately small teaching run, not a literature benchmark split.
train_ids, val_ids, test_ids = ids[:8000], ids[8000:9000], ids[9000:10000]
assert not (set(train_ids.tolist()) & set(val_ids.tolist()))
assert not (set(train_ids.tolist()) & set(test_ids.tolist()))
assert not (set(val_ids.tolist()) & set(test_ids.tolist()))
targets = torch.cat([dataset[int(i)].y[:, 0] for i in train_ids])
mu, sigma = targets.mean(), targets.std(unbiased=False)
if not torch.isfinite(targets).all() or not torch.isfinite(sigma) or sigma <= 0:
    raise ValueError('Invalid training targets')
loaders = [DataLoader(dataset[ix], batch_size=64, shuffle=(j == 0),
    num_workers=0, generator=torch.Generator().manual_seed(seed))
    for j, ix in enumerate([train_ids, val_ids, test_ids])]

class Baseline(nn.Module):
    def __init__(self):
        super().__init__()
        self.c1 = GCNConv(dataset.num_node_features, 64)
        self.c2 = GCNConv(64, 64)
        self.head = nn.Linear(64, 1)

    def forward(self, batch):
        x = self.c1(batch.x.float(), batch.edge_index).relu()
        x = self.c2(x, batch.edge_index).relu()
        return self.head(global_mean_pool(x, batch.batch)).flatten()

model = Baseline()  # CPU only; no geometry or edge_attr features in this baseline.
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

@torch.no_grad()
def mae(loader):
    model.eval()
    total, count = 0.0, 0
    for batch in loader:
        predicted = model(batch) * sigma + mu
        errors = (predicted - batch.y[:, 0]).abs()
        if not torch.isfinite(errors).all():
            raise ValueError('Nonfinite evaluation')
        total += errors.sum().item()
        count += batch.num_graphs
    return total / count  # Debye; no normalization statistics fitted to this split.

history, best, weights = [], float('inf'), None
for epoch in range(1, 11):
    model.train()
    loss_sum, n = 0.0, 0
    for batch in loaders[0]:
        optimizer.zero_grad()
        loss = nn.functional.mse_loss(model(batch), (batch.y[:, 0] - mu) / sigma)
        if not torch.isfinite(loss):
            raise ValueError('Nonfinite loss')
        loss.backward()
        optimizer.step()
        loss_sum += loss.item() * batch.num_graphs
        n += batch.num_graphs
    val = mae(loaders[1])
    history.append({'epoch': epoch, 'train_normalized_mse': loss_sum / n, 'val_mae_D': val})
    if val < best:
        best, weights = val, copy.deepcopy(model.state_dict())
model.load_state_dict(weights)
test_mae = mae(loaders[2])  # Test once after validation-only selection.
torch.save(weights, out / 'best_state_dict.pt')
np.savez(out / 'splits.npz', train=train_ids.numpy(), validation=val_ids.numpy(), test=test_ids.numpy())
files = {str(f.relative_to(root)): hashlib.sha256(f.read_bytes()).hexdigest()
    for f in root.rglob('*') if f.is_file()}
report = {'target': 0, 'property': 'dipole moment', 'unit': 'D', 'seed': seed,
    'dataset_count': len(dataset), 'split_sizes': [8000, 1000, 1000],
    'train_mean_D': mu.item(), 'train_std_D': sigma.item(), 'epochs': 10,
    'validation_mae_D': best, 'test_mae_D': test_mae, 'history': history,
    'versions': {'torch': torch.__version__, 'pyg': torch_geometric.__version__,
        'rdkit': rdkit.__version__, 'numpy': np.__version__},
    'device': 'cpu', 'dataset_file_sha256': files,
    'scope': 'Teaching baseline; small random split, no bond types or coordinates; no transfer guarantee.'}
(out / 'results.json').write_text(json.dumps(report, indent=2) + '\n')
公式出典

期待される結果

排他的な学習用分割と学習集合だけの標準化が明示されています。

4. テストで選択せず学習と評価

Linux

記録した環境と新しいデータ・出力先で実行します。グラフ形状、有限な特徴とラベル、データ一覧を確認し、検証集合で選択して最終テスト MAE を D で報告します。

python qm9_gcn_baseline.py qm9_fresh qm9_review
公式出典

期待される結果

splits.npz、best_state_dict.pt、ログ・単位付き指標・ハッシュを含む results.json を期待します。本バッチの実測指標はありません。

トラブルシューティング

  • RDKit 不足や旧キャッシュ:必要な環境と空の保存先を準備します。
  • 非有限ラベル・損失:停止してデータと単位を確認します。
  • 件数異常:除外リストとハッシュを確認します。
  • MAE 不良:学習平均の基準と分割を確認し、テストで調整しません。
まだ動作しません

代替案

公式 NNConv は結合・幾何情報を扱う別の方向です。全データの標準化を修正してから応用し、この GCN と品質が同等とは主張しません。