分子图数据表示:一场从化学结构到机器学习的翻译
做分子图深度学习,说白了就是让计算机看懂化学结构。但计算机不懂“苯环”“羰基”这些概念,它只认得数字。所以我们需要一套严格的翻译规则——把原子、化学键乃至整个分子的信息,编码成计算机能处理的特征向量。
我记得自己第一次做分子属性预测时,以为直接把SMILES扔进模型就行了——嗯,结果当然是一塌糊涂。后来才明白,特征表示才是图神经网络的灵魂。搞不定特征,再好的模型架构也是白搭。
这一节我们就来拆解分子图的三层特征:原子特征(节点)、键特征(边)和全局特征(图级别)。这是构建分子图的基石,也是后续GNN模型最底层的输入。
原子特征:给每个节点贴上标签
原子是分子图的节点。每个节点需要携带足够的信息,来描述“这个原子是什么、处于什么状态”。
我个人习惯把原子特征分成三类:
- 身份特征:原子类型、周期表中的位置
- 电子特征:杂化方式、形式电荷、孤对电子数
- 拓扑特征:连接原子数(度)、是否在环上
实际项目中,最常用的原子特征组合是这样的:
| 特征名称 | 描述 | 常见编码方式 | 维度(示例) |
|---|---|---|---|
| 原子类型 | C, N, O, S, F, Cl, Br, I, P... 前9-10种常见元素 | One-hot | 9 |
| 度(Degree) | 该原子连接的化学键数量(氢键除外) | One-hot(0~5或更多) | 6 |
| 形式电荷 | 原子在分子中的电荷状态,常见 -1, 0, 1 | One-hot | 3 |
| 杂化类型 | sp, sp², sp³ | One-hot | 3 |
| 氢原子数目 | 连接该原子的氢原子个数 | One-hot(0~4或直接数值) | 5 |
| 芳香性 | 是否在芳香环中 | 布尔值(0或1) | 1 |
| 在环中 | 是否在任意环结构中 | 布尔值 | 1 |
来看一个具体的编码例子。假设我们有一个碳原子(C),sp²杂化,形式电荷为0,连接3个原子(度=3),带1个氢,不在芳香环中。它的特征向量大概长这样:
原子类型: [1, 0, 0, 0, 0, 0, 0, 0, 0] # C是第一个
度: [0, 0, 0, 1, 0, 0] # 度=3 -> 第4位为1
形式电荷: [0, 1, 0] # 0 -> 中间
杂化: [0, 1, 0] # sp² -> 中间
氢原子数: [0, 1, 0, 0, 0] # 1个H -> 第2位
芳香性: [0] # false
在环中: [0] # false
完整拼接:22维浮点向量
避坑指南:我曾经在项目中把所有原子特征都用了One-hot编码,结果模型训练特别慢。后来发现像“氢原子数目”这样的特征,直接用归一化的数值输入效果反而更好。实验前建议先做个小测试——One-hot vs 数值编码,哪个对下游任务更友好。
键特征:边的信息同样重要
光有节点还不够。原子之间怎么连接的?单键还是双键?顺反异构怎么区分?这些信息要编码在边上。
键特征往往被初学者忽略,但实战中它经常是模型性能的关键。我做过一个手性分子识别项目,一开始只用键序(单、双、三),模型死活分不清对映体。加上立体化学特征后,准确率直接从70%跳到了92%。
最常见的键特征:
- 键序:单键=1,双键=2,三键=3,芳香键=1.5
- 共轭性:是否处于共轭体系
- 环内键:是否在环中
- 立体化学:顺式(Z)、反式(E)、无指定
- 键长(可选):通常归一化后使用
用JSON来表示分子图数据,是最常见的存储格式:
{
"atoms": [
{"element": "C", "hybridization": "sp2", "charge": 0, "degree": 3, "h_count": 1, "in_ring": false},
{"element": "O", "hybridization": "sp2", "charge": 0, "degree": 1, "h_count": 0, "in_ring": false}
],
"bonds": [
{"src": 0, "dst": 1, "order": 2, "conjugated": false, "in_ring": false, "stereo": "none"},
{"src": 0, "dst": 2, "order": 1, "conjugated": false, "in_ring": false, "stereo": "none"}
]
}
注意:立体化学的编码比较微妙。有些任务用3位One-hot:[Z, E, 无指定];有些则直接用一个取值范围[-1, 0, 1]的数值。我个人倾向于前者,因为布尔型的特征在模型里学习起来更直接。不过遇到手性分子多的数据集,我会额外加一个“是否存在手性中心”的全局特征。
曾经踩过的坑:在编码芳香键时,我用了默认键序1.5。结果模型在预测某些环状分子的性质时误差特别大。原因是RDKit默认的芳香键是1.5,但有些模型库(比如DGL的SchNet)预期输入是整数键序。最后我不得不加了一层预处理:遇到芳香键时拆分成单键+双键交替。这种细节差异,换框架就得查一遍,不是某一家的问题。
全局特征:整个分子的肖像
有些性质,只看原子和键是看不出来的。比如分子量、辛醇-水分配系数(logP)、拓扑极性表面积(TPSA)。这些是全局特征,附加到整个图上,不是某个节点或边上。
常见的全局特征:
| 特征 | 意义 | 来源 |
|---|---|---|
| 分子量 | 所有原子质量之和 | 计算 |
| LogP | 脂溶性,药物化学经典指标 | RDKit计算或实验值 |
| TPSA | 极性表面积,与吸收相关 | RDKit |
| 可旋转键数 | 分子柔性指标 | RDKit |
| 氢键供体/受体数 | 影响溶解性和靶点结合 | RDKit |
| 芳香环数 | 芳香体系复杂度 | 计算 |
全局特征怎么用?常见做法有两种:
- 嵌入到图表示中:作为额外的输入向量,和图神经网络输出的节点表示拼接
- 作为辅助监督:多任务学习中,让模型同时预测分子属性和全局特征,提升表征质量
我个人更推荐第二种。不过要注意:全局特征和目标任务不能太相关,否则模型会偷懒,只靠全局特征猜测,忽略图结构信息。比如预测溶解度时,把LogP作为额外输入,模型可能就不学原子间的交互模式了——反而导致泛化能力下降。
特征组合策略:不是越多越好
你可能会想:那就把所有能算的特征都怼进去呗。可别这么干。特征维度爆炸会带来几个问题:
- 模型参数量激增,小数据集上容易过拟合
- 冗余特征混淆学习信号,降低泛化能力
- 训练速度下降,每个batch传递的数据量变大
那我怎么选?先说一个我自己总结的经验法则:
- 根据任务类型判断:分类任务优先原子类型+键序+全局LogP;回归任务注意特征归一化
- 先跑一个基线模型(只用最基本特征),再看加特征后性能是否提升
- 如果特征加了一堆但性能没涨,就是白加——果断砍掉
另外,不同框架对特征顺序有默认要求。比如PyTorch Geometric(PyG)的GINConv,希望节点特征在第一维;DGL的GATConv则默认特征在最后一维。写代码前一定要读文档,不然维度错位排查起来极其痛苦——我就在这上边浪费过整整两天。