为什么大模型能堆到几十层还训得动?答案藏在一个最不起眼的动作里
一个几十层、上百层堆起来的大模型,凭什么不会"算着算着就崩"——数值要么爆炸成天文数字,要么衰减到接近 0? 答案不在什么高深结构里,而在一个简单到几乎会被忽略的动作上:
每过一层,就把数据"减掉均值、除以标准差",重新拉回"中心 0、波动 1"的标准状态。
这个动作在统计里叫标准化(standardization),在大模型里叫 LayerNorm,是每个 Transformer block 的标配零件——也是最少被人讲清楚的那一个。今天每一个大模型(LLaMA、Qwen、GPT、DeepSeek)能又深又稳地训下去,靠的正是它。
这篇文章不堆公式、不查正态分布表,全部用能跑起来的 PyTorch 代码, 把"减均值、除标准差"从一个朴素的统计动作,一路接到撑起深度网络的稳定器。 并且顺手回答四个问题:
- 数据在网络里传着传着,为什么会越来越飘?
- "减均值、除标准差"这套动作,到底救了什么?
- 大模型为什么用 LayerNorm,而不是你可能听过的 BatchNorm?
- 既然都归一化到"均值 0、方差 1"了,为什么又要乘一个 γ、加一个 β 把它变回去?
0. 一句话主线
如果只能留一句话,那就是这句:
标准化(归一化)= 把一组数"减掉均值、除以标准差",拉回"中心在 0、波动为 1"的标准状态。
这个动作最常见的用途,是把量纲不同的数据放到同一把尺子上比; 而大模型用它做另一件更关键的事——把每一层算出来的、量级越来越野的词向量,重新拉回一个稳定区间, 好让下一层能安安稳稳地接着算。从头到尾,就是在做"减均值、除标准差"这一件事。
import torch
scores = torch.tensor([88.0, 92.0, 79.0, 95.0, 61.0]) # 一组原始数值
z = (scores - scores.mean()) / scores.std(unbiased=False) # 减均值,除标准差
print("标准化后:", z.round(decimals=2))
print("新均值≈", z.mean().round(decimals=2), " 新标准差≈", z.std(unbiased=False).round(decimals=2))
# 新均值≈0,新标准差≈1 —— 无论原来什么量级,都被拉回同一个标准状态
记住这个画面:不管原来是几十还是几千,减均值、除标准差之后,一律变成"中心在 0、波动为 1"。 下面所有东西都挂在"把分布拉回标准状态"这一件事上。
1. 标准化:把量纲不同的数,拉到同一把尺子上
标准化最直观的用途,是比较量纲、量级都不同的数据。它干的事只有一句:消除量纲和量级,只留下"相对位置"。
import torch
subject_a = torch.tensor([110.0, 95.0, 120.0, 88.0]) # A 项,量级偏大
subject_b = torch.tensor([70.0, 55.0, 82.0, 60.0]) # B 项,量级偏小
def zscore(x):
return (x - x.mean()) / x.std(unbiased=False)
print("A 项标准化:", zscore(subject_a).round(decimals=2))
print("B 项标准化:", zscore(subject_b).round(decimals=2))
# 两项被拉到同一把尺子:谁在自己那组里更靠前,一眼就能比
两组数原始量级不同,没法直接比。 但减均值、除标准差之后,两组都变成"我在本组里领先/落后多少个标准差",量纲消失了,只剩相对位置。 这套"抹掉绝对量级、只留相对结构"的动作,正是大模型每一层都需要的——下面看它为什么需要。
2. 标准化在几何上做了什么:居中 + 收窄
别把标准化当成一个公式,把它当成对整个分布做的两个动作:先平移到 0,再缩放到 1。
import torch
x = torch.randn(1000) * 20 + 100 # 均值≈100、标准差≈20 的一堆数
print("原始: 均值≈", x.mean().round(), " 标准差≈", x.std(unbiased=False).round())
x_centered = x - x.mean() # 动作一:整体平移,把中心搬到 0
x_norm = x_centered / x.std(unbiased=False) # 动作二:整体缩放,把波动压到 1
print("标准化: 均值≈", x_norm.mean().round(), " 标准差≈", x_norm.std(unbiased=False).round())
# 均值≈0,标准差≈1:钟形曲线还是那个钟形,只是被搬到原点、宽度收成了 1
分布的"形状"没变,只是被搬到了原点、宽度收成了 1。 想象一条钟形曲线: 标准化不改变它是"钟形"这个事实,只是把它挪到中间、把胖瘦调成标准宽度。 这个"居中 + 收窄"的动作,就是我们接下来要塞进神经网络每一层里的东西。
🤔 疑惑点一:数据在网络里传着传着,为什么会"越来越飘"?归一化救了什么?
每一层都是"乘一个矩阵、过一个激活"。矩阵乘法会把数值的量级一层层放大或缩小——就像麦克风对着音箱,前一层的输出当后一层的输入,一点点偏差被反复放大,几十层之后数值要么爆炸成天文数字(梯度爆炸),要么衰减到接近 0(梯度消失),网络就训不动了。归一化的作用,就是在每一层之后强行把数值"拉回标准状态",掐断这条放大链。
先亲眼看看"不归一化会飘成什么样":
import torch
torch.manual_seed(0)
x = torch.randn(1, 64)
for layer in range(1, 51):
W = torch.randn(64, 64) * 0.6 # 一个普通的线性层
x = torch.relu(x @ W) # 乘矩阵 + 激活,模拟往深处传
if layer % 10 == 0:
print(f"第 {layer:2d} 层后:数值绝对值均值 ≈ {x.abs().mean():.4f}")
# 你会看到数值一路飘走(膨胀或塌缩),越深越离谱
再看看"每层之后归一化一下"的效果:
import torch
import torch.nn.functional as F
torch.manual_seed(0)
x = torch.randn(1, 64)
for layer in range(1, 51):
W = torch.randn(64, 64) * 0.6
x = torch.relu(x @ W)
x = (x - x.mean(-1, keepdim=True)) / (x.std(-1, keepdim=True) + 1e-5) # 每层归一化
if layer % 10 == 0:
print(f"第 {layer:2d} 层后:数值绝对值均值 ≈ {x.abs().mean():.4f}")
# 加了归一化,数值稳稳待在一个区间,50 层也不飘
第一段代码里数值一路失控,第二段里稳如老狗。 这就是为什么 Transformer 能把几十上百层堆起来——每个 block 里都夹了一次"减均值、除标准差", 把上一层的输出重新拉回标准状态,让下一层永远面对一个"规矩"的输入。 那个不起眼的"减均值、除标准差",就是这条深度网络能训得动的命脉。
3. LayerNorm:对"每个词向量自己"做一次标准化
现在把这套动作正式接到大模型上。Transformer 里的 LayerNorm 干的事, 和前面那个标准化一字不差——对每个词的那一行向量,减掉这行的均值、除以这行的标准差。
import torch
import torch.nn as nn
torch.manual_seed(0)
x = torch.randn(2, 4) # 2 个词,每个词是一个 4 维向量
# —— 手动版:就是"减均值、除标准差" ——
mean = x.mean(dim=-1, keepdim=True)
std = x.var(dim=-1, keepdim=True, unbiased=False).sqrt()
manual = (x - mean) / (std + 1e-5)
# —— PyTorch 官方版 ——
ln = nn.LayerNorm(4, elementwise_affine=False) # 先关掉 γ/β,只做纯标准化
official = ln(x)
print("手动 vs 官方 完全一致:", torch.allclose(manual, official, atol=1e-5)) # True
nn.LayerNorm 拆开看,内核就是那两步动作,一点没多。
(补一句现代进展:LLaMA 这些新模型用的是 RMSNorm——把"减均值"那步省了,只保留"除以均方根",
更省算力、效果几乎不变。但骨架还是同一件事:把一行数拉回标准量级。)
🤔 疑惑点二:既然都归一化到"均值 0、方差 1"了,为什么又要乘 γ、加 β 把它变回去?
因为"强行统一成均值 0、方差 1"虽然稳,却也抹掉了模型本来可能想保留的信息——万一某一层确实需要一个更大的波动、或者一个非 0 的中心呢?所以 LayerNorm 在标准化之后,又乘一个可学习的 γ(缩放)、加一个可学习的 β(平移)。这两个参数让模型自己去学"这一层到底要多强的归一化":需要就保持标准状态,不需要就用 γ、β 把它调回来。归一化负责"稳",γ/β 负责"留住表达能力"。
import torch
import torch.nn as nn
torch.manual_seed(0)
x = torch.randn(2, 4)
ln = nn.LayerNorm(4) # 默认带 γ、β
print("初始 γ:", ln.weight.data) # 全 1
print("初始 β:", ln.bias.data) # 全 0
# 初始时 γ=1、β=0,等于"只做纯标准化";训练中它们会被学成模型需要的值
# 手动复现完整版:标准化后再 * γ + β
mean = x.mean(-1, keepdim=True)
std = x.var(-1, keepdim=True, unbiased=False).sqrt()
manual = (x - mean) / (std + 1e-5) * ln.weight + ln.bias
print("和官方完全一致:", torch.allclose(manual, ln(x), atol=1e-5)) # True
归一化把数值拉回标准状态(保证稳定),γ 和 β 再给模型一个"反悔的自由"(保住表达力)。 一稳一活,这就是 LayerNorm 完整的样子——而它的地基,仍然是那句"减均值、除标准差"。
🎬 动手:把归一化塞进一个最小 Transformer block,看它稳住全场
Transformer 的每个 block 都是 x = x + 归一化后的子层输出(残差 + 归一化)。
写一个最小版,直观感受"有没有归一化"的差别:
import torch
import torch.nn as nn
torch.manual_seed(0)
d = 16
ln = nn.LayerNorm(d)
linear = nn.Linear(d, d)
x = torch.randn(4, d) * 5 # 故意给一个量级偏大的输入
print("输入量级(绝对值均值):", x.abs().mean().round(decimals=2))
# 无归一化:直接过子层
no_norm = x + torch.relu(linear(x))
print("无归一化 输出量级:", no_norm.abs().mean().round(decimals=2))
# 有归一化(pre-norm,现代 Transformer 的标准做法):先归一化再进子层
pre_norm = x + torch.relu(linear(ln(x)))
print("有归一化 输出量级:", pre_norm.abs().mean().round(decimals=2))
# 有归一化的那条,子层始终面对"标准状态"的输入,量级被牢牢摁住
归一化让每个子层永远面对一个"规矩"的输入,量级不会随着层数滚雪球。 把它乘上几十层,就是大模型能又深又稳的秘密。
仓库里的动画脚本把这个"归一化如何驯服分布"的过程画了出来:
python layernorm_visualization.py
左边是一堆数值往深层网络里传:没有归一化时,你会看到直方图一层层被拉宽、 甚至甩出屏幕(爆炸)或缩成一根针(消失); 右边打开归一化后,每一层的直方图都被"居中 + 收窄"重新拽回标准钟形—— 同样的网络,同样的深度,一个飘到失控,一个稳如水平线。 最后你会直观看到:深度网络能训得动,靠的就是每层之间这一次不起眼的"减均值、除标准差"。
缝合:把所有画面接起来
回到开头,现在每个概念都有了画面和代码出处:
| 概念 | 朴素理解 | 这篇文章怎么看(画面) | 在大模型里是什么 |
|---|---|---|---|
| 均值 μ | 一组数的平均 | 分布的"中心在哪"(第 0、2 节) | LayerNorm 里要减掉的那个中心 |
| 标准差 σ | 数据有多分散 | 分布的"波动有多大"(第 2 节) | LayerNorm 里要除掉的那个尺度 |
标准化 (x-μ)/σ |
把数据拉到同一把尺子 | 把分布拉回"中心 0、波动 1"(第 1 节) | 每一层对每个词向量做的归一化 |
| 消除量纲 | 让不同来源的数能比 | 抹掉绝对量级、只留相对结构 | 掐断深层网络的数值放大链(疑惑点一) |
| 缩放平移 γ/β | 标准化本身没有 | 归一化后再给模型"反悔的自由" | LayerNorm 的可学习参数(疑惑点二) |
三句话总结这篇文章:
标准化的灵魂是"减均值、除标准差"——把任何一组数拉回"中心 0、波动 1"的标准状态(第 1、2 节); 深层网络离不开它,因为数值一层层传会越滚越飘,每层归一化一次就掐断了这条放大链(疑惑点一); 于是 LayerNorm 就是对每个词向量做的标准化,再配一对可学习的 γ、β,一稳一活,撑起了每个 Transformer block(第 3 节、疑惑点二)。
标准化这个动作,简单到你可能从没多看它一眼—— 但正是这个"减均值、除标准差",成了大模型每一层的稳定器, 是几十层网络能堆起来、还训得动的命脉。 现在把上面每段代码跑一遍,你就摸到了 Transformer 里最沉默、也最不可或缺的那块零件。
备注(选题/标题): 这篇刻意不走"高中知识"钩子——高中壳的标题已经密集出现(sin/cos、数列、求根公式、向量……), 除 sin/cos 外反响平平,继续堆会稀释稀缺性、给老读者疲劳感。所以这里改用"机制悬念"式开头 (对标《为什么 GPT 淘汰了 RNN》那种问题钩子)。备选标题: - 《所有人都在讲注意力,却没人讲清楚是什么让 Transformer 真正稳住》 - 《LayerNorm:每个 Transformer 都有、却最少被讲清的那个零件》 与《向量→注意力》《对数→交叉熵》承接,共同拼出 Transformer block 的几块地基: "相关度(注意力)/损失(交叉熵)/稳定(归一化)/位置(sin/cos)",可互相引流。 配套动画脚本:
scripts/layernorm_visualization.py。