1. 分子图模型工程导论

分子图模型工程,是把分子当作图结构来处理,并围绕“图神经网络训练—验证—部署”建立一套可复现、可度量、可迭代的工程体系。它与传统基于分子指纹(如 Morgan 指纹)加梯度提升树的流程最大的区别在于:模型不再依赖人工设计的固定特征,而是直接在原子与化学键构成的图上做消息传递,让模型自己学习局部化学环境与全局性质之间的映射关系。

一、分子图表示的核心概念

一个分子可以形式化地写成图 G = (V, E)。其中节点集合 V 对应原子,边集合 E 对应化学键。工程实现时,通常把它展开成三组张量:

  • 节点特征矩阵 X:形状 [N, d_node],N 为原子数。每一行描述一个原子,常用特征包括原子序数、元素类型 one-hot、杂化方式(sp/sp²/sp³)、形式电荷、是否在环上、手性标签、隐式氢数量等。
  • 边索引 edge_index:形状 [2, E],用两行分别存“起点原子下标”和“终点原子下标”。无向键通常写成双向边,即一条键在 edge_index 中出现两次。
  • 边特征矩阵 E_feat:形状 [E, d_edge],描述键的类型(单键/双键/三键/芳香键)、是否共轭、是否在环内、立体化学标记等。

此外还有两个常被忽略但很关键的输入:全局特征向量 u(如分子量、可旋转键数、拓扑极性表面积)和批次向量 batch(把多个小分子拼成一张大图时,标记每个原子属于哪个分子)。以阿司匹林(C₉H₈O₄)为例,若只保留重原子,N = 13,E(双向计)约为 26,节点特征维度取 32,则 X 为 [13, 32],edge_index 为 [2, 26]。这个规模说明:分子图普遍很小,但数量极大,因此工程瓶颈往往不在单图计算,而在数据吞吐与批处理效率。

参照约定:本文所有“节点/边”均指重原子及其之间的键,氢原子默认隐式处理;edge_index 采用“源→目标”方向,无向键双向登记。全文与图示保持一致。

二、训练与验证工程的整体流程

从原始 SMILES 到可交付模型,工程链路可以拆成六个环节,每个环节都有明确的输入输出与失败模式:

  1. 数据获取与清洗:从 ChEMBL、PubChem、ZINC 或内部实验记录读取 SMILES 与标签。需要处理盐离子剥离、互变异构体标准化、重复条目去重、标签单位统一(如 IC₅₀ 统一为 nM 并取 −log 转换)。
  2. 图构建与特征化:用 RDKit 解析 SMILES,生成原子/键特征,输出 PyG 的 Data 对象或 DGL 图。此步要固定特征字典顺序,否则训练与推理会错位。
  3. 数据划分:随机划分在分子性质预测中容易高估性能,因为相似骨架会同时落入训练与测试集。更严谨的做法是 Scaffold 划分(按 Murcko 骨架分组)或时间划分。
  4. 模型训练:选择 GIN、GCN、MPNN 或 AttentiveFP 等消息传递骨架,配置层数、隐藏维度、readout 方式、学习率与早停策略。
  5. 验证与评估:回归任务看 RMSE、MAE、R²;分类任务看 ROC-AUC、PR-AUC、F1。同时必须报告划分方式与随机种子,否则指标不可比。
  6. 导出与推理:固化特征化代码与权重,保证线上推理与训练时的图构建逻辑完全一致。

下面这张图把上述链路与每步的关键产物、判断依据放在一起,便于对照理解各环节之间的依赖关系。

分子图模型工程链路:输入 → 图构建 → 划分 → 训练 → 验证 → 推理 ① 数据获取清洗 SMILES + 标签 去盐 / 去重 / 单位统一 ② 图构建特征化 X / edge_index / E_feat RDKit → PyG Data ③ 数据划分 随机 / Scaffold / 时间 train / val / test ④ 模型训练 GIN / GCN / MPNN 消息传递 + readout ⑤ 验证与评估 RMSE / MAE / R² ROC-AUC / PR-AUC 指标不达标 → 调参 / 换骨架 / 重划分 各环节关键产物与判断依据 ① 清洗 产物:标准化 SMILES 表 判断:重复率、无效 SMILES 比例 ② 图构建 产物:Data 对象 / 特征字典 判断:原子数分布、特征维度一致 ③ 划分 产物:train/val/test 索引 判断:骨架重叠率、标签分布偏移 ④ 训练 产物:权重 + 训练日志 判断:验证损失是否早停 ⑤ 验证 产物:指标报告 + 预测散点 判断:测试集指标是否稳定、是否过拟合 ⑥ 推理部署 产物:固化特征化 + 权重 判断:离线/在线图构建一致性

三、典型应用场景

分子图模型并非只用于“预测一个数”。按任务形态大致可分四类:

  • 分子性质回归:水溶性(logS)、脂水分配系数(logP)、毒性(LD₅₀)、ADMET 端点。输出是连续值,损失常用 MSE 或 Huber。
  • 活性分类:某靶点是否活性、是否 hERG 阻断。输出是概率,损失用 BCE,评估看 ROC-AUC。
  • 分子生成与优化:以图生成为核心,结合强化学习或条件 VAE,在满足约束下优化性质。
  • 反应预测:把反应物与产物建模为图到图的映射,用于逆合成与产率预测。

不同场景对工程的要求差异明显:回归任务更关注标签噪声与划分方式,分类任务更关注类别不平衡与阈值选择,生成任务则更关注采样效率与化学有效性。

四、工具链概览

当前主流工具链可以按职责分层理解:

层级常用工具职责
化学信息学RDKit、Open BabelSMILES 解析、标准化、特征生成
图深度学习框架PyTorch Geometric、DGL图数据结构、消息传递层、批处理
训练与实验管理PyTorch Lightning、Weights & Biases训练循环、日志、超参记录
分子专用库DeepChem、Chemprop预置模型、数据加载、基准评测
部署ONNX Runtime、TorchServe模型导出与在线推理

一个最小可运行示例(PyG 风格)如下,用于说明图构建与训练入口的衔接方式:

from rdkit import Chem
import torch
from torch_geometric.data import Data

def mol_to_graph(smiles):
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        return None
    atom_feats, edge_index, edge_feats = [], [], []
    for atom in mol.GetAtoms():
        atom_feats.append([
            atom.GetAtomicNum(),
            atom.GetDegree(),
            int(atom.GetIsAromatic()),
            atom.GetFormalCharge(),
        ])
    for bond in mol.GetBonds():
        i, j = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()
        edge_index += [[i, j], [j, i]]
        bt = bond.GetBondTypeAsDouble()
        edge_feats += [[bt], [bt]]
    x = torch.tensor(atom_feats, dtype=torch.float)
    ei = torch.tensor(edge_index, dtype=torch.long).t().contiguous()
    ea = torch.tensor(edge_feats, dtype=torch.float)
    return Data(x=x, edge_index=ei, edge_attr=ea)

g = mol_to_graph("CC(=O)Oc1ccccc1C(=O)O")  # 阿司匹林
print(g)
# Data(x=[13, 4], edge_index=[2, 26], edge_attr=[26, 1])

这段代码的关键点在于:特征字典顺序必须固定,且无向键要双向登记,否则消息传递时会出现信息不对称。打印出的 x=[13, 4] 与 edge_index=[2, 26] 正是前文阿司匹林示例的规模,可作为图构建是否正确的第一道自检。

常见误区:把随机划分得到的测试集指标当作真实泛化能力。若训练集与测试集共享 Murcko 骨架,ROC-AUC 可能虚高 0.1 以上。工程上应至少同时报告随机划分与 Scaffold 划分两组结果。