一个极简但极关键的技巧:不让每层"重做",而是让它在原输入上"加一点改动"。这是 Transformer 能堆几十层还训得动的钥匙。
| Topic | 残差连接(Residual / Skip Connection) |
| Objectives | 理解 output = x + Layer(x) 为什么让深层网络训得动 |
| Prerequisites | 多头注意力 ✅、梯度直觉 ✅(补充 B) |
| Outcome | 能说清残差的三个好处:梯度高速路 / 易学恒等 / 逐层微调 |
| Warning | 它不是注意力的一部分,是"包在每个子层外面"的通用技巧 |
Transformer 要堆很多层(几十甚至上百)。但朴素地"一层接一层"堆,会遇到两个坑:
残差连接就是解决这个的:一个改动小到几乎"作弊",效果却是革命性的。
普通做法:一层直接输出它的加工结果。
残差做法:把原输入 x 原封不动加回来,层只负责算"该加多少改动"。
那条把 x 直接搬到输出的线,叫 skip connection(捷径 / 跳连)。于是 Layer(x) 学的不再是"完整的新表示",而是"在 x 基础上的增量(residual,残差)"。
+x 的捷径让梯度能直接穿过(x 对 x 的梯度恒为 1,不衰减)。所以哪怕上百层,梯度也能顺着捷径畅通传到前面,不再消失。Layer(x) ≈ 0,输出就 ≈ x(原样通过)。所以加层最差也不会变差 —— 深度不再是负担。x + Layer(x),两者形状必须一样,都是 [N × d_model]。这正是为什么整条主干把 d_model 保持恒定(多头也要拼回 512)—— 不然这个"加回来"就做不成。残差是 d_model 恒定的又一个理由。
一个 Block 里有两个子层,每个都包一层残差:
这条一路被不断"加改动"的向量流,有个形象的名字叫 残差流(residual stream):从 embedding 出发,每个子层往上叠一点信息,一直流到最后。
术语约定:下文 g(x) = 普通堆叠的层(没有 +x 捷径,直接输出结果);Layer(x)/f(x) = 残差层里那个只算 delta 的子模块;x + Layer(x) = 残差层。
✅ 对。普通层产出完整新值 g(x);残差层只产出改动量,输出 = x + Layer(x),即 原值 + delta。"residual(残差)"字面就是"输出 − 输入 = delta"。
x + f(x) 数学上不就等于某个 g(x) 吗?为什么非写成 x + Layer(x),不直接叫 Layer'(x)?作为函数,两者表达能力完全相同(任何 g 都能写成 x + f,令 f = g − x)。所以你的质疑在"能表示哪些函数"层面成立。残差要解决的不是表达能力,而是"梯度下降好不好训"——差别在参数化方式(怎么写)如何重塑训练,不在函数本身。两个具体差别:
g'(x),堆 N 层 ≈ g'₁·g'₂·…·g'ₙ 连乘,普遍 <1 就趋近 0(消失)。残差因子是 1 + f'(x)——每项都有个"1"(来自 +x 捷径),连乘不塌成 0。名字可以换成 Layer'(x),但只要内部结构是"恒等 + 增量",导数就带那个救命的 1;普通 g 没有这条捷径就没有。g(x)=x(有非线性时很难);残差只需 f(x)=0(权重推向 0,trivial),且初始化时 f≈0 → 每层一开始就接近"原样通过",起点好。方向对,但一个措辞要拧准:不是"g 的输出范围更大/有一堆废可能"——两者可达的输出集合完全一样(表达能力相同,别滑回"g 能表示更多")。真正的差别是先天假设(inductive bias) + 优化起点:
| 普通 g(x) | 不带"答案≈x"的假设,也没把 x 递给你 → 从随机起点在整个空间摸索,还得自己重新学会用到 x |
| 残差 x+f(x) | 内建假设"答案≈x,只学修正 delta",并把 x 白送 → 从 x 附近起步、就近微调,梯度顺捷径回流 |
Layer(x),残差层输出什么?多出来的那条"捷径"叫什么?答完这 3 题,下一课 LayerNorm——和残差搭档的"数值稳定器"。