上一章《过拟合与正则化》让拟合和参数两股力拔河;参数压住了,数值却没人管。 第 1 层改一点点参数,第 5 层收到的数就挪了位置,每层都在追一个移动的靶子。 归一化做的事小得不可思议:把这层收到的数减去平均,再按散开的程度缩一缩。 但它让深层网络第一次真正能训起来。
一个 20 层的网络,前向传播就是 20 次"乘一乘、加一加,再过一道激活函数"。 如果每一层都让数值的尺度放大一点点——哪怕只放大 10%—— 20 层之后就是 1.1²⁰ ≈ 6.7 倍。反过来,每层缩小一点点,就会一路缩到趋近于 0。
这会带来两个后果:数值跑到
🎯 类比 像一个接力队。第 1 棒跑得快一点、慢一点,后面每一棒都必须重新调整自己的节奏,
整个队永远配合不好。
归一化做的事就是:不管第 1 棒怎么跑,交棒时都把棒子放在固定的位置上。
后面的人不用一直重学,只要专心练好自己的那一段。
左边是某一层收到的数,右边是同一批数过归一化之后的样子。 下面两个滑块:均值 μ 管这堆数的中心在哪,标准差 σ 管它们摊得多开。 (这两个词数学那节有一块专门讲,这里先按"中心"和"摊得多开"用。)
核心大图 · 拖动两个滑块,看右边怎么变
某一层的激活分布
过归一化之后
不管左边怎么挪,右边那张图都被掰成均值 0、标准差 1。
它只动整堆数的位置和宽窄,不动数之间的相对大小——归一化之后,谁比谁大、大多少,还和原来一样,
所以不同的输入不会变得分不出来。
它也没有让信息变多,只是让数值的尺度可预测。
三步合起来就是一条公式——数学那节那张卡会把它写成式子,再拆成四步画给你看。
如果只做前两步,就强行规定"每层输入必须均值 0、方差 1"。
万一某个特征"大一点才有用"呢?γ 和 β 给了网络反悔的权利。
极端情况下 γ = σ、β = μ,整个归一化被完全抵消——所以从表达能力看,加上它们不会让模型更差。
但网络通常不会把它们全抵消:抵消掉,归一化带来的训练稳定也就没了。
留着一个能反悔的旋钮,换来的正是训练的稳。
但第 ① 步藏着一个真实的麻烦:μ 和 σ 是从"这一批"数据估出来的,它本身就有误差。 批越小,这个估计越不准。
互动 · 批越小,统计量抖得越厉害
⚠️ batch = 1 时它会彻底失效 批里只有一条数据时,"这一批的均值"就是那条数据本身,标准差是 0。
除以 0 是算不出来的——所以所有实现都在分母上加一个很小的正数 ε(比如 1e-5)挡着。
但分子 x − μ 这时也正好是 0,0 除以 ε 还是 0,于是所有输出恒等于 β——
信息被完全抹掉了,而且和输入再无关系。
这就是为什么 BatchNorm 做不了在线学习(数据一条一条来、来一条学一条)、也做不了 batch size = 1 的训练。
把下面的批大小拖到 1,看左边的直方图会铺满整个区间。
训练时有"一批"数据,可以算 μ 和 σ。但推理的时候你可能只来一条数据, 算不出批次统计量。所以:
训练和推理时 BatchNorm 用的统计量不一样,PyTorch(这里用的深度学习框架)里用 model.train() 和
model.eval() 这两个开关切换。
忘了切,训练指标看着很好,一上线结果全乱——这是新手最常见的 bug 之一。
互动 · 滑动平均怎么追上一个一直在漂移的真实统计量
⚠️ 推理时错用 batch 统计量会怎样 同一个样本,单独送进去和放进一批里送进去,会得到完全不同的预测结果。
因为单独送进去时,它就变成了"这一批"的全部——归一化之后输出恒等于 β。
这就是为什么评测时必须 model.eval()。
而且这种 bug 在训练时完全看不出来,只在部署后暴露。
🎯 类比 像一个刚入职的员工。培训期间(训练)他跟着小组一起干活,
每次都用"小组的平均水平"来校准自己。
但正式上岗(推理)时他经常一个人面对客户——
这时他得用培训期间攒下的经验平均值,而不是当场看周围同事在干什么。
没切换过来的人,会在独自面对客户时手足无措。
两者的公式一模一样,唯一的区别是:在哪个方向上算均值和方差。 把数据想成一张表格——一批 6 张图、每张 8 个特征,就是 6 行 8 列: BatchNorm 竖着切,LayerNorm 横着切。
互动 · 切换看看归一化的是哪一条线
| 对比项 | BatchNorm | LayerNorm |
|---|---|---|
| 归一化方向 | 沿着批次方向:同一个特征,跨样本 | 沿着特征方向:同一个样本,跨特征 |
| 依赖批次吗 | 依赖。batch 太小会严重不稳,=1 完全失效 | 完全不依赖 |
| 训练/推理一致 | 不一致,需要滑动平均 | 完全一致 |
| 变长序列 | 很麻烦(一批句子里长短差很多,统计量会混在一起) | 天然支持 |
| 自回归生成(一个字一个字往外吐) | batch 通常就是 1,没法用 | 没问题 |
| 主要用在哪 | CNN(处理图像的网络,batch 大且尺寸固定) | Transformer(处理句子的网络)、RNN(更早的序列网络)、大模型 |
💡 为什么 Transformer(句子网络)一律用 LayerNorm 三个原因叠在一起:
① 长度不一——一批句子里每句长短差很多,BatchNorm 的统计量会被长短句搅乱;
② batch 不能太大——大模型的 batch 受
③ 推理时只是一条——生成文本是一个
LayerNorm 只看单条样本内部,天然没有这三个问题。
数学那节那张卡把归一化写成了两步:减均值(中心化)和除标准差(缩放)。 RMSNorm 要问的是:减均值这一步真的必要吗?
均方根(RMS)就是先把每个数平方、平均,再开方——它不管中心在哪,只看这堆数「能有多大」。
互动 · 同一组数据,两种算法的每一步
归一化在每一层、每一个 token(一小段文字)上都要算一遍。
少一次「把一堆数合成一个数」的计算,在 700 亿参数的大模型上就是实打实的速度提升。
而且实证结果是:效果几乎一样——按 RMSNorm 论文的说法,真正起作用的主要是缩放(re-scaling),减均值这一步本来就可以省。
LLaMA、Qwen、Mistral 系列全部用 RMSNorm,就是这个原因。
x + F(x),F 就是这一层做的事。这条"绕行"的路,让梯度可以原样往回走。
归一化层放在这条绕行路的里面还是外面,决定了这条路是不是真的通畅。
残差连接:输入绕过这一层,直接加到输出
互动 · 20 层之后,梯度还剩多少(真实的连乘)
| 对比项 | Post-Norm(原版) | Pre-Norm(现代) |
|---|---|---|
| 写法 | x ← LN( x + F(x) ) | x ← x + F( LN(x) ) |
| 残差通路 | 被归一化挡住,梯度必须穿过去 | 完全干净,一路直通到底 |
| 需要 warmup 吗 | 非常需要,否则容易炸(损失突然变成 NaN、训练崩掉;warmup:训练开头几百步,把学习率从很小慢慢调大) | 没那么敏感 |
| 谁在用 | 原版 Transformer、BERT | GPT 系列、LLaMA、几乎所有现代大模型(LLM) |
容易混淆的一点:GPT-2 从这一版起改用了 Pre-Norm(归一化挪到子层之前;子层是 Transformer 里的一次注意力或一次前馈,「Block」就是「一个子层 + 残差 + 归一化」的一整块), 同时还在整个堆叠的最后额外加了一个 LayerNorm。那是输出层的归一化,不是 Block 内部的 Post-Norm——两件事不要混(下一章《初始化与训练稳定性》会从方差的角度再对一遍)。
下一章 《初始化与训练稳定性》会从方差的角度再看一遍同一个问题,两边的结论一致:
Pre-Norm 让残差通路上没有东西挡住梯度,所以 ∂y/∂x = 1 这个恒等项能原封不动地传下去。
上面表格里那个 warmup,就是因为 Post-Norm 让梯度更难走,训练开头才需要把学习率慢慢加上去。
前面说过「归一化就是把数值减去均值、再除以标准差」。这句话只有十几个字, 但它到底在干什么,一张图就能看完——下面四步走完,这一章的公式你就全看懂了。
动画 · 一团数字,四步之后变整齐(灰色大点是上一步在哪)
卡上的 μ、σ 跟着第 2 节那两个滑块变;γ、β 跟着下面动画的那两个滑块变。
🎯 用「排队拍合影」理解这四步 一群小朋友高矮不一,站得也歪。摄影师做四件事:
① 让所有人站到同一条线上(减掉平均位置)→
② 从镜头里看他们一样高(除掉个子差异)→
③ 摄影师自己决定要不要放大(乘 γ)→
④ 自己决定往左还是往右挪一点(加 β)。
① 和 ② 是「整理」,③ 和 ④ 是「网络自己说了算」——
这正是为什么归一化不会把网络变笨:它随时可以把整理过的效果抵消掉。
把动画的四步点完,再去第 2 节拖那两个滑块。
第 2 节那个「标准差 σ」滑块管的是某一层收到的数摊得多开;这里的第 ② 步,就是把那个 σ 除掉。
拖大它,第 2 节右边那张图仍然被掰回均值 0、标准差 1。
📐 看不明白 μ 和 σ?只有三个词的事
上面整节都在用这两个词。如果你本来是懂的,跳过这一块。 如果卡在这儿,下面三句话够用了。
前面几节讲的都是 BatchNorm 和 LayerNorm。这一节把常见的归一化方法排开—— 它们的分歧大多出在同一件事上:均值方差是沿着哪个方向算的。
| 方法 | 归一化什么 | 特点 |
|---|---|---|
| BatchNorm 2015 | 同一特征,跨样本 | 让深层 CNN 第一次真正能训起来。是归一化这个方向的开山之作 |
| LayerNorm 2016 | 同样本,跨特征 | 和 batch 无关,适合序列。成了 Transformer 的标配 |
| InstanceNorm | 单样本单通道内(通道:图片的一层特征图) | 风格迁移(把一张照片画成另一张的样子)里常用,会抹掉每张图自己的对比度这类整体信息 |
| GroupNorm | 单样本,把通道分成若干组分别归一化 | 不依赖 batch,小 batch 场景的救星。扩散模型的 UNet(一种图像生成网络)大量使用 |
| RMSNorm | 只除均方根,不减均值 | 更快、更省,效果接近。LLaMA / Qwen / Mistral 都用 |
| WeightNorm | 对权重而不是激活做归一化 | 早期方法,和这里讲的(对激活做归一化)不是一路 |
| QK-Norm | 对注意力里的 Q、K 分别归一化(注意力:让每个词决定该多看哪些别的词;Q、K 是每个词发出的「问题」和「标签」) | 防止注意力分数(logits:还没压成概率的分数)爆炸,训练超大模型时能提高稳定性 |
| DeepNorm | 放大残差分支的比例 | 让 1000 层 Transformer 也能稳定训练 |
归一化是深度学习里少有的、几乎只有好处没有坏处的东西。但「几乎没有」不等于没有—— 它有几条边界,而其中最新的一条是 2025 年才出现的。
| 代价 | 说明 |
|---|---|
| BatchNorm 离不开 batch | 小 batch 不稳、batch=1 失效、变长序列麻烦、在线学习用不了、训练推理必须区分—— 五条限制全来自同一个设计选择 |
| 归一化在推理时也要算 | LayerNorm / RMSNorm 对每个 token(一小段文字)都要做一次「把一堆数合成一个数」的计算, 在长序列生成里是实打实的开销 |
| RMSNorm 在部分任务上略差 | 省掉均值中心化是有代价的,只是大多数任务上感觉不到 |
| γ、β 不能省 | 它们参数很少,但去掉会损失表达力—— 网络需要保留"拒绝归一化"的权利 |
| 它不是万能的 | 它防住的是数值挤进饱和区这一路。初始化太离谱、学习率设错—— 这些问题它救不了 |
CNN 图像分类(batch 大且定长)→ BatchNorm
Transformer / LLM → RMSNorm(或 LayerNorm),放在子层之前(Pre-Norm)
RNN / 序列模型 → LayerNorm
batch 很小 → GroupNorm 或 LayerNorm
扩散模型 → GroupNorm(UNet 里随处可见)
2025 年出现的两条,都指向同一个方向
DyT(x) = tanh(α·x),
直接换掉整个归一化层——没有均值、没有方差、也不用把一堆数合成一个数。tanh 本来就会做。这两条都还新,别当成定论。但它们说明一件事: 「归一化是必需的」这句话,至少已经不是共识了。
| 它不成立的地方 | 什么时候真的会痛 | 怎么办 |
|---|---|---|
| BatchNorm 的原始解释是错的(它说效果来自「内部协变量偏移」:每层收到的数的分布,随着前面层的参数更新一直在变) | 你在做技术决策或写文章,引用的还是那个被引用了上千次的因果解释 | 换成「让损失面变平滑、允许更大的学习率」——2018 年的对照实验把协变量偏移人为注入回去,BatchNorm 依然有效,所以那个解释靠不住 |
| 「越稳」不等于「越好」 | 你在追极限效果,而选 Pre-Norm 只是因为「大家都用它」 | 知道自己换掉了什么:Post-Norm 的正则化更强、效果通常更好,代价是要小心地做 warmup。HybridNorm 是 2025 年给的一个折中 |
| 归一化层可能不是必需的 | 你在做推理优化,而归一化每次都要把一堆数合成一个数,正好卡在长序列生成的每一步上 | 可以试试 DyT 这类逐元素替代。但它是 2025 年的结果,先在小规模上验证,别直接上生产 |
| BatchNorm 离不开 batch | 小 batch、变长序列、在线学习、或者推理时本来就是一条 | 换成 LayerNorm / GroupNorm / RMSNorm。这不是 bug,是同一个设计选择的必然后果 |
| 它救不了别的毛病 | 你的模型不收敛,而你以为「有归一化了应该没问题」 | 它只防住数值挤进饱和区这一路;初始化和学习率的问题,它管不了。先去看《初始化与训练稳定性》和《超参怎么选》 |
最常被问的两个问题,结论先放这里:BatchNorm 的减法和 RMSNorm 省掉的那次减法不是一回事——一个跨样本,一个是同样本跨特征;第一次自己选,先看数据是不是变长序列、batch 小不小,剩下的照上面速查表来。
不是一回事。BatchNorm 减的是跨样本的均值(那是它依赖 batch 的根源), RMSNorm 省掉的是同样本内跨特征的均值。前者是负担,后者基本是白送的。
先看两件事:你的数据是不是变长的序列(是 → 直接 LayerNorm/RMSNorm); batch 小不小(小到统计量开始抖,就换掉 BatchNorm)。都不是的话,上面那张速查表够用。
这一章看上去只是一个除法。但它和「正则化」用的是同一个思路: 不是禁止网络去做某件事,而是让它做那件事变得很贵。
回到第 2 节那两张直方图。把「标准差 σ」滑块拖到最右边 4.5—— 左边那堆数越摊越平,「落在饱和区的比例」从默认的 50% 涨到 60% 左右; 右边那张图却始终被掰回均值 0、标准差 1。
然后回到第 3 节,把「批大小 B」拖到 1:这一次连归一化都救不了,直方图会铺满整个区间。
两个瞬间合起来就是这张本质卡:归一化给的是「尺度可控」这个软偏好,而不是「永远正确」这个硬保证。
| 暗线 | 这一章的回答 |
|---|---|
| A 信息流动 | 形状完全不变:(B, T, d) 进、(B, T, d) 出,元素个数一个都没变
(B、T、d 就是「几条样本、每条多长、每个位置几个特征」三个方向)。
变的只是每个数的尺度。这也是这一章每张图都在问的那个问题:同一批数,你沿着哪个方向算均值和方差? BatchNorm 沿样本方向,LayerNorm 沿特征方向——公式一模一样,方向不同 |
| B 什么被牺牲了 | 计算 + 自由度。每个 token、每一层多做一次「把一堆数合成一个数」的计算; BatchNorm 还牺牲了「与 batch 无关」的能力——小 batch 不稳、batch=1 失效、 训练/推理不一致、变长序列麻烦、逐字生成时也没法用,五条限制全来自同一个设计选择 |
| C 参数账本 | 参数少得可以忽略:LayerNorm 在每个归一化点只有 2 个长度为 d 的向量(γ、β),RMSNorm 只有 1 个(γ)。
在一个 d=4096、32 层的模型里,大约是 2 × 4096 × 32 ≈ 26 万个参数——对 70 亿来说不到 0.004%。 但计算量不小:每个 token 一次求均值 + 一次求方差(RMSNorm 只求均方根)—— 都是「读进来、算一算、写回去」,所以这笔账的重点不在算了多少次,而在搬了多少数据 |
| D 跑在什么上 | 卡在读写上。归一化做的事可以概括成「读进来 → 算一个总数 → 写回去」,
读写的数据量比算术量大得多,所以它几乎总在等内存。 这也解释了 RMSNorm 为什么值钱:省掉「减均值」那一步,省的是读写而不是计算—— 少读一次就是真的快。完整的账在 《硬件与算力账本》 |
| E 它假设了什么 | 假设「尺度是一个可以独立于内容被拿掉的量」——也就是「数值大一点小一点只是尺度问题,语义没变」。 BatchNorm 还额外假设了「同一批里的样本可以互相代表」(batch 内独立同分布)。 后一个假设在变长序列和逐字生成里直接破了——这就是 LayerNorm 存在的理由 |
| F 违背了哪个直觉 | 直觉是「归一化只是为了数值好看、算得快」。 真正的答案是它改变了损失曲面的形状——让梯度更可预测、允许更大的学习率。 |
带走的那张图 · 同一张表格,两把刀
同一张表格,两把刀:竖着切是 BatchNorm,横着切是 LayerNorm。
它接住了上一章的什么:过拟合与正则化(《过拟合与正则化》) 在损失上加了一项惩罚;这一章加的是另一种约束——两者都是「不禁止,只让它变难」。
它给下一章留了什么:归一化只解决尺度。真正让几十上百层能一起训起来的另一半是 残差连接 + 初始化尺度——那就是 《初始化与训练稳定性》的主题, 也是 Transformer 能推到几千层规模的前提。
归一化 = 把每层收到的数值减去均值、除以标准差,让每层都站在同一起跑线。
它只动位置和宽窄,不动数之间的相对大小;加 γ、β 是为了保留"拒绝归一化"的权利。
BatchNorm 跨样本统计(CNN 用),LayerNorm 跨特征统计(Transformer 用)——
公式完全一样,只是方向不同。
RMSNorm 再砍掉减均值那一步,更快且效果接近,是大模型的默认选择。
放在残差外面(Post-Norm)会挡住梯度,放在里面(Pre-Norm)则一路通畅——
所以绝大多数现代 LLM 都用 Pre-Norm。
上面讲的都是「够用」的版本。想往下挖,这里有三个入口—— 它们不是必修内容,是给想再往前走一步的读者准备的。
左边是上面讲过的逻辑,右边是它在 PyTorch 里的样子——每一行都对着。
正文为了讲清楚,把两个地方简化了。真要动手实现时,这两条才是完整的。
第 1 条 · 训练时统计量是「边走边记」的,不是重算的
正文第 4 节说「推理时用滑动平均」,这条就是那个滑动平均的更新式。
m 是动量(PyTorch 默认 0.1):每次只用一小部分当前 batch 的统计量去修正长期记忆。
这也解释了为什么 batch 很小时 BatchNorm 会坏:μbatch 本身就抖,而这个式子会让抖动
一节一节存进长期记忆里,推理时取出来的滑动平均值也跟着抖。
第 2 条 · 同一个公式,两种走法
这两行长得几乎一样——差别只在「μ 和 σ² 从哪来」。
训练时从当前这批数据里算(所以和 batch 绑死);推理时从记下来的长期值里取(所以能脱离 batch)。
BatchNorm 全部的麻烦,都来自这两行不是同一个式子。
LayerNorm / RMSNorm 没这个问题——它们两行完全一样。