药物数据表示:分子图构建、原子特征、键特征、全局特征

上节课我们拿到了 SMILES。但 GCN 不认识字符串。

它吃的是图——节点、边、特征。

说白了,就是把化学结构翻译成机器能看懂的数字表格。

我个人做这个转换,90% 的情况都用 RDKit。它稳定、文档全,踩过的坑网上都有答案。

在正式开始之前,我们先明确一下图的概念。

2.1 分子图构建

分子图里,节点是原子,边是化学键。

但这里有一个关键点:一个分子是一个全连通图吗?

不是。只有通过化学键连接的原子才有边。

避坑指南: 我曾经在预处理一个包含金属配合物的数据集时,RDKit 默认的 Sanitize 过程会把某些配位键识别为无效键。 结果分子图直接少了一大半节点。所以记得在构建前加一句 SanitizeMol,或者手动处理金属的价键规则。 嗯,这里要注意细节。

构建流程其实很简单:

  1. 读取 SMILES → Chem.MolFromSmiles(smiles)
  2. 可选:标准化 → rdkit.Chem.MolStandardize
  3. 遍历原子和键,提取特征
  4. 组装成图数据结构(torch_geometric.data.Data)

2.2 原子特征(节点特征)

接下来是重头戏——特征工程。

GCN的节点特征,就是用来描述“这个原子是个什么样的原子”。

我建议使用 RDKit 的 GetAtom 系列方法提取。

常用的原子特征包括:

  • 原子类型(C, O, N, S等):独热编码
  • 杂化方式(SP, SP2, SP3):独热编码
  • 形式电荷(Formal Charge):数值
  • 氢原子个数(NumH):数值
  • 是否为芳香环原子(IsAromatic):0/1标志
  • 是否为手性中心(IsChiral):0/1标志

你想想看,如果一个原子是C,在SP2杂化下,又在芳香环里,这个特征组合起来,就能很精确地定位它在分子里的角色。

特征类别描述维度备注
AtomType元素类型12H, C, O, N, F, P, S, Cl, Br, I, B, Others
Degree连接度60到5
FormalCharge形式电荷1-1到1
NumH氢原子数50到4
IsAromatic芳香性10或1
Hybridization杂化方式4SP, SP2, SP3, Others

2.3 键特征(边特征)

有节点还要有边。边特征描述的是“原子之间怎么连接的”。

  • 键类型:单键、双键、三键、芳香键 → 独热编码
  • 共轭性:IsConjugated → 0/1
  • 是否在环内:IsInRing → 0/1
  • 立体结构:BondStereo → 独热编码(None, Z, E, any等)
注意邻接矩阵的构建: 很多新手直接把邻接矩阵填成0/1。这不够。
我建议把边特征拼成一个边特征矩阵,然后和邻接矩阵一起输入到GCN层里。
当然,如果你的GCN模型设计比较简单,只用邻接矩阵存拓扑结构也行,但边特征可以用来初始化消息传递的权重。

2.4 全局特征

有些性质是分子整体的,单个原子看不出来。比如分子量、LogP、电荷数。

这些特征怎么用?

一种方法是在做图分类时,把全局特征拼到图读出层(Readout)的结果后面。

另一种是在消息传递过程中,把全局特征作为上下文向量,增强节点特征。

我个人建议先把全局特征算出来,存好备用。等到设计模型架构时,再看怎么融合进去。

常用全局特征:
- 分子量 (MolWt)
- 脂水分配系数 (LogP)
- 拓扑极性表面积 (TPSA)
- 可旋转键数 (NumRotatableBonds)
- 氢键供体/受体计数
- 形式电荷总和

2.5 整合与数据准备

好了,特征和边都定义好了。怎么放到内存里?

我一般用 to_molecular_graph 这类函数,或者自己写一个 DataSet 类。

最终我们会得到:

  • node_features: [batch_size, num_atoms, atom_feat_dim]
  • edge_index: [2, num_edges]
  • edge_attr: [num_edges, bond_feat_dim]
  • global_feat: [batch_size, global_feat_dim]
小提示:GCN 不需要显式输入邻接矩阵,edge_index 就可以用来做稀疏矩阵运算,显存占用更小。我建议在数据量大的时候,优先使用 edge_index 格式。

下面这段代码演示如何用 RDKit 提取原子和键特征:

from rdkit import Chem
from rdkit.Chem import Descriptors
import numpy as np

mol = Chem.MolFromSmiles('CCO')
# 提取原子特征
atom_features = []
for atom in mol.GetAtoms():
    feat = [
        atom.GetAtomicNum(),
        atom.GetFormalCharge(),
        int(atom.GetIsAromatic()),
        atom.GetTotalNumHs(),
        atom.GetHybridization().real
    ]
    atom_features.append(feat)
atom_features = np.array(atom_features)

# 提取边特征
edge_index = []
edge_attr = []
for bond in mol.GetBonds():
    i = bond.GetBeginAtomIdx()
    j = bond.GetEndAtomIdx()
    edge_index += [[i], [j]]
    edge_attr.append([
        bond.GetBondTypeAsDouble(),
        int(bond.GetIsConjugated()),
        int(bond.GetIsAromatic())
    ])

# 提取全局特征
global_feat = [
    Descriptors.MolWt(mol),
    Descriptors.MolLogP(mol),
    Descriptors.TPSA(mol)
]
分子图构建与特征提取流程 SMILES 分子图 节点特征 边特征 全局特征 GCN输入 特征拼成矩阵后,作为GCN的输入,开始消息传递。