《Attention is All You Need》
核心创新点:Transformer 架构和 Self-Attention 自注意力机制
Transformer 之前,主流序列转导模型通常具有以下特征:
- 主体结构多基于 RNN,CNN 作为替代路线
- 通常采用 encoder-decoder 架构
- 使用注意力机制增强
Transformer结构的创新:
- 用 Self-Attention 取代 RNN/CNN 作为序列建模核心
- 引入 Multi-Head Attention,让模型从多个子空间看关系
- 用 Positional Encoding 补足序列位置信息
Transformer是首个完全基于自注意力、不使用RNN或CNN的序列建模模型
Encoder-Decoder 架构
早期 seq2seq 的核心思想:encoder 先压缩,decoder 再生成
- 编码器负责“读懂输入序列”,把它压缩成一个向量
- 解码器负责“根据这个向量逐步生成输出序列
编码器解码器结构解决了输入输出不等长的问题
早期 encoder 通常是一个 RNN / LSTM / GRU,编码器按顺序读入 token
每读一个 token,更新一次隐藏状态
$$
h_t = f(h_{t-1}, x_t)
$$
最后,encoder 会得到一个整体表示,通常记作$c = h_n$ (context vector),也可以理解成“源句子的压缩表示”
解码器 decoder 在第 $t$ 步根据:
- encoder 给出的上下文向量 $c$
- 上一步生成的 token $y_{t-1}$
- decoder 自己的隐藏状态 $s_{t-1}$
预测当前 token $P(y_t \mid y_{<t}, c)$
早期模型最大的问题是:不管输入句子多长,都要压缩进一个固定长度向量 $c$
这会导致:
- 长句信息容易丢失;
- 远距离依赖难以保存;
- decoder 生成后半句时,可能已经“忘了”输入前面的内容;
- 所有源句信息都挤在一个固定向量里,形成 bottleneck
Attention 机制
加入 attention 之后
1 | source sequence -> Encoder -> h1, h2, ..., hn |
decoder 不再只看一个固定向量 $c$,而是在生成每个词时动态查看 encoder 的所有隐藏状态
解决的问题:
解决模型处理长序列时的“遗忘”问题
解决不同时间步输入对当前时刻输出的“重要性”问题
decoder 每生成一个词,都计算当前解码状态和所有 encoder 隐藏状态的相关性,然后加权汇总
$$
c_t = \sum_{i=1}^{n} \alpha_{t,i} h_i
$$
位置编码
因为没有 RNN 的时间递推,也没有 CNN 的局部窗口顺序结构,Self-Attention 本身不知道 token 的顺序
在 encoder 和 decoder 底部给输入 embedding 加入 positional encodings,用来注入 token 的位置信息
$$
X_{pos} = E_{pos} + PE_{pos}
$$
原始 Transformer 用的正弦/余弦位置编码
$$
PE_{(pos,2i)}
\sin
\left(
\frac{pos}{10000^{2i/d_{\text{model}}}}
\right)\\
PE_{(pos,2i+1)}
\cos
\left(
\frac{pos}{10000^{2i/d_{\text{model}}}}
\right)
$$
假设模型维度是 $d_{\text{model}} = 4$,那么一个位置 $pos$ 的位置编码大概长这样
$$
PE_{pos}
[
\sin(\omega_1 pos),
\cos(\omega_1 pos),
\sin(\omega_2 pos),
\cos(\omega_2 pos)
]
$$
不同维度使用不同频率,
- 低维度:变化快,区分近距离位置
- 高维度:变化慢,表达长距离趋势
为什么不用简单的 1, 2, 3, 4?
Bert有这么做过,直接用整数位置有几个问题:
- 标量位置太粗糙,难以和高维词向量融合
- 数值大小会随序列长度变大,尺度不稳定
- 不容易表达相对位置关系,比如“前一个词”“后两个词”
正弦/余弦编码的好处是:每个位置被表示成一个稳定的高维模式,而且不同频率可以覆盖不同尺度的位置关系
论文中给出的一个重要动机是:对于固定偏移 $k$,$PE_{pos+k}$ 可以表示为 $PE_{pos}$ 的线性函数,因此模型可能更容易学习相对位置关系
多头注意力机制
多头注意力 Multi-Head Attention 的核心思想是:把同一批 token 映射到多个不同的表示子空间中,并行做多次 attention,然后把结果拼接起来
Transformer 使用的基础注意力是 Scaled Dot-Product Attention
$Q,K,V$ 都来自同一个输入序列,让输入序列内部的 token 互相建模关系
$$
\mathrm{Attention}(Q,K,V)=\mathrm{softmax}\left(\frac{QK^\top}{\sqrt{d_k}}\right)V
$$
$$
\mathrm{Softmax}(z_i)=\frac{e^{z_i}}{\sum_{j=1}^{k} e^{z_j}}
$$
QK^T:计算每个 token 对其他 token 的关注程度- softmax:把关注分数归一化成权重
- 乘 V:根据权重加权汇聚信息
经过这个Attention以后的向量信息会融合其它token的信息
如果只有一个 attention head,那么模型只有一套 $Q,K,V$ 投影,那么token只能用一种方式去理解自己
但是语言中token的关系从来不是一维的,单头使得被迫将不同层面的关系压缩进一组权重,注意力权重变成一个折中的分布,一个注意力分布无法承载多种独立的关系模式,表达能力被严重限制
多头注意力的设计就是:允许模型在多个子空间中并行学习不同的相关性模式
多头注意力不是直接对原始 $Q,K,V$ 做很多次一样的 attention,而是先用不同的线性变换得到不同 head 的 $Q_i,K_i,V_i$
1 | q_proj = nn.Linear(d_model, d_model) |
第 $i$ 个 head 是
$$
\mathrm{head}_i
\mathrm{Attention}
\left(
QW_i^Q,
KW_i^K,
VW_i^V
\right)
$$
然后把所有 head 的输出拼接起来
$$
\mathrm{MultiHead}(Q,K,V)=\mathrm{Concat}(head_1,\dots,head_h)W^O
$$
再经过一个线性变换,融合多头信息
$$
\mathrm{FFN}(x)=W_2,\sigma(W_1x+b_1)+b_2
$$
相当于八个低清晰度的表情包你给拼一块了说它是高清的,那肯定不行,还得再处理一下
这种模式使得每个头只需要捕获一种或少量关系模式
这里也会有一个问题,拆成多个头降低了维度,可能会损失表达能力,但损失的远小于收益
拆分成多头在同等计算量上获取到的信息大于单头,并且低维子空间相当于隐含的正则化,保证每种学习关系独立,也防止了每个头在高维空间过拟合,多头保证了多种关系可以并行捕捉
head数量需要根据实际情况决定,head 太少,表达能力可能不足;head 太多,每个 head 的维度会变小,单个 head 的表示能力下降,而且计算和工程开销增加
通常 $h$ 是一个超参数,需要和 $d_{\text{model}}$ 配合
Mask
mask 不是加在 $Q$、$K$、$V$ 上,而是加在 attention logits 上
$$
\mathrm{MaskedAttention}(Q,K,V)
\mathrm{softmax}
\left(
\frac{QK^\top}{\sqrt{d_k}} + M
\right)V
$$
其中 $M$ 是 mask 矩阵
$$
M_{ij}
\begin{cases}
0, & j \le i \
-\infty, & j > i
\end{cases}
$$
这样可以获得每个 token 根据注意力权重,从它能看到的 token 的 value 向量中汇聚出来的新表示
加Mask是为了避免关注到后面的信息,只注意自己和之前的信息
张量形状
每个 token 原始 hidden state 的维度是
$$
X \in \mathbb{R}^{B \times L \times d_{\text{model}}}
$$
通过线性层得到
1 | Q: [B, L, d_model] |
然后拆成 $h$ 个 head:
$$
d_{\text{head}} = \frac{d_{\text{model}}}{h}
$$
如果 $d_{\text{model}}=512$,$h=8$,那么
1 | Q: [B, h, L, d_head] = [B, 8, L, 64] |
每个 head 单独做 attention
1 | head_i: [B, L, 64] |
8 个 head 拼接后
1 | Concat(heads): [B, L, 512] |
最后再经过输出投影 $W^O$
分母 $\sqrt{d_k}$
注意力分数是:
$$
q \cdot k = \sum_{i=1}^{d_k} q_i k_i
$$
假设$q_i, k_i$都是均值为 $0$、方差为 $1$ 的独立随机变量
那么单项乘积 $q_i k_i$ 的均值大约是 $0$,方差大约是 $1$
点积是 $d_k$ 个这样的项相加,所以方差大约是
$$
\mathrm{Var}(q \cdot k) \approx d_k
$$
$d_k$ 越大,点积结果越容易变得很大或很小
为了把点积分数标准化到相对稳定的尺度,需要除以标准差
如果不除会导致
- attention 权重过早接近 one-hot,模型探索性变差
- softmax 进入饱和区,梯度很小,训练变慢或不稳定
本质上类似于初始化中的方差归一化思想
Post-LN和Pre-LN的区别
Post-LayerNorm是原文设计
1 | x |
Attention block:
$$
x_1 = \mathrm{LayerNorm}(x+\mathrm{Attention}(x))
$$
FFN block:
$$
x_2 = \mathrm{LayerNorm}(x_1+\mathrm{FFN}(x_1))
$$
可以这么理解
$$
y = \mathrm{LN}(x+F(x))\quad z= x+F(x)
$$
损失对输入的梯度:
$$
\frac{\partial L}{\partial x}=\frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial x} = \frac{\partial L}{\partial y} \cdot \frac{\partial \mathrm{LN}}{\partial z}(I
+\frac{\partial F}{\partial x})
$$
问题在于LN在在残差连接之后,${\partial \mathrm{LN}}/{\partial z}$梯度值近似为$1/\sqrt d_k$,使得残差传播的恒等路径梯度不再是1,每层都要缩放一次
所以梯度随网络深度呈指数衰减,导致低层(靠近输入的层)梯度几乎消失,梯度消失会导致Adam等优化器的更新变得不稳定
Layer Norm
对每个 token 的 hidden dimension 做归一化
- 不跨 batch 归一化
- 不跨 sequence length 归一化
- 只在每个 token 自己的特征维度上归一化
$$
\mu = \frac{1}{d}\sum_{i=1}^{d} x_i\qquad \sigma^2 = \frac{1}{d}\sum_{i=1}^{d}(x_i - \mu)^2
$$
归一化:
$$
\hat{x}_i = \frac{x_i - \mu}{\sqrt{\sigma^2 + \epsilon}}
$$
不是像 Batch Norm 那样依赖 mini-batch 统计
Transformer 需要 Layer Norm是因为每一层都有复杂变换
如果不做归一化,层数加深后 hidden states 的尺度可能变得不稳定
Layer Norm 的作用是把每个 token 的 hidden state 拉回到相对稳定的尺度
Post-LayerNorm
Post-LN 是原始 Transformer 的写法;Pre-LN 是后来更常用的稳定训练写法
1 | x |
Attention block:
$$
x_1 =\mathrm{LayerNorm}(x+\mathrm{Attention}(x))
$$
FFN block:
$$
x_2 = \mathrm{LayerNorm}(x_1+\mathrm{FFN}(x_1))
$$
Post-LN 的优点是
- 每个子层输出后都被规范化,层输出尺度更受控
但缺点是
- LayerNorm 位于 residual add 之后,会影响残差路径上的梯度传播
- 深层时训练更敏感
- 通常更依赖 learning rate warm-up、初始化和优化器超参数
Pre-LayerNorm
把 LayerNorm 移到子层前面
1 | x |
Attention block:
$$
x_1 =x+\mathrm{Attention}( \mathrm{LayerNorm}(x))
$$
FFN block:
$$
x_2 = x_1+\mathrm{FFN}(\mathrm{LayerNorm}(x_1))
$$
最关键的在于这样使得残差路径变成恒等映射
$$
y = x+F(\mathrm{LN}(x))\quad u = \mathrm{LN}(x)
$$
损失对输入的梯度:
$$
\frac{\partial L}{\partial x}=\frac{\partial L}{\partial y} \cdot \frac{\partial y}{\partial x} = \frac{\partial L}{\partial y}(I+\frac{\partial F}{\partial u}\cdot \frac{\partial u}{\partial x})
$$
Pre-LN 的优点是
- residual path 更接近 identity
- 梯度传播更顺畅
- 深层模型更容易稳定训练
- 对 learning rate warm-up 的依赖更小
缺点是
- 每层输出没有立即归一化,主残差流的尺度可能累积
- 子层 F 可能更像是在 residual stream 上加小修正
- 极深模型中可能出现“有效深度不足”或层贡献变弱的问题
为什么现在模型主流是Decoder结构?
18年GPT和BERT发布
GPT采用Decoder-only,单向注意力,从左往右预测下一个token
BERT采用Encoder-only,双向注意力,采用 Masked Language Modeling,在当时的 NLP 基准测试上显著优于 GPT1
转变可以分为几个原因
核心原因 训练效率差异
Decoder-only 采用因果语言模型训练,给定前面的 token,预测下一个 token
在一段长度为n的序列中,每个位置都会产生预测任务,因此一个 1024 token 的序列可以产生 1024 个训练信号,并同时计算 loss
相比之下,BERT 的 MLM 训练只对约 15% 被遮盖的位置计算 loss,同样长度的序列只有大约 150 个有效训练信号,其余位置不会产生梯度更新
当训练数据规模达到万亿 token 级时,这种训练信号利用率差异会带来明显的效率差距
重要原因 训练和推理的一致性
BERT 在训练时需要引入 mask 标记,模型学习的是“看到 mask 预测原 token”,而在实际下游任务或推理时,输入文本中通常并不存在 mask 标记,因此训练数据分布和推理分布并不完全一致
虽然 BERT 通过随机替换等策略缓解这一问题,但本质上的分布偏移仍然存在
Decoder-only 模型则始终执行同一种行为——从左到右预测下一个 token——训练和推理过程完全一致,因此在大规模生成任务中更加稳定
上下文学习(In-context Learning),也称为零样本学习(zero-shot)
从 GPT2 开始发现模型即使不进行微调,只通过在 prompt 中给出几个示例,就可以在新任务上产生合理输出
这种能力与 decoder-only 的训练方式高度一致:示例可以直接作为上下文的一部分,让模型继续生成答案
而 BERT 的训练目标是“填空预测”,并不天然支持这种序列生成式学习,因此很难形成类似能力
scaling 行为的可预测性
OpenAI 的 scaling law 研究发现,在自回归语言模型中,当模型参数量、数据量和计算量同时扩大时,训练 loss 会按照稳定的幂律规律下降,并且这种趋势可以较好预测
这种可预测性使工程团队能够在训练前评估算力投入和模型性能之间的关系,从而支持超大规模模型训练决策,Decoder-only 架构在这一 scaling 过程中表现出非常稳定的行为
因果注意力
单向注意力迫使模型只能依赖已有上下文推断后续内容,不能直接利用未来信息,这种限制在模型规模扩大后反而有利于模型学习更强的因果推理能力
而双向注意力虽然在小模型阶段拟合速度更快,但容易利用统计捷径
