用程序员最熟的「版本管理 + 代码风格规范」来类比,不用公式也能彻底搞懂。
一、什么是残差?
一句话定义
残差 = 期望输出 - 原始输入 = H(x) - x
在残差连接中,网络不是直接学习完整的输出,而是学习输出相对于输入的增量——也就是残差。
大白话类比:git diff
你写了一段代码(输入 x),想把它改得更好(期望输出 H(x))。
残差 = 期望输出 - 原始代码 = H(x) - x
这就好比你在 Git 里做了一次修改,git diff显示的就是你改了多少行、加了什么新内容,而不是把整个文件重新打印一遍。
网络只需要学会“这段代码需要改多少”,而不是从零生成整段代码。
为什么叫“残”差?
因为它是处理完之后剩下的增量——原始信息保留,只学习“新增的那部分”。
二、梯度消失是什么意思?
一句话定义
神经网络层数一多,最前面的层“学不到东西”了——梯度(更新信号)越传越弱,最后几乎消失。
大白话类比:没有版本管理的代码仓库
想象一个项目没有 Git 历史,所有人都在同一个文件上直接修改:
- 第一个人改了几行
- 第二个人又改了几行
- ……
- 第100个人打开文件时,已经看不出最初的样子了,也不知道每一步改了什么
梯度消失就是这个道理:
- 反向传播时,梯度(纠错信号)从最后一层往第一层传
- 每经过一层,梯度都要乘以激活函数的导数(通常小于1)和权重矩阵的缩放因子
- 层数一多,乘到第10层、第20层,梯度就变成 0.0000001 了
- 最前面的层几乎收不到更新信号,学不动了
对Transformer的影响
Transformer 动不动就几十上百层,如果没有残差连接,前面的层根本学不到东西,训练不起来。
三、为什么要做残差连接?
核心目的:解决梯度消失,让深层网络能训练起来
大白话类比:Git 版本管理
没有残差的时候,就像没有做版本管理,每次在原副本上直接修改:
原始代码 → 第1次修改 → 第2次修改 → ... → 第100次修改 → 最终文件每一次修改都可能覆盖、丢失信息,到最后面目全非,无法回溯。
有了残差连接,就像每完成一次需求,提交一次 commit,保留增量修改记录:
原始代码 → commit1 → commit2 → ... → commit100 ↑ ↑ ↑ 原始+增量1 原始+增量2 原始+增量100不管你怎么改,原始版本始终可回溯,每次只记录“改了什么”。
三大好处
- 梯度传得动:反向传播时,梯度可以通过捷径(恒等映射的导数为1)直接传回去,不会乘来乘去变没了
- 学习更轻松:不用学完整的输出,只学“在输入基础上改多少”(学残差比学完整映射简单多了)
- 信息不丢失:原始信息一路保留到底,不会越处理越丢
更形象的比喻
- 没有残差:像在同一个 Word 文档上反复修改,越改越乱,想找回最初的版本也找不到了
- 有残差:每次修改都另存为一个新文件(保留原始文件),并在新文件上标注“相对于原文件的改动”。任何时候都能回溯到最初版本,也能清楚看到每一步改了哪里
四、怎么做残差连接?
公式(就一行)
输出 = 输入 + 神经网络层(输入) y = x + F(x)伪代码
defresidual_block(x,layer):# 1. 正常经过一层神经网络output=layer(x)# 2. 把原始输入直接加回去(残差捷径)returnx+output# 核心就这一步Transformer里的实际样子
每一个注意力层、每一个前馈层,外面都包了一层残差连接:
输入 x ↓ ┌─────────────┐ │ 多头注意力 │ ← F(x) └─────────────┘ ↓ + ← 残差连接(直接加原始 x) ↓ 层归一化 ↓ ┌─────────────┐ │ 前馈网络 │ ← F(x) └─────────────┘ ↓ + ← 又一个残差连接 ↓ 层归一化 ↓ 输出注意点
维度必须一样才能加。如果维度不一样,就加一个线性投影把 x 转成相同维度再加。
五、什么是层归一化(LayerNorm)?
一句话定义
把每一层输出的特征,按每个样本自身拉到一个稳定的范围内,让数值稳定、训练更快。
大白话类比:统一代码风格规范
不同程序员写的代码风格各异:
- 张三喜欢 2 空格缩进
- 李四喜欢 Tab 缩进
- 王五喜欢大括号换行
直接把这些代码拼在一起,后续维护的人会疯掉。
层归一化就像一个代码格式化工具(如 Prettier):
- 算出当前代码的平均缩进、命名风格(均值 mean)
- 算出风格的离散程度(标准差 std)
- 把所有代码统一成标准的 2 空格、驼峰命名(归一化到均值0、方差1)
- 再允许每个团队保留一点点自己的偏好(可学习的缩放和平移参数)
为什么需要它?
- 数值稳定:不会某一层输出特别大、某一层特别小(代码风格统一)
- 训练更快:梯度更稳定,不容易梯度爆炸/消失(后续程序员不用适应各种奇葩风格)
- 收敛更好:模型更容易找到最优解(团队协作更顺畅)
六、Post-LN 和 Pre-LN 分别是什么?有什么差异?
这是 Transformer 里最容易搞混的细节,其实就是层归一化放的位置不一样。
1. Post-LN(后归一化,原始Transformer用的)
先做残差相加,再做归一化
x → 注意力层 → + → LayerNorm → 输出 └── x ──┘ ↑ 残差连接顺序:F(x) → 加x → 归一化
2. Pre-LN(前归一化,现在主流都用这个)
先做归一化,再进层,最后残差相加
x → LayerNorm → 注意力层 → + → 输出 └── x ──┘ ↑ 残差连接顺序:归一化 → F(x) → 加x
3. 核心差异对比表
| 维度 | Post-LN(原始版) | Pre-LN(主流版) |
|---|---|---|
| 归一化位置 | 残差相加之后 | 进层之前 |
| 训练难度 | 难,深层容易不稳定 | 易,深层也很稳 |
| 梯度流 | 梯度可能爆炸/消失 | 梯度更平滑 |
| 能不能直接堆深层 | 不行,20层以上就难训 | 可以,100层也能训 |
| 性能上限 | 在浅层或精心调参时可能略优 | 在深层和大规模训练中更可靠,最终效果基本持平 |
| 现在谁用 | 很少用了 | GPT、BERT、几乎所有新模型 |
大白话总结
- Post-LN:像先写完代码再格式化——如果代码本来就乱,格式化后可能还是乱,而且深层时容易崩
- Pre-LN:像先定好代码规范再写代码——每写一段都先格式化,保证风格统一,怎么写都不乱
七、残差连接 + 层归一化在 Transformer 中起到什么作用?
这俩是 Transformer 的「稳定器双雄」,缺一不可。
1. 残差连接:解决“能不能训”的问题
- 让梯度能传回去(恒等映射导数为1),深层网络学得动
- 保留原始信息,不会越处理越丢
- 降低学习难度,只学增量
2. 层归一化:解决“稳不稳”的问题
- 数值范围统一,不会忽大忽小
- 梯度更稳定,不容易爆炸/消失
- 训练速度更快,收敛更好
3. 加在一起:1+1>2
残差负责梯度能传回去,LayerNorm负责数值不跑偏,两者配合,Transformer 才能堆到几十上百层还能稳定训练。
类比
- 残差连接= Git 版本管理,每次 commit 都知道原版本和增量,保证历史可回溯、梯度可传导
- 层归一化= ESLint + Prettier,统一代码风格,保证后续协作不出乱子
八、有替代方案吗?
有,但各有优劣,目前残差+LayerNorm 还是绝对主流。
1. 残差连接的替代方案
| 方案 | 原理 | 优缺点 |
|---|---|---|
| DenseNet(密集连接) | 每一层都和前面所有层相连 | 信息更全,但参数多、计算量大 |
| Highway Network | 加门控,控制多少信息走捷径 | 更灵活,但多了参数,训练更复杂 |
| ReZero | 残差前乘一个可学习的权重,初始为0 | 训练更稳,初始化更友好 |
2. 层归一化的替代方案
| 方案 | 原理 | 优缺点 |
|---|---|---|
| RMSNorm | 只算均方根,不算均值,更简单更快 | 效果与 LayerNorm 相当或略优,计算速度更快,新模型常用 |
| BatchNorm | 按batch维度归一化 | NLP里不好用,因为序列长度不一样 |
| GroupNorm | 按通道分组归一化 | 视觉里常用,NLP用得少 |
3. 最新进展
- RMSNorm正在逐步替代 LayerNorm,更简单更快,效果相当(LLaMA、Qwen 都在用)
- 并行残差(Parallel Transformer):注意力和前馈层并行计算后相加,可提升训练速度,但效果可能与串行有差异,尚未成为主流
- 无归一化:一些新研究尝试去掉归一化,靠初始化和特殊结构保证稳定,但还没成主流
九、一句话总结
残差连接是 Git 版本管理,保证每一次 commit 都知道原版本和增量,让梯度传得回去、信息不丢失;层归一化是 ESLint + Prettier,统一代码风格规范,让每一层输出都在合理范围。两者配合,Transformer 才能又深又稳地训起来。