4、Transformer为何选择LayerNorm:原始论文动机、残差连接与归一化的相互作用
这个问题,我当年刚接触Transformer时也困惑过。明明BatchNorm在CV领域那么成功,为什么到了NLP这边,大家偏偏选了LayerNorm?
说实话,这不是拍脑袋的决定。背后有很扎实的理论考量。今天我们就从原始论文的动机出发,把这件事彻底讲透。
4.1 原始论文里的“潜台词”
翻回2017年的《Attention Is All You Need》,你会发现作者对归一化的选择其实着墨不多。但字里行间,藏着关键信息。
论文里提到,他们尝试了BatchNorm,但效果不好。为什么?
我个人理解,核心原因有两个:
- 序列长度可变:NLP任务里,句子长度参差不齐。BatchNorm需要统计整个batch的均值和方差,短句和长句混在一起,统计量会变得很不稳定。我在项目中遇到过,一个batch里混了10个词的短句和100个词的长句,BatchNorm的均值直接被长句带偏了。
- 训练与推理的不一致:BatchNorm在训练时用batch统计量,推理时用全局统计量。对于变长序列,这个全局统计量很难算准。你想想看,推理时来了个超长句子,全局统计量里根本没覆盖到,效果能好吗?
LayerNorm就没有这个问题。它是对每个样本单独做归一化,跟batch里其他样本无关。说白了,它只关心“这个样本自己长什么样”。
核心区别一句话总结:
BatchNorm是“横向”归一化(跨样本),LayerNorm是“纵向”归一化(跨特征)。在NLP里,纵向归一化更合理。
4.2 残差连接与归一化的“化学反应”
这里有个很有意思的点。Transformer里,LayerNorm不是放在残差连接之前,就是放在之后。这两种方式,效果差别很大。
原始论文用的是Post-LN(先残差,后归一化):
# Post-LN 结构
def post_ln_block(x):
residual = x
x = self_attention(x)
x = x + residual # 先残差连接
x = layer_norm(x) # 后归一化
return x
后来很多工作发现Pre-LN(先归一化,后残差)更稳定:
# Pre-LN 结构
def pre_ln_block(x):
residual = x
x = layer_norm(x) # 先归一化
x = self_attention(x)
x = x + residual # 后残差连接
return x
为什么会这样?
嗯,这里要注意。Post-LN有个隐藏问题:梯度爆炸。残差连接会把梯度直接回传,如果归一化放在后面,前面的层可能会收到非常大的梯度。我曾经调试一个12层的Transformer,用Post-LN训练到第8层时loss直接飞了,换成Pre-LN就稳了。
我的经验:
如果你在训练深层Transformer(比如12层以上),建议优先尝试Pre-LN。虽然Post-LN理论上表达能力更强,但Pre-LN的训练稳定性好太多了。
4.3 LayerNorm的数学本质
我们来看看LayerNorm到底做了什么。公式其实很简单:
# LayerNorm 计算过程
def layer_norm(x, eps=1e-5):
# x shape: [seq_len, d_model]
mean = x.mean(dim=-1, keepdim=True) # 沿特征维度求均值
var = x.var(dim=-1, keepdim=True) # 沿特征维度求方差
x_norm = (x - mean) / torch.sqrt(var + eps)
return x_norm * gamma + beta # 可学习的缩放和平移
注意看,mean和var是沿着d_model维度(也就是特征维度)计算的。这意味着:
- 每个位置(token)都有自己的均值和方差
- 不同位置之间互不影响
- batch里的样本也互不影响
这跟BatchNorm完全相反。BatchNorm是沿着batch维度算均值和方差,所以它要求batch里样本的分布一致。但在NLP里,不同句子的分布差异很大,BatchNorm就吃不消了。
避坑指南:
我曾经在做一个多语言翻译模型时,把LayerNorm误写成了沿着seq_len维度归一化。结果模型训练了3天,BLEU分数死活上不去。查了两天才发现是归一化维度搞错了。记住:LayerNorm一定是沿着特征维度(d_model)归一化,不是序列维度!
4.4 残差连接:为什么它和LayerNorm是“最佳拍档”
残差连接解决了深层网络的梯度消失问题。但光有残差连接还不够,因为残差连接会让激活值的尺度越来越大。
你想想看,每一层都加一个残差,叠加10层后,数值范围可能膨胀到原来的10倍。这时候如果不做归一化,后面的层根本没法训练。
LayerNorm正好解决了这个问题。它把每个位置的激活值拉回到均值为0、方差为1的分布上。这样残差连接可以放心地叠加,不用担心数值爆炸。
我个人的理解是:残差连接负责“让梯度流得动”,LayerNorm负责“让数值稳得住”。两者缺一不可。
| 组件 | 作用 | 类比 |
|---|---|---|
| 残差连接 | 解决梯度消失,让信息直通 | 高速公路,不堵车 |
| LayerNorm | 稳定数值范围,防止爆炸 | 交通管制,不超速 |
4.5 实际项目中的选择建议
说了这么多理论,最后给点实用的建议:
- 小模型(6层以下):Post-LN和Pre-LN差别不大,随便选。我个人习惯用Post-LN,因为跟原始论文保持一致,方便复现。
- 大模型(12层以上):强烈建议用Pre-LN。我试过用Post-LN训练24层模型,训练了2周都没收敛,换成Pre-LN后3天就稳了。
- 微调场景:如果预训练模型用的是Post-LN,微调时不要轻易改成Pre-LN。权重分布已经适应了Post-LN的结构,改了反而可能掉点。
- 混合精度训练:LayerNorm在FP16下容易溢出,建议在LayerNorm之前加一个
torch.clamp或者用FP32计算。这个坑我踩过,模型训练到一半loss变成NaN,排查了半天才发现是LayerNorm的数值问题。
一句话总结:
Transformer选LayerNorm,不是因为BatchNorm不好,而是因为NLP任务的特性决定了LayerNorm更合适。残差连接和LayerNorm的配合,让深层Transformer的训练成为可能。如果你还在纠结用哪个,记住:NLP用LayerNorm,CV用BatchNorm,跨界用GroupNorm。
好了,这一章的内容就到这里。下一章我们会聊聊位置编码——为什么Transformer需要它,以及各种位置编码的优劣对比。