第三章 · 3.2

3.2 特征化管线:分子如何变成模型输入

The Featurization Pipeline
特征化把化学对象改写为张量,是化学先验进入模型的位置。本节以谱系表串起 ECFP 位向量、理化描述符与图结构三类表示,逐字段剖析图特征化的节点与边字段及其化学假设,阐明 one-hot 编码以维度换取类别变量上不存在的序与距离,说明变长图靠连通块拼接与索引偏移完成批处理,最后分析字段缺失与解析失败两类风险的机理。

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 里常用的特征化器沿这条线排开。

表 3.2-1DeepChem 常用特征化器谱系:向量一族与图一族
特征化器产物形状典型下游
CircularFingerprintECFP 位向量(2.5 节)定长,如 1024/2048 位随机森林等传统模型(3.3 节基线)
RDKitDescriptors约 200 维理化+拓扑描述符(2.4 节)定长实数向量传统模型的另一套基线输入
MolGraphConvFeaturizerGraphData:节点特征矩阵 + 边索引 + 边特征随分子大小变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),也是本节习题沿用的工作表。

表 3.2-2图特征化的典型字段表及其化学假设
部位字段编码化学假设——为什么放进去
节点元素种类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 章),原子与键分别流向两张表。

SMILES 解析为分子图后,原子与键分别流入节点特征矩阵与边索引、边特征表 输入 SMILES "CCO" C C O 分子图:3 个原子、2 条键(2.1 节) 特征化器 = 字段表 + 编码规则 字段一经选定即成契约 节点特征矩阵 X:3 行 × 44 列 原子 0 · C 原子 1 · C 原子 2 · O 元素 16 度 11 电荷 5 手性 4 杂化 6 芳 1 · 环 1 列数 d = 16+11+5+4+6+1+1 = 44:字段表的宽度(习题 3.2-1) one-hot 点亮的维:每个字段块至多一维为 1 边索引与边特征 src dst 0 1 1 0 1 2 2 1 键型 4 共轭 1 · 环 1 每条键拆成两条有向边:消息沿两个方向各走一遍 一个分子 → 三元组 (X, edge_index, E):DeepChem 称之为 GraphData 变长三元组如何进入批处理:图 3.2-2 的拼接与偏移。
图 3.2-1 字段流。SMILES 先解析为分子图(第 2 章),原子与键分别流入两张表:节点特征矩阵每行一个原子,按字段表分块,紫色方块为点亮的 one-hot 维;边索引把每条键拆成两条有向记录,边特征同样按字段分块。乙醇无环、无芳环、无共轭,末尾几个块全空。列数 44 与习题 3.2-1 的字段表一致;DeepChem 默认表取项相近,宽度为 30。

下面印证字段表的形态:对阿司匹林做图特征化,打印三元组的形状。

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(k)j = δkj = 1(j = k);0(j ≠ k)  k, j ∈ {1, …, K}
(3.2-1)δ 为 Kronecker 记号。one-hot 向量恰有一个非零分量,非零位置即类别编号。
‖e(a) − e(b)‖2 = 0(a = b);2(a ≠ b)
(3.2-2)任意两个不同类别距离同为 √2:等距。对照整数编码 d(C,N)=1、d(C,O)=2、d(C,S)=3——序与远近全是虚构。

两条公式带来三条推论。其一,内积 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)

(i, j) ∈ Em → (i + om, j + om), om = Σm′<m nm′
(3.2-3)nm 为第 m 个分子的节点数;偏移 om 是排在它前面的分子节点总数。节点特征矩阵上下相接,边索引逐块平移。

拼接后,第 m 个分子的节点原样接在前面分子之后,它的边 (i, j) 整体平移 om。关键性质由构造直接保证:跨连通块没有边。消息传递每一步只沿边走(3.5 节),块间无边,块与块就互不通信——在大图上跑 L 层消息传递,与逐个分子各跑 L 层,输出严格相同。批处理只是重排了内存,没有改动计算语义。图 3.2-2 画出这张大图与偏移的账。

乙醇、苯、乙酸三个小分子经节点重编号与边偏移拼成一张不相交大图 分子 A:CCO(3 原子) C C O 分子 B:苯(6 原子) C C C C C C 分子 C:乙酸(4 原子) C C O O 块 A:偏移 o = 0 块 B:偏移 o = 3 块 C:偏移 o = 9 0 1 2 C C O 3 4 5 6 7 8 C C C 9 10 11 12 C O O 边重写规则:块 m 的边 (i, j) → 大图边 (i + o, j + o),偏移逐块取 (0, 3, 9) 例:苯的边 (0, 1) → (3, 4);乙酸的边 (1, 3) → (10, 12)。两条虚线之间没有任何边。 一步消息只沿边走:块间无边,块与块互不通信——批计算与逐分子计算严格等价。 batch 指示向量 b = (0,0,0, 1,1,1,1,1,1, 2,2,2,2):读出阶段按块聚合(3.5 节)。
图 3.2-2 批拼接。乙醇、苯、乙酸各自带局部编号(上行),拼接成一张大图(下行):节点重新全局编号 0–12,块内边按偏移 o = (0, 3, 9) 平移,虚线两侧不存在任何边。一步消息传递只沿边走,块与块因此互不通信;在大图上跑 L 层与逐分子各跑 L 层,输出相同。读出阶段按 batch 指示向量把 13 个节点重新分成三组。

剩下的技术问题是读出:大图混着多个分子,读出函数(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)
解析失败时以占位向量顶替,保留记录并使失败可审计。

参考文献与延伸阅读

  1. Rogers D, Hahn M. 2010. Extended-connectivity fingerprints. Journal of Chemical Information and Modeling 50:742–754.
  2. 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).
  3. 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.
  4. 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.
  5. Wu Z, Ramsundar B, Feinberg EN, et al. 2018. MoleculeNet: a benchmark for molecular machine learning. Chemical Science 9:513–530.
  6. 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.
  7. DeepChem. 2024. DeepChem 2.8 官方文档: Molecular Featurizers. deepchem.io.