一、图神经网络与QSAR概述:背景、意义与课程目标

1.1 我们为什么需要这个课程?

先聊点实际的。各位搞药物发现的朋友,或者做计算化学的同行,应该都有这种体会:

  • 数据越来越多:高通量筛选、公共数据库每天产出海量分子活性数据。
  • 传统方法有点顶不住了:指纹+随机森林虽然经典,但分子的空间结构、原子间长程依赖关系,这些信息基本被丢掉了。
  • 图神经网络(GNN)恰好能补这个短板:分子天生就是图结构,原子是节点,化学键是边。GNN 直接在图上做消息传递,说白了就是让每个原子“看”到邻居甚至更远的原子,然后学出整个分子的表征。

我在早期的 QSAR 项目里,试过用 ECFP 加 XGBoost,模型 AUC 做到 0.85 左右,但一到骨架跃迁的测试集就直接掉到 0.72。后来换了个简陋的 GCN,同样的数据集,AUC 涨到 0.91。那一刻我意识到,这条路值得深耕。

核心矛盾:传统分子描述符是“人工设计的固定视角”,GNN 是“数据驱动的动态视角”。我们需要把这两种思路融合起来。

1.2 这门课到底讲什么?

课程内容可以分为三大块,我画了个结构图,你大概看一眼就明白了:

GNN QSAR 融合实践知识体系 GNN QSAR 融合 理论基础与图构建 模型架构与训练 实战部署与优化 分子图构建策略 节点与边特征工程 GCN / GAT / MPNN 回归与分类任务调优 可解释性分析 工业级部署方案 覆盖:图构建 → 模型选型 → 训练调参 → 可解释性 → 生产部署

1.3 课程目标:你能带走什么?

跟着我把这30章走完,我希望你至少能做到三件事:

  1. 能自己搭一套 GNN QSAR 流水线:从分子データ读入、图构建、模型训练到结果分析,每一步都讲透。
  2. 能避开那些常见的坑:比如图构建时氢原子怎么处理?节点特征用什么?我曾在一个项目中没考虑氢键供体,结果模型在激酶数据集上直接翻车,那个教训挺深刻的。
  3. 能理解模型在“想”什么:不只是调包跑个 AUC,而是能用注意力权重、梯度解释去分析模型学到了哪些化学知识。

打个比方:传统的 QSAR 像用固定焦距的相机拍照,角度有限。GNN 像无人机环绕拍摄,还能自动补光。本课就是教你怎么操作这架无人机,以及怎么把拍出来的数据变成可用的信息。

1.4 背景:QSAR 的困境与 GNN 的破局

先快速回顾一下。QSAR(定量构效关系)核心目标是:用分子结构预测活性。

传统做法长这样:

  • 计算分子描述符(MolLogP、TPSA、分子量...)
  • 生成指纹(ECFP、MACCS...)
  • 用 RF、SVM、XGBoost 做回归或分类

问题在哪?我总结了三个:

困境 表现 后果
信息丢失 指纹是哈希得到的位向量,丢失了原子连接的精确拓扑 对类似但不等价的分子区分度差
长程依赖弱 传统描述符很难捕捉相隔较远的官能团协同作用 对弱相互作用、协同效应预测不准
可迁移性差 在训练集分布外的分子上,模型基本失效 仅适用已知骨架,通用性不足

GNN 怎么破的? 说白了,它把分子当成一张图,每个原子通过消息传递不断聚合邻居信息。经过若干层后,每个节点的表征里包含了局部甚至全局的结构信息。你想想看,这比固定指纹灵活多了。

我习惯把这种能力叫作“端到端的结构编码”。不需要手工设计特征,图结构本身就是输入。这也是为什么 GNN 在分子性质预测上表现突出的原因。

一句话总结:GNN 让 QSAR 从“离散特征”时代跨入了“连续结构学习”时代。

1.5 课程面向谁?需要什么基础?

我不要求你已经是图神经网络专家,但希望你有这些基础:

  • Python 熟练:能写类、会调包、懂 numpy/pandas
  • 机器学习基础:知道什么是过拟合、交叉验证、超参数
  • 基本化学知识:看得懂 SMILES,知道原子、键、官能团

如果上面三条有短板,建议先补齐再上车。尤其是 SMILES 解析,我在代码里会经常用到 RDKit,如果你没接触过,最好提前跑几个例子熟悉一下。

1.6 课程资源与工具链

本课程所有核心代码都基于以下工具:

  • PyTorch + PyTorch Geometric:GNN 主力框架
  • RDKit:分子处理与图构建
  • DGL:备选图框架,部分章节我会对比两种框架的差异
  • Weights & Biases / MLflow:实验跟踪

一个典型的分子图构建流程,大概是这样:

from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors
import torch
from torch_geometric.data import Data

def mol_to_graph(smiles):
    mol = Chem.MolFromSmiles(smiles)
    if mol is None:
        return None
    
    # 节点特征:原子序数 + 手性 + 度 + 形式电荷 + 氢键供体/受体
    atom_features = []
    for atom in mol.GetAtoms():
        feat = [
            atom.GetAtomicNum(),
            float(atom.GetChiralTag()),
            float(atom.GetDegree()),
            float(atom.GetFormalCharge()),
            float(atom.GetNumExplicitHs()),
            float(atom.GetIsAromatic()),
        ]
        atom_features.append(feat)
    
    # 边索引与边特征
    edge_index = []
    edge_attr = []
    for bond in mol.GetBonds():
        i = bond.GetBeginAtomIdx()
        j = bond.GetEndAtomIdx()
        edge_index.append([i, j])
        edge_index.append([j, i])  # 无向图
        bond_type = bond.GetBondTypeAsDouble()
        edge_attr.append([bond_type, float(bond.GetIsConjugated())])
        edge_attr.append([bond_type, float(bond.GetIsConjugated())])
    
    x = torch.tensor(atom_features, dtype=torch.float)
    edge_index = torch.tensor(edge_index, dtype=torch.long).t().contiguous()
    edge_attr = torch.tensor(edge_attr, dtype=torch.float)
    
    return Data(x=x, edge_index=edge_index, edge_attr=edge_attr)

# 示例
data = mol_to_graph("CC(=O)Oc1ccccc1C(=O)O")  # 阿司匹林
print(data)

这段代码是我早期项目里的简化版。实际生产环境中,节点特征通常要包含更多信息,比如 Gasteiger 电荷、部分电荷、是否在环中等。后面课程我会详细展开特征工程的最佳实践。

1.7 学习建议:怎么用好这份指南?

嗯,这里说点我个人习惯的学习方法:

  • 别只看,要动手跑:每一章的代码我都提供了完整的 notebook,务必执行一遍。
  • 改参数、换数据:把里面的分子数据集换成你自己的,看看效果差异。这才是真学透。
  • 记笔记:尤其是踩过的坑,比如“这个分子 SMILES 解析出来是 None”、“这个层的传播维度对不上”。记录下来,以后就是你的避坑手册。

警惕“复制粘贴依赖症”:我看到太多人把代码复制过去跑通了就觉得自己会了。模型不收敛的时候,需要你能看懂梯度为什么消失,而不是换个学习率碰运气。这也是我设计大量可视化与解释性内容的原因。

1.8 本章小结

这一章是热身。你知道了:

  • 为什么传统 QSAR 到了瓶颈,GNN 能带来新突破
  • 这门课的三块知识体系:理论基础、模型实战、部署优化
  • 你需要哪些前置技能、用哪些工具
  • 一个最简的分子图构建示例