3.2.1 特征化谱系:向量与图
3.1 节把 DeepChem 拆成数据、特征化、模型三层,本节钻进中间那层。特征化(featurization)只做一件事:把一个化学对象——SMILES 字符串、Mol 对象、反应式——改写成模型能读取的数字块。改写规则一旦固定,模型的世界随之固定:此后它只见数字,不见分子。第二章已经备好两种成品。描述符(2.4 节)把分子压成约两百维的理化与拓扑数字,指纹(2.5 节)把圆形子结构集合折叠成定长位向量(Rogers & Hahn, 2010)。两者共享同一条路线:整分子 → 定长向量。结构信息在特征化这一步就烧进向量,模型的全部任务只剩从向量到标签的映射。
图特征化换一条路:不压扁,把分子图原样交出——每个原子一行特征,每条键一条通路记录。结构留给模型,让图卷积(3.4 节)与消息传递(3.5 节)在训练中自己学会怎么读它(Kearnes et al., 2016; Duvenaud et al., 2015)。向量一族与图一族的分野,恰好对应传统机器学习与图神经网络的分野;表 3.2-1 把 DeepChem 里常用的特征化器沿这条线排开。
| 特征化器 | 产物 | 形状 | 典型下游 |
|---|---|---|---|
| CircularFingerprint | ECFP 位向量(2.5 节) | 定长,如 1024/2048 位 | 随机森林等传统模型(3.3 节基线) |
| RDKitDescriptors | 约 200 维理化+拓扑描述符(2.4 节) | 定长实数向量 | 传统模型的另一套基线输入 |
| MolGraphConvFeaturizer | GraphData:节点特征矩阵 + 边索引 + 边特征 | 随分子大小变 | GCN、DMPNN 等图模型(3.4–3.6 节) |
| ConvMol | 节点特征 + 邻接表(adjacency list),DeepChem 早期图格式 | 随分子大小变 | GraphConvModel |
| WeaveFeaturizer | 原子对特征(atom-pair features):任意原子对都有特征 | 约 n2 规模 | Weave 模型 |
表中最后一行值得单说一句:Weave 不只为成键的原子对造特征,而是为任意两个原子——包括相隔很远的——都造特征,把「远距关系值不值得看」也交给模型裁决;代价是特征量按原子数平方增长,大分子上内存吃紧(Kearnes et al., 2016)。选表中哪一行不是工程口味,而是先验声明——这一点 3.2.5 节正面展开。先从图一族共同的骨架讲起:字段表。
3.2.2 图特征化的字段表
图特征化器交给模型的不是一个张量,而是一个三元组:节点特征(node features)矩阵 X ∈ {0,1}n×d(n 个原子,每个 d 维)、边索引(edge index)表(m 条键各拆成两个有向条目)、边特征矩阵 E(每条有向边一行)。d 是多少、每一维是什么,完全由字段表(feature schema)决定。表 3.2-2 列出一张典型字段表:它综合了 DeepChem 图特征化器与经典图卷积实现的常用取项(Duvenaud et al., 2015; Kearnes et al., 2016),也是本节习题沿用的工作表。
| 部位 | 字段 | 编码 | 化学假设——为什么放进去 |
|---|---|---|---|
| 节点 | 元素种类 | one-hot,常见元素 +「其他」桶 | 元素几乎决定原子的化学个性:电负性、价轨道、成键偏好 |
| 节点 | 原子度(重邻居数) | one-hot,0–10 | 区分末端、链中、支化中心;与氢数合看即饱和度 |
| 节点 | 形式电荷(formal charge) | one-hot,−2…+2 | 离子状态直接改写静电、氢键与结合行为 |
| 节点 | 手性标签 | one-hot,R/S/未指定等 | 对映异构体的生物活性可差数量级 |
| 节点 | 杂化方式 | one-hot,sp/sp2/sp3/… | 几何与反应性的代理:平面还是四面体 |
| 节点 | 是否芳环 | 布尔 | π 体系参与堆积与氢键 |
| 节点 | 是否在环 | 布尔 | 环内原子构象受限 |
| 边 | 键型 | one-hot,单/双/三/芳香 | 键级决定键长、刚度与电子分布 |
| 边 | 是否共轭 | 布尔 | 电子离域的通路 |
| 边 | 是否环键 | 布尔 | 旋转受限,影响构象熵 |
逐条追问「为什么放进去」,答案都是同一个句式:该字段承载的化学,被认为足以区分与目标性质相关的原子环境。元素表只收十几种常见元素,罕见元素落入「其他」桶——这是覆盖范围的显式声明。度与氢数合起来给出饱和度:同为碳,甲基碳与羰基碳在(度、杂化)两栏就已分开。手性标签要占好几维,因为对映体在许多表示里塌缩成同一个向量,而它们的药理差异可以有数量级——沙利度胺是最沉痛的注脚。字段之间允许冗余:芳环原子必在环中,度与氢数强相关。冗余无害,学习器自行取舍;缺失有害,而且训练无法弥补(3.2.5 节)。
图 3.2-1 把整条流水线画成字段流:SMILES 先解析为分子图(第 2 章),原子与键分别流向两张表。
下面印证字段表的形态:对阿司匹林做图特征化,打印三元组的形状。
import deepchem as dc feat = dc.feat.MolGraphConvFeaturizer() # 字段表:元素/形式电荷/杂化/氢键/芳香/度/氢数 g = feat.featurize(["CC(=O)Oc1ccccc1C(=O)O"])[0] # 阿司匹林 → 图 3.2-1 那样的三元组 print(g.node_features.shape) # (13, 30):13 个原子 × 30 维字段 print(g.edge_index.shape) # (2, 26):13 条键,每条拆成两条有向边 print(g.node_features[0][:4]) # 首原子碳的元素 one-hot 前缀:类别落在正交基的一个方向
三个数字一一对回字段表:13 是原子数(矩阵行数),30 是 DeepChem 默认字段表折算的列数(思想同表 3.2-2,取项与档位由实现自定),26 是 13 条键的双向展开。同一个分子换一张字段表,形状与语义都随之改变;字段表是特征化器与模型之间的契约。
习题 3.2-1
某图特征化器的节点字段表为:元素 one-hot(16 类,含「其他」桶)、原子度 one-hot(0–10)、形式电荷 one-hot(−2…+2)、手性 one-hot(4 类)、杂化 one-hot(6 类)、是否芳环(1 维)、是否在环(1 维)。(1) 求节点特征向量长度 d。(2) 一个 8 原子、8 条键的分子,节点特征矩阵、边索引、边特征的形状各是什么(边字段表:键型 4 + 共轭 1 + 环键 1)?(3) 一个批次依次装着 8、6、5 个原子(各有 8、6、5 条键)的三个分子,写出各块偏移、拼接大图的节点数,以及拼接后三个张量的形状。
参考解答(1) d = 16+11+5+4+6+1+1 = 44 维。(2) 节点特征 (8, 44);8 条键拆成 16 条有向边,边索引 (2, 16),边特征 (16, 6)。(3) 偏移 o = (0, 8, 14):第 2 块从全局 8 号起,第 3 块从 14 号起。大图共 8+6+5 = 19 个节点,节点特征 (19, 44);键共 19 条、38 条有向边,边索引 (2, 38),边特征 (38, 6)。列数在整个批次不变——字段表对每个分子一视同仁,这正是「批次 = 大图」可行的前提。
3.2.3 one-hot:维度换正确性
表 3.2-2 里反复出现 one-hot,值得把它的数学语义讲透。元素、键型、杂化都是类别变量(categorical variable):取值之间没有大小,也没有远近。「碳」不比「氮」小,单键不比双键近。编码若图省事直接给整数——C=0、N=1、O=2、S=3——线性代数看不出这是类别,它只认数字,而数字自带序与距离:C 到 O 距离为 2、C 到 N 距离为 1,编码什么都没解释,却已替你声明「O 比 N 离 C 更远」,还顺带声明了全序 C<N<O<S。这两条声明在化学里都不成立。
独热编码(one-hot 编码)。设类别域含 K 个类别。映射 e 把第 k 类送到 K 维单位向量的第 k 个方向:该分量取 1,其余取 0。K 个类别由此张成一组正交基,每类独占一个方向;类别之间的「序」与「远近」在编码后不复存在——除非数据本身带来。代价是 K 类换 K 维,维度随类别数线性增长。
两条公式带来三条推论。其一,内积 e(a)Te(b) = 0(a≠b):类别各占正交基的一个方向,互不干扰。其二,距离恒为 √2:虚假的远近被抹平,「远近」这张白纸重新变白,模型想给两类安什么关系,由数据说了算。其三,线性层 w·x + b 在 one-hot 下对第 k 类的输出是 wk + b:每个类别配一个自由权重,类别上的任意实值函数都能表出。整数编码做同一件事,输出只能沿虚构的数值轴等距排开——N 的取值被迫落在 C 与 O 的正中间,三类取值共线(习题 3.2-2 把这条约束算清楚)。
代价是维度膨胀:K 类换 K 维,元素表 16 类就是 16 列,其中 15 列对任何一个原子永远是 0。这笔账用维度换正确性——宁可稀疏,不注入虚假结构。稀疏并不贵:一个 44 维节点特征只有几个 1,按「位置、值」存储,实际占用与类别总数基本无关;稠密化只发生在进入网络、与权重矩阵相乘的一刻,而 one-hot 乘矩阵就是按类别取回对应权重行(查表),比真正的乘法还快。两相比较,把虚假距离喂给模型,之后用多少数据都洗不掉;多给几十列零,几乎不花钱。
习题 3.2-2
元素域 {C, N, O, S}。(1) 整数编码 C=0、N=1、O=2、S=3:求全部两两欧氏距离,指出其中隐含的两条「声明」。(2) 改用 4 维 one-hot:再求全部两两距离。(3) 线性模型 f(x) = w·x + b:证明整数编码下 N 的输出必为 C 与 O 输出的算术平均,one-hot 编码下三类输出可以任取。(4) 结合存储与计算,说明「维度换正确性」这笔交易为什么划算。
参考解答(1) C–N = 1,N–O = 1,O–S = 1,C–O = 2,N–S = 2,C–S = 3。声明一:全序 C<N<O<S;声明二:S 与 C「相距最远」、N 与 O「贴近 C」的程度不同——两条均无化学依据。(2) one-hot 下任意两类距离同为 √2,序与远近消失。(3) 整数编码 x 为标量特征:f(C) = b,f(N) = w + b,f(O) = 2w + b,于是 f(N) = (f(C)+f(O))/2,一般地四类输出等距共线;one-hot 编码下 f(第 k 类) = wk + b,wk 各自自由,类别上的任意函数都可表出。(4) one-hot 向量只 1 个非零,稀疏存储只记位置与 1;与权重矩阵相乘等价于取回第 k 行,计算反而更省。膨胀的只是形式维度,虚假结构却是永远洗不掉的偏差。
3.2.4 图的批处理:拼接与偏移
向量一族天然好批:等长向量按行堆叠,得到 (B, d) 的规则张量。图没有这个福气:乙醇 3 个原子,青霉素几十个,多肽成百上千,(n, d) 的 n 各不相同,堆不进同一个张量。第一条出路是填充对齐:把小图补零到批内最大节点数,再配掩码告诉模型哪些是真原子。可行,但浪费随批内尺寸离散度增长,而且任何一层忘了传掩码,结果就错。第二条出路更彻底:不堆叠,拼接——把一批分子拼成一张图批处理(graph batching)意义上的大图,即若干分子图的不相交并(disjoint union)。
拼接后,第 m 个分子的节点原样接在前面分子之后,它的边 (i, j) 整体平移 om。关键性质由构造直接保证:跨连通块没有边。消息传递每一步只沿边走(3.5 节),块间无边,块与块就互不通信——在大图上跑 L 层消息传递,与逐个分子各跑 L 层,输出严格相同。批处理只是重排了内存,没有改动计算语义。图 3.2-2 画出这张大图与偏移的账。
剩下的技术问题是读出:大图混着多个分子,读出函数(3.5 节)按块聚合才能得到每个分子的向量。为此批数据额外带一个 batch 指示向量,标记每个节点属于哪个连通块——它正是偏移序列的逆映射。从 GPU 视角看,拼接后的批仍是一组规则张量(相接的节点矩阵、平移后的边索引),稀疏的 gather/scatter 执行高效。填充与拼接并非对立:需要逐节点对齐的任务(如分子生成)仍用填充;在性质预测的主路上,拼接是默认做法。
3.2.5 特征化即归纳偏置
现在把前面的线索拧成一句话:字段表是归纳偏置(inductive bias)的注入点。选定表 3.2-2,等于向模型声明先验:「与目标性质相关的化学,都在这些字段里。」1.2 节说表示决定上限,这里给出机制:不在字段表里的属性,模型永远学不到。损失函数再聪明、数据再多,都只能在表示之内找规律;表示之外的差异,在特征化那一刻已经消失。
一个干净的反例是电荷异构体(charge isomers)。字段表若不含形式电荷——省掉它并非罕见,一些精简表只留元素、度、键型与环隶属——甘氨酸的中性形式 NCC(=O)O 与两性离子 [NH3+]CC(=O)[O-] 就折叠成同一份输入:元素逐位相同、重原子度相同、键型相同、无环无芳。输入恒等,输出必然恒等,模型被迫对生理 pH 下的主导形式与中性形式给出同一个数。这不是拟合误差,是表示层面的恒等(习题 3.2-3 给出完整论证)。
DeepChem 默认表保留了形式电荷,两种形式因此可分;下面的代码把这笔账摆出来,并看一眼量子化学任务常用的加宽字段表。
g1 = feat.featurize(["NCC(=O)O"])[0] # 甘氨酸:中性形式 g2 = feat.featurize(["[NH3+]CC(=O)[O-]"])[0] # 甘氨酸:两性离子(质子转移) diff = (g1.node_features != g2.node_features).any(axis=1) # 默认表含形式电荷一栏 print(diff.sum()) # 2:只有 N 与 O 的特征行不同——电荷字段记下了质子转移 z = dc.feat.MolGraphConvFeaturizer(use_partial_charge=True) # QM 类任务常追加部分电荷 print(z.featurize(["NCC(=O)O"])[0].node_features.shape[1]) # 字段表加宽一列
反方向的坑同样真实。字段表越宽、类别档位越多,稀有档位(某个罕见元素、某种罕见杂化)的训练样本就越少,权重学不动,反而拖累泛化。量子化学任务常反其道行之:标签对电荷分布敏感,特征化便追加部分电荷等字段,MoleculeNet 的 QM 系列甚至把 Mulliken 电荷直接列为预测目标(Gilmer et al., 2017; Wu et al., 2018)。字段的取舍是设计决策,值得一份决策流程。
为一个新任务挑选特征化字段。① 从机理出发列候选:什么原子/键属性驱动该性质?反应性任务离不开电荷与杂化,结合能任务离不开氢键与芳香性。② 审数据分布:元素覆盖、电荷范围、手性有无、环系多寡;字段表必须罩住训练集与将来的输入。③ 起步用最小字段表,配 3.3 节的强基线。④ 消融验证:逐字段增删,只看验证集而非训练集。⑤ 字段表连同版本号写进实验记录——它变了,模型的世界就变了,结果必须重新对账。
习题 3.2-3
设某特征化器的字段表只含:元素、重原子度、键型、是否芳环、是否在环(不含形式电荷,也不含氢计数)。论证:在该表下,中性甘氨酸 NCC(=O)O 与两性离子 [NH3+]CC(=O)[O-] 不可区分,并说明这对模型的预测意味着什么。
参考解答逐一核对字段。元素:两分子都是 N、C、C、O、O,逐位对应。重原子度:N 均为 1(一个重邻居 C),两个 C 分别为 2 与 3,两个 O 均为 1——完全相同。键型:N–C 单、C–C 单、C=O 双、C–O 单,两分子一致。芳环与环隶属:两者都无环。于是两张分子图同构,且同构映射下节点特征、边特征逐项相等,特征化输出(X, edge_index, E)恒等。模型的输出是输入的函数,输入恒等则输出恒等:无论标签怎么写、训练多久,两种形式必然得到同一个预测值。而化学上两性的 pKa 相差多个单位,生理环境下两性离子是主导形式——表示层面的恒等把这条信息提前删除了。出路只有改字段表:把形式电荷(或氢计数)放回去,如本节代码所示。
3.2.6 失败模式:解析失败与静默偏差
字段表管「分子变成什么」,还有一类失败发生在更早:SMILES 变不成分子的那批样本。语法错误、价键违例(六价碳)、空字符串——解析器只能返回空。特征化管线此刻面临选择。策略一:丢弃并计数,失败样本出列,数据集少几行。策略二:以空特征哨兵(sentinel features)顶替——零向量或专门的标志位占住这一行,样本留在数据集里,训练时按掩码剔除或交给模型自行处理。
两种策略没有绝对优劣,纪律比选择重要。丢弃的危险在于「静默」:被打进日志一行的 warning,在数据集层面悄悄改写分布。容易解析失败的分子——金属有机、多电荷物种、大环、配体复合物——恰恰最不寻常,也常常最值得预测;它们成批消失后,训练分布变「干净」,模型在寻常分子上的指标好看,而在最需要预警的地方没有声音。2.6 节的清洗是主动决策:丢什么、为何丢,逐一记录;这里的丢弃多半是默认行为,绕过了审查。到 3.8 节评估时,这笔账必须翻出来:成绩单只覆盖幸存者,诚实的评估要求同时报告特征化成功率与失败清单(Ramsundar et al., 2019)。
静默丢弃的偏差。设某库 1 万条记录中有 300 条解析失败、默认丢弃且无显式记录(示意数字)。表面上模型照常训练、指标照常上报;实际上训练分布已经偏移——被丢的恰是结构最怪的分子。三个连环后果:训练集与真实输入的差距被隐藏;对外报告的指标只定义在「可解析子集」上,名不副实;后续基于同一管线的实验继承了同一偏差,且无人知道。纪律:丢弃必须计数、记录理由,并在结果报告中给出特征化成功率;哨兵方案则把失败显式化,可按子集统计、可监控数据漂移。
哨兵方案的真正价值在审计而非建模:失败被显式表示,就能按子集统计失败率、按时间监控漂移,数据质量恶化时第一时间可见。特征化管线的输出因此不只是张量,还应包括一本账:多少成功、多少失败、失败的是什么。
3.2.7 小结与去向
本节把特征化既当作流水线、也当作契约来读:谱系决定表示的形态(向量或图),字段表决定模型看得见的化学,one-hot 让类别变量不携带虚假的序与距离进入张量,拼接与偏移把变长的图送进规则张量的世界。表示就位,下一节拿它去打仗:ECFP 配随机森林,先立一条不容跳过的基线(3.3 节)。
关键术语
- 特征化 (featurization)
- 把化学对象按固定规则改写为模型可用张量的环节,DeepChem 三层抽象的中间层。
- 字段表 (feature schema)
- 特征化器声明的一组字段及编码档位,决定模型看得见的化学。
- 独热编码 (one-hot encoding)
- 把 K 类类别变量映到 K 维单位向量,类别间正交且等距。
- 类别变量 (categorical variable)
- 取值无天然序与远近的变量,如元素种类、键型、杂化方式。
- 节点特征 (node features)
- 分子图中每个原子的字段向量按行堆成的矩阵。
- 边索引 (edge index)
- 以两端点编号对列出的邻接表,消息传递的通路清单。
- 图批处理 (graph batching)
- 把一批图拼成一张不相交大图并平移边索引,保持逐分子语义。
- 不相交并 (disjoint union)
- 若干图共用一套全局编号、互不连边的合成方式。
- 归纳偏置 (inductive bias)
- 学习器内建的前提假设;特征化字段表是最上游的一层。
- 原子对特征 (atom-pair features)
- Weave 一族为任意原子对(含不成键)构造的特征。
- 形式电荷 (formal charge)
- 原子的形式电荷数,区分电荷异构体的关键字段。
- 哨兵特征 (sentinel features)
- 解析失败时以占位向量顶替,保留记录并使失败可审计。
参考文献与延伸阅读
- Rogers D, Hahn M. 2010. Extended-connectivity fingerprints. Journal of Chemical Information and Modeling 50:742–754.
- Duvenaud DK, Maclaurin D, Aguilera-Iparraguirre J, et al. 2015. Convolutional networks on graphs for learning molecular fingerprints. Advances in Neural Information Processing Systems 28 (NIPS 2015).
- Kearnes S, McCloskey K, Berndl M, Pande V, Riley P. 2016. Molecular graph convolutions: moving beyond fingerprints. Journal of Computer-Aided Molecular Design 30:595–608.
- Gilmer J, Schoenholz SS, Riley PF, Vinyals O, Dahl GE. 2017. Neural message passing for quantum chemistry. Proceedings of the 34th International Conference on Machine Learning (PMLR 70):1263–1272.
- Wu Z, Ramsundar B, Feinberg EN, et al. 2018. MoleculeNet: a benchmark for molecular machine learning. Chemical Science 9:513–530.
- Ramsundar B, Eastman P, Walters P, Pande V. 2019. Deep Learning for the Life Sciences: Applying Deep Learning to Genomics, Microscopy, Drug Discovery, and More. Sebastopol: O'Reilly Media.
- DeepChem. 2024. DeepChem 2.8 官方文档: Molecular Featurizers. deepchem.io.