残差与层归一化
想把注意力层堆得更深,要先解决训练不稳定的问题:复用残差连接,再加上新引入的 LayerNorm。
残差连接:给深层网络留一条捷径
想让注意力真正强大,要把注意力层反复堆叠(原始 Transformer 6 层,现代大模型可达几十层)。但「深」的代价在 CNN 中已经领教过: 网络一深就难训,梯度通路不畅、深层退化都会拖累训练。当年 ResNet 用 残差连接 化解了它:让每一层只学习「残差」(相对输入的变化量),并给梯度留一条直通的捷径。
同样的方案,Transformer 直接复用。每个子层都包成残差形式:
这条公式里藏着两条路:一条让 x 真的「经过」子层做变换,另一条把原始 x 原样绕过去。下图把它们分开画出来。
可以是一个多头注意力(也可以是之后会见到的其他子层)。 即使某一层暂时没学到有用的东西,输入 x 也能原样通过,深层堆叠不再以「训不动」为代价。
残差之外,还需要归一化
残差解决了梯度通路,但深层堆叠还有另一个麻烦:层层变换之间, 数值分布会不断漂移 , 这一层输出偏大、下一层输入就偏大,越往深处越失控,训练忽快忽慢。 把每层的输入「拉回标准范围」的手段,就是归一化。
CNN 中我们用的是 BatchNorm :对一批样本的同一通道(连同所有空间位置)统计均值和方差。 但把它搬到文本上会遇到麻烦。
换个方向归一:LayerNorm
先把坐标系定下来:一个 batch 的文本在模型里是一个 的三维张量,每个样本是一句话,每句有若干位置,每个位置是一个 维特征向量。BatchNorm 沿「跨样本」方向统计,问题正出在这个方向上。
层归一化(Layer Normalization,LayerNorm) 换了一个统计方向: 不再跨样本统计,而是对 单个样本、单个位置 的 维特征向量,算自己的均值 和方差 ,归一化后再用两个可学习参数恢复表达能力:
- :该位置特征向量自己的均值与方差
- :一个很小的数,防止除零
- :可学习的缩放与偏移。归一化把均值、方差固定到了 0 和 1, 这两个参数让模型在需要时把尺度调回来
每个词自己归一自己 ,不必管 batch 里有谁、句子多长, padding 的问题自然消失,这正适合文本。
同样一份数据,BatchNorm 与 LayerNorm 算的是其中完全不同的一批格子。下面把这个 的张量摊平成并排的三张表, 每张表对应一个样本 ,表的每一列是一个位置的 维特征向量,每一行是同一个特征通道。切换两种模式,看清各自把哪些格子凑在一起算均值和方差。
用 nn.LayerNorm 验证它的行为:
标准组合:Add & Norm
残差和 LayerNorm 在 Transformer 里总是成对出现,组成一个标准模块,论文图里叫 Add & Norm :
Add 是残差相加,Norm 是层归一化。原论文还会在子层输出上加一个 Dropout(随机置零防过拟合,之前课程已学)。把它整理成数据流,就是输入 x 依次流过下面这几步。图里每一步,正对应那行代码里的一层包裹:
每一层包裹都对得上:最里层 sublayer(x) 算子层变换,外面一层 dropout(...) 随机置零,再 x + ... 把残差的 x 加回来,最外层 norm(...) 做 LayerNorm。之后凡是搭建 Transformer 的层,每个子层外面都套这一句,组装的时候会反复见到它。
这种「先相加、再归一」的顺序称为 Post-LN,是原论文的做法;后来的大模型多改用 Pre-LN:只对进入子层的那一支先做归一化,残差直连不经过归一化,训练更稳定。两者思想一致,这里仍按原论文的 Post-LN 讲解。
还差最后一步
目前已经具备:多头注意力(词与词之间交换信息)、位置编码(把顺序信息带入模型)、Add & Norm(支撑深层堆叠、让训练更稳定)。
之后的《编码器》会补上这一步,并把它们组合成完整的编码器。