LayerNorm 原理 从特征轴到归一化梯度
沿定义、计算与实验边界阅读,分开模型原理、教学验证和实际系统效果。
章节速览7 节 · 展开选择
Transformer 入门里,LayerNorm 常与残差连接一起出现;BatchNorm则已经展示了“减均值、除标准差”的计算。两者算式相似,但统计轴不同,输入之间的依赖关系也不同。
这篇先把一组特征画成一行,再沿着前向、手算和反向走一遍。重点是说明:哪几个数共同决定统计量,哪些参数在多行之间共享,以及 epsilon 为什么不能在推导中悄悄丢掉。
先把统计轴画对
对形状为 (B,T,H) 的序列表示,若采用 LayerNorm(H),每个样本的每个 token 都沿最后 H 个特征计算自己的均值和方差。共有 B×T 组统计量;把它们展平成 R=B×T 行、每行 D=H 个数,便于逐行分析。这里 D 表示一组中的元素总数,不是张量轴的数量。
更一般地,normalized_shape=(H,W) 表示一起归一化末尾两个轴,每组有 D=H×W 个元素,而不是分别给每个轴算一次。normalized_shape 必须匹配输入的末尾形状;更改张量轴顺序后,需要重新检查它代表的含义。PyTorch 2.14 LayerNorm 官方文档规定了末尾轴、逐元素仿射参数以及分母为 D 的方差约定。
对第 r 行、第 i 个特征,定义:
1 | μ_r = (1/D) Σ_i x_ri |
每行有自己的 μ_r、v_r、q_r;γ_i、β_i 则有 D 个,跨 R 行共享。若末尾归一化形状是 (H,W),参数也具有该形状;展平只是记号与教学实现,不会把它们变成一个标量。
| 输入与约定 | 一组统计覆盖什么 | 谁会影响某个位置的统计 |
|---|---|---|
| LayerNorm,(B,T,H),归一化 H | 一个 token 的 H 个特征 | 同 token 的特征 |
| LayerNorm,(B,T,H),归一化 (T,H) | 一个样本的全部 T×H 个位置 | 同样本的其他 token 也会影响 |
| BatchNorm1d,输入 (N,C) | 一个通道的 N 个样本 | 同通道的其他样本 |
Ba、Kiros 与 Hinton 的原始论文从单个样本内的单元计算统计,并为单元设置增益和偏置;它不要求把同批其他样本混进统计。本文聚焦这些计算关系,不据此推断任意网络的训练速度或效果。
flowchart TD A[确认 normalized shape 与末尾轴] --> B[把每组特征看成一行] B --> C[各行独立计算均值与方差] C --> D[减均值 除以带 epsilon 的标准差] D --> E[每个特征使用自己的 gamma 和 beta] E --> F[跨行复用同一组参数]
一行三个数,前向与反向手算
取一行 x=[1,2,3],γ=[1,2,3],β=[0,1,−1],ε=1/3。这个 epsilon 是为了让标准差恰好等于 1 的教学取值;PyTorch 文档的默认值为 1e−5,不能把这里的手算设置当成调参建议。
均值 μ=2、方差 v=2/3,所以 q=1,z=[−1,0,1],得到 y=[−1,1,2]。设输出的上游梯度 g=[1,−1,2];核验时可以使用标量目标 L=Σ_i g_i y_i,它只检查这一层的导数。
反向先经过每特征的仿射变换,得到 h_i=γ_i g_i,即 h=[1,−2,6]。接着会用到 mean(h)=5/3 与 mean(hz)=5/3。
| i | x_i | γ_i | β_i | z_i | y_i | g_i → h_i | dL/dx_i |
|---|---|---|---|---|---|---|---|
| 1 | 1 | 1 | 0 | −1 | −1 | 1 → 1 | 1−5/3+5/3=1 |
| 2 | 2 | 2 | 1 | 0 | 1 | −1 → −2 | −2−5/3=−11/3 |
| 3 | 3 | 3 | −1 | 1 | 2 | 2 → 6 | 6−5/3−5/3=8/3 |
因此输入梯度为 [1,−11/3,8/3],三个分量之和为 0;这一行贡献 dγ=[−1,0,2]、dβ=[1,−1,2]。手机可横向查看手算表;键盘用户可先聚焦表格区域,再按方向键。
反向传播为什么要先乘各自的 gamma
以下暂时省略行下标 r,只推导一行。上游梯度为 g_i,先定义 h_i=γ_i g_i。由于不同特征的 γ_i 不同,不能把某个公共 γ 提到公式外面,也不能先平均 g,再统一乘一次 γ。
改变 x_i 既改变直接路径,也改变这一行的 μ 与 v。利用 Σ_i c_i=0,沿计算图有:
1 | dL/dv = −(q³/2) Σ_i h_i c_i |
第一行来自 q=(v+ε)^(−1/2) 的导数;均值支路中,方差对 μ 的项包含 Σ_i c_i,因此抵消为零。最后把 c_i=z_i/q 代入,可得紧凑公式。ε 始终保留在 q 和 z 中。
一行内部的输入梯度相互耦合;不同的行在这个局部算子中不共享统计,也没有跨行的输入偏导。但仿射参数跨行共享,所以必须累加每行的贡献:
1 | dL/dγ_i = Σ_r g_ri z_ri |
这里用求和还是在外部损失中再除以样本数,取决于损失定义。若上游已经来自平均损失,不能在层内重复除以 R。
flowchart LR A[每行收到上游梯度 g] --> B[逐特征乘 gamma 得到 h] B --> C[该行计算 mean h 与 mean hz] C --> D[得到该行全部输入梯度] A --> E[逐特征计算 g 乘 z 与 g] E --> F[跨行求和得到参数梯度]
epsilon 保留了什么,又改变了什么
首先,z 的均值为 0,但其方差是 v/(v+ε)。ε>0 时,不能说归一化方差总等于 1;经过非均匀的 γ、β 后,y 更不必具有零均值或单位方差。
把 J 记为 z 对 x 的雅可比矩阵,1 为全 1 向量,可将上面的导数写成:
1 | J = q [I − 11ᵀ/D − zzᵀ/D] |
J 是对称矩阵。由 Σ_i z_i=0,得到 J1=0:给同一行全部特征加常数不改变输出,因此 Σ_i dx_i=0。若中心化向量 c 非零,则:
1 | Jc = εq³ c |
证明第二个方向只需代入 Σ_i c_i²=Dv;于是 q(1−vq²)=εq³。它说明ε>0 时沿 c 的梯度通常仍然存在,不能套用 ε=0 时“梯度必与输入中心化方向正交”的结论。本例 dx·c=5/3,而非 0。
固定 ε 时,把 x 乘正数 a,通常不能得到完全相同的 z;同时把 ε 改为 a²ε 才恢复这一缩放关系。对于 v≫ε 的数据,固定 ε 的影响可能较小,但那是近似条件。
常量行也值得单独看:c=0、z=0,输出为 β,然而 dx=q[h−mean(h)] 仍可能非零。D=1 则不同:唯一特征始终减去自身,z=0,输入梯度和 dγ 都为 0,dβ 仍接收上游梯度。
自检:一行的输出等于 beta,是否就说明输入梯度全为零?
不一定。常量行在当前点输出 beta,但各特征若分别扰动,会出现非零中心化值;D>1 时 dx=q[h−mean(h)] 可能非零。D=1 时,每次扰动仍只有一个数,中心化结果恒为零,才必有零输入梯度。
训练与评估一致,不等于 padding 自动安全
LayerNorm 使用当前输入的统计,不维护 BatchNorm 那样的 running_mean、running_var;相同输入、参数与 ε 下,LayerNorm 这个算子的训练和评估计算相同。这是官方文档明确的行为,不代表整个网络切换模式后都相同;例如 Dropout 的行为会改变。
padding 是否影响有效位置,要从统计轴与后续算子判断:
- 只沿 H 归一化时,一个 padding token 是另一行,不会直接进入有效 token 的统计。
- 若沿 (T,H) 归一化,padding 会落在同一统计组中,改变有效位置的均值与方差。
- 即使只沿 H,padding 行归一化后也不保证保持零;全零行会输出 β。后续注意力、池化和损失仍需正确使用掩码。
- 若归一化组内部本身包含无效特征,标准 LayerNorm 不会自动跳过它们,需要另行定义掩码统计及对应梯度。
这些结论来自对统计轴和后续运算的直接分析。行间独立只描述 LayerNorm 本身;前面的注意力已经可以让一个 token 的表示依赖其他 token。
完整标准库 Python 实验
代码接收非空的 R×D 矩阵,每行代表一组已经展平的归一化特征;γ、β 长度为 D。它实现前向、输入梯度和跨行参数梯度,使用不可变缓存保存本次 γ 的快照。多轴张量的 reshape、掩码、自动微分、混合精度与优化器都不在这个教学实现中。
1 | from dataclasses import dataclass |
运行得到 y=[[-1,1,2]],dx 约为 [[1,−3.666667,2.666667]],dγ=[−1,0,2]、dβ=[1,−1,2],与手算一致。前向和反向均为 O(RD) 时间,输出、缓存与输入梯度均为 O(RD) 空间,参数梯度为 O(D)。实现拒绝非有限数与常见溢出,不承诺处理全部接近浮点极限的输入。
tests/verify-layernorm-article.cjs 从本文提取代码,使用独立标量目标的中心差分检查全部输入与参数导数,并以高精度、显式雅可比核对;还检查多行累加、行间独立性、平移、epsilon 缩放关系与缓存快照。这些检查验证该层计算,没有运行完整 Transformer 训练,也没有验证泛化收益。
接入模型前的边界清单
| 情形 | 要核对的内容 |
|---|---|
| normalized_shape | 与输入末尾形状一致,轴顺序和语义正确 |
| γ、β | 每组中的每个位置各一个,跨所有外部行共享 |
| v 的分母 | 使用 D,不使用 D−1;它不是估计总体方差的无偏校正 |
| ε | 位于平方根内部,影响归一化方差和尺度方向梯度 |
| D=1 | 输出为 β,输入梯度与 dγ 为 0;并非非法形状 |
| 常量行 | 输出为 β,但 D>1 时输入梯度不一定为 0 |
| 小批次 | 统计不跨批次,并不等于所有模型都无需考虑批量设置 |
| padding | 检查是否落入统计轴,以及后续注意力、池化和损失的掩码 |
| 训练与评估 | LayerNorm 局部计算一致;整个网络可能仍受 Dropout 等影响 |
| 数值差分 | 每次扰动都重新计算统计量;不要把前向缓存当成常数目标 |
自检:按行独立统计,为什么 gamma 的梯度还要跨行求和?
各行有自己的均值和方差,但它们用了同一组 gamma、beta。一个共享参数经多条路径影响损失,反向就把这些路径的贡献相加。输入梯度的行间独立性与参数梯度的跨行累加可以同时成立。
读完可以回到 BatchNorm 的批次统计与运行缓冲量比较统计轴,也可结合共享参数的反向传播,再把这些局部关系放回 Transformer 的残差块。先明确计算与依赖,再讨论具体架构和实测效果。