4. Transformer 模型基础:从直觉到架构
聊到分子特征编码,绕不开 Transformer。这个模型当年在 NLP 领域一炮而红,后来被化学信息学“借”过来,发现效果出奇的好。我最早接触它是在做分子性质预测项目时,当时试了一堆传统指纹和图神经网络,效果总差口气。换成 Transformer 之后,某些任务的精度直接跳了 5 个百分点——可以说印象深刻。
这一讲,我们先把 Transformer 的骨架搭起来。不扯太深的理论,重点是让你知道它“长什么样”、“每个零件干嘛用”。后面几讲再逐步填充血肉。
4.1 从全局看:编码器-解码器结构
Transformer 最初是为序列到序列任务设计的,比如机器翻译。所以它的整体架构分两半:编码器(Encoder)和 解码器(Decoder)。
我以前带新人时,常把这比作“做笔记”和“写作业”。编码器负责读懂输入序列,把它浓缩成一组“知识向量”;解码器则拿着这些向量,一步步生成输出序列。当然,在分子特征编码场景里,我们通常只取编码器部分——把分子的 SMILES 或图结构编码成特征向量,然后接下游任务(分类、回归等)。
具体来说,编码器和解码器由多个相同结构的层堆叠而成。每一层包含两个关键子层:多头自注意力和前馈神经网络。解码器则多一个掩码自注意力子层,用来防止看到未来信息。
为了让你看得更清楚,我画了张架构图。注意看两侧的结构差异:
你会发现,编码器的输出(上下文向量)会作为 Key 和 Value 传给解码器的交叉注意力层。这样解码器在生成每个新 token 时,都能“回顾”输入序列的全局信息。这是整个架构最巧妙的地方——信息不对称,但相互依赖。
4.2 核心组件拆解
现在我们把编码器里的两个关键组件拆开来看。解码器的结构类似,只是多了一层交叉注意力,原理是相通的。
4.2.1 自注意力机制
说白了,自注意力的目的就是让每个位置“看”到序列里的其他所有位置,然后决定“我应该多关注谁”。你想想看,在句子 “The cat sat on the mat” 里,“sat” 和 “cat” 的关联显然比 “sat” 和 “mat” 更紧密——自注意力就是要学出这种关系。
具体计算分三步:
- 对每个输入,通过三个不同的线性变换得到 Query、Key、Value 向量。
- 计算 Query 和所有 Key 的点积,除以 sqrt(d_k) 做缩放,然后用 softmax 归一化成注意力权重。
- 将注意力权重与 Value 加权求和,得到当前位置的输出。
我之前用 Python 写过一个极简版,帮你理解核心运算逻辑:
import numpy as np
def simple_attention(Q, K, V):
# Q, K, V: (seq_len, d_k)
d_k = Q.shape[-1]
scores = np.matmul(Q, K.T) / np.sqrt(d_k) # 缩放点积
weights = np.exp(scores) / np.sum(np.exp(scores), axis=-1, keepdims=True) # softmax
output = np.matmul(weights, V)
return output, weights
实际项目中,我们绝不会手写这个——PyTorch 和 TensorFlow 都封装好了。但理解这个流程很重要,因为后面讲分子图结构时,你会看到同样的思想被用于“原子-原子”关系建模。
4.2.2 多头注意力
自注意力一次只能学一种“关系模式”。但实际任务中,分子里的原子可能同时有“局部成键关系”、“全局环结构关系”、“官能团归属关系”——多个头正好解决这个问题。
多头注意力的做法很简单:把 Query、Key、Value 切分成 h 份(h 是头数),每份分别做自注意力,最后把结果拼起来再过一次线性层。我见过初学者把“多头”想象成多个独立的注意力模块,其实不是——它们共享输入,只是投影到不同的子空间。
头数 h 和每个头的维度 d_k 满足关系:d_model = h * d_k。比如说,d_model=512,h=8,那么每个头的维度就是 64。这个配比我个人习惯在开新项目时先用默认值,跑通后再调。
4.2.3 位置编码
自注意力是置换不变的——它不考虑顺序。这对分子来说很要命,因为 SMILES 字符串的顺序(原子出现的先后)包含了重要信息,分子图的拓扑结构更是跟顺序强相关。所以我们必须加位置编码。
原版 Transformer 使用的是正弦/余弦位置编码,公式如下:
PE(pos, 2i) = sin(pos / 10000^{2i / d_model})
PE(pos, 2i+1) = cos(pos / 10000^{2i / d_model})
这个方法的好处是:不用额外学习参数,而且理论上能处理任意长度的序列。我实际用下来,它在 SMILES 编码任务上表现不错,但如果你用图数据,可能需要考虑其他位置编码方式(比如拉普拉斯位置编码),这个我们后面会专门讲。
4.2.4 前馈神经网络 (FFN)
自注意力层后面接一个两层的全连接网络,中间带一个 ReLU 激活:
FFN(x) = max(0, xW1 + b1) W2 + b2
通常 W1 的维度是 d_model × d_ff,d_ff 一般是 d_model 的 4 倍。这个 FFN 本质上在做“逐位置的特征变换”——每个位置的输出只依赖该位置的信息,和自注意力的“跨位置”特性互补。
4.3 残差连接与层归一化
两个重要的小技巧,但少了它们 Transformer 根本训不动。
- 残差连接:把子层的输入加到输出上,即
output = x + sublayer(x)。这解决了深层网络梯度消失的问题。我第一次训 6 层 Transformer 时没加残差,loss 直接不动,加了之后立刻收敛。 - 层归一化:对每个样本的特征维度做归一化(均值为 0,方差为 1)。它能让训练更稳定,而且不受 batch size 影响——这一点比批归一化更适合序列模型。
原版架构用的是“后归一化”(子层后才做归一化),但后来很多变体改用“前归一化”(子层前就归一化),训练更稳定。我的经验是:小模型用后归一化差别不大,大模型推荐前归一化。
4.4 关键超参数速查
以下是我做分子编码项目时最常调的参数,整理成了一张表:
| 参数 | 含义 | 常见范围 | 我的建议 |
|---|---|---|---|
| d_model | 词嵌入维度 / 特征维度 | 128 ~ 1024 | 256 起步,看数据量决定 |
| d_ff | 前馈网络隐藏层维度 | d_model × 4 | 保持 4 倍,调太大除了费显存没好处 |
| h | 注意力头数 | 4 ~ 16 | 8 是安全牌 |
| N | 编码器/解码器层数 | 2 ~ 12 | 分子任务 4~6 层够用 |
| dropout | 丢弃率 | 0.1 ~ 0.3 | 小数据集用 0.2,大数据集 0.1 |
| pos_encoding | 位置编码方式 | sin/cos / 可学习 / RoPE | SMILES 用 sin/cos,图结构用 RoPE |
嗯,这部分信息量有点大。但别担心,后面几讲我们会逐一展开这些组件在分子场景下的具体用法。你现在只要记住 Transformer 的基本骨架:自注意力捕捉全局依赖,FFN 做逐位置变换,残差+归一化保证训练稳定——就够了。