3. Transformer 核心原理:从 Scaled Dot-Product Attention 到完整结构
Transformer 这个东西,现在做多模态的人几乎天天都在用。但说实话,很多人只是调包调习惯了,真问到里面那几个核心公式为什么这么设计,反而答不上来。我当年刚接触时也踩过不少坑,尤其是位置编码那块,理解偏了后面做多模态对齐就会出问题。
这一章咱们就把 Transformer 的底裤扒干净。从最基础的注意力计算,到多头机制,再到位置编码和整体结构,一个一个来。
3.1 Scaled Dot-Product Attention:为什么非要除根号 d_k?
先看最核心的公式:
Attention(Q, K, V) = softmax(Q × K^T / √d_k) × V
Q、K、V 分别是 Query、Key、Value 矩阵。d_k 是 Key 的维度。
很多人会问:为什么非要除这个 √d_k?
我解释一下。Q 和 K 做点积,结果是一个标量。如果 d_k 很大,比如 512 甚至 1024,点积的方差就会很大。方差大了,softmax 的输入值就会往两端跑——有的特别大,有的特别小。softmax 对极端值特别敏感,梯度会变得非常小,模型就学不动了。
除以 √d_k 相当于把方差拉回到 1 左右。这样 softmax 的梯度就能保持稳定。说白了,这是个数值稳定性技巧。
计算流程其实很简单:
- Q 和 K^T 做矩阵乘法,得到注意力分数矩阵
- 每个元素除以 √d_k
- 对每一行做 softmax,得到注意力权重
- 权重矩阵乘以 V,得到加权后的输出
用代码实现就是:
import torch
import torch.nn.functional as F
def scaled_dot_product_attention(Q, K, V):
d_k = Q.size(-1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / torch.sqrt(torch.tensor(d_k, dtype=torch.float32))
attn_weights = F.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)
return output, attn_weights
3.2 多头注意力机制:让模型从不同角度去看
单头注意力有个问题:它只能学习一种注意力模式。但实际场景中,一个词可能跟多个不同位置的词都有关系。比如 "苹果" 这个词,在 "苹果很好吃" 里跟 "好吃" 相关,在 "苹果公司发布了新手机" 里跟 "公司" 和 "手机" 相关。
多头注意力就是让模型同时学习多个不同的注意力模式。每个头关注不同的子空间,最后把结果拼起来。
具体做法:
- 把 Q、K、V 分别线性投影到 h 组不同的子空间
- 每组独立做 Scaled Dot-Product Attention
- 把 h 个头的输出拼接起来
- 再做一次线性变换,得到最终输出
公式长这样:
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) × W_O
其中 head_i = Attention(Q × W_Q_i, K × W_K_i, V × W_V_i)
W_Q_i、W_K_i、W_V_i 是每个头的投影矩阵,W_O 是输出投影矩阵。
我建议你记住一个数字:8 个头是最常见的配置。BERT base 用 12 个头, 用 12 个头,ViT 用 12 个头。但也不是越多越好,头太多每个头分到的维度太少,反而学不到什么东西。
3.3 位置编码:给序列注入顺序信息
Transformer 没有循环结构,也没有卷积。它一次性看到整个序列,但不知道哪个词在前哪个词在后。如果不加位置信息,"我打你" 和 "你打我" 在模型眼里是一样的。
位置编码就是来解决这个问题的。
最经典的是正弦余弦位置编码:
PE(pos, 2i) = sin(pos / 10000^(2i / d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i / d_model))
其中 pos 是位置索引,i 是维度索引。
为什么用正弦余弦?有几个好处:
- 值域在 [-1, 1] 之间,稳定
- 不同位置的编码可以通过线性变换近似得到,模型能学到相对位置关系
- 可以外推到比训练时更长的序列
除了正弦余弦,现在更常用的是可学习位置编码。BERT、 系列用的都是可学习的。每个位置对应一个可训练的向量,让模型自己学出位置信息。好处是更灵活,坏处是不能外推到更长的序列。
还有一个变种是相对位置编码,比如 Transformer-XL 和 T5 用的那种。它不直接编码位置,而是编码两个 之间的相对偏移。在做长序列任务时,相对位置编码往往效果更好。
3.4 Transformer 整体结构:Encoder-Decoder 的骨架
把上面这些组件拼起来,就是完整的 Transformer 了。
标准的 Transformer 是 Encoder-Decoder 结构。Encoder 把输入序列映射成连续表示,Decoder 根据这个表示生成输出序列。
每个 Encoder 层包含两个子层:
- 多头自注意力
- 前馈神经网络(FFN)
每个 Decoder 层包含三个子层:
- 掩码多头自注意力(防止看到未来信息)
- 交叉注意力(关注 Encoder 的输出)
- 前馈神经网络
每个子层后面都跟着残差连接和 LayerNorm。这个设计很巧妙——残差连接让梯度能直接流过,LayerNorm 稳定训练过程。
下面这张图展示了 Transformer 的核心结构:
你可能会问:Encoder 和 Decoder 之间怎么交互?答案是交叉注意力。Decoder 的 Query 来自自身,Key 和 Value 来自 Encoder 的最后一层输出。这样 Decoder 在生成每个词时,都能"看到"完整的输入序列。
3.5 关键参数速查表
下面这个表是我平时做工程时常用的参数配置,你可以直接参考:
| 参数 | 符号 | 常见取值 | 说明 |
|---|---|---|---|
| 模型维度 | d_model | 512, 768, 1024 | 所有子层的统一维度 |
| 头数 | h | 8, 12, 16 | d_model 必须能被 h 整除 |
| 每头维度 | d_k = d_v | 64, 64, 64 | 通常固定为 64 |
| FFN 中间维度 | d_ff | 2048, 3072, 4096 | 通常是 d_model 的 4 倍 |
| 层数 | N | 6, 12, 24 | 越深能力越强,但训练难度也越大 |
| Dropout | - | 0.1 | 防止过拟合 |
好了,Transformer 的核心原理就这些。Scaled Dot-Product Attention 解决了注意力计算的稳定性问题,多头机制让模型能从不同角度理解关系,位置编码补上了序列顺序信息,整体结构把这几块拼成了一个可训练的端到端系统。