药物数据表示:分子图构建、原子特征、键特征、全局特征
上节课我们拿到了 SMILES。但 GCN 不认识字符串。
它吃的是图——节点、边、特征。
说白了,就是把化学结构翻译成机器能看懂的数字表格。
我个人做这个转换,90% 的情况都用 RDKit。它稳定、文档全,踩过的坑网上都有答案。
在正式开始之前,我们先明确一下图的概念。
2.1 分子图构建
分子图里,节点是原子,边是化学键。
但这里有一个关键点:一个分子是一个全连通图吗?
不是。只有通过化学键连接的原子才有边。
避坑指南:
我曾经在预处理一个包含金属配合物的数据集时,RDKit 默认的 Sanitize 过程会把某些配位键识别为无效键。
结果分子图直接少了一大半节点。所以记得在构建前加一句
SanitizeMol,或者手动处理金属的价键规则。
嗯,这里要注意细节。构建流程其实很简单:
- 读取 SMILES →
Chem.MolFromSmiles(smiles) - 可选:标准化 →
rdkit.Chem.MolStandardize - 遍历原子和键,提取特征
- 组装成图数据结构(
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 | 元素类型 | 12 | H, C, O, N, F, P, S, Cl, Br, I, B, Others |
| Degree | 连接度 | 6 | 0到5 |
| FormalCharge | 形式电荷 | 1 | -1到1 |
| NumH | 氢原子数 | 5 | 0到4 |
| IsAromatic | 芳香性 | 1 | 0或1 |
| Hybridization | 杂化方式 | 4 | SP, SP2, SP3, Others |
2.3 键特征(边特征)
有节点还要有边。边特征描述的是“原子之间怎么连接的”。
- 键类型:单键、双键、三键、芳香键 → 独热编码
- 共轭性:IsConjugated → 0/1
- 是否在环内:IsInRing → 0/1
- 立体结构:BondStereo → 独热编码(None, Z, E, any等)
注意邻接矩阵的构建:
很多新手直接把邻接矩阵填成0/1。这不够。
我建议把边特征拼成一个边特征矩阵,然后和邻接矩阵一起输入到GCN层里。
当然,如果你的GCN模型设计比较简单,只用邻接矩阵存拓扑结构也行,但边特征可以用来初始化消息传递的权重。
我建议把边特征拼成一个边特征矩阵,然后和邻接矩阵一起输入到GCN层里。
当然,如果你的GCN模型设计比较简单,只用邻接矩阵存拓扑结构也行,但边特征可以用来初始化消息传递的权重。
2.4 全局特征
有些性质是分子整体的,单个原子看不出来。比如分子量、LogP、电荷数。
这些特征怎么用?
一种方法是在做图分类时,把全局特征拼到图读出层(Readout)的结果后面。
另一种是在消息传递过程中,把全局特征作为上下文向量,增强节点特征。
我个人建议先把全局特征算出来,存好备用。等到设计模型架构时,再看怎么融合进去。
常用全局特征:
- 分子量 (MolWt)
- 脂水分配系数 (LogP)
- 拓扑极性表面积 (TPSA)
- 可旋转键数 (NumRotatableBonds)
- 氢键供体/受体计数
- 形式电荷总和
- 分子量 (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)
]