梯度消失与梯度爆炸
分析深层网络反向传播中梯度衰减或发散的现象与缓解方法。
"加深"反而变差的奇怪现象
上节课结束时我们留了一个问题:把一种网络从 20 层 加深到 56 层 , 训练损失反而 升高 。 这不是过拟合—— 如果是过拟合,训练损失应当比浅网络 更低 , 只是测试损失上升。 但这里发生的是 连训练集都难以拟合 。
下面这张图把这个实验画了出来。这里用的是 普通堆叠的深层网络 —— 层与层直接相接、没有任何跨层连接的常规结构; 横轴为训练迭代次数,纵轴为分类误差,对比 20 层与 56 层两个网络在训练集、测试集上的表现。
网络变深,能表达的函数变多,理论上应当至少不弱于浅网络 ——只要让多出来的几十层"什么都不做"(学成 恒等映射 ), 就能复现浅网络的表现。 但实际训练时连这一点都做不到。
这一 退化现象 的成因其实比较复杂:即便对网络做了规范化、 反向传播里的梯度数值看上去也并不离谱, 足够深的普通堆叠网络依然难以优化。 其中, 层数加深时梯度在反向传播中是否稳定 , 是这类优化困难里最早被注意到、也最容易讲清楚的一条线索。 本节就从这条线索切入——先看看梯度在层与层之间是怎么传递的。
反向传播视角:层与层之间的"连乘"
MLP 章节我们学过:反向传播本质上是 链式法则 —— 损失对某层参数的梯度,可以拆成"上一层传下来的梯度 × 该层的局部偏导"。
换一种说法: 从输出层往前传,每经过一层,梯度都要乘上"该层的局部偏导矩阵" 。 这个矩阵在数学上叫做 雅可比矩阵 (Jacobian)—— 知道这个名字就行,本节不展开。
把链式法则展开看
假设有一个 4 层 MLP,输出 , 损失为 。 损失对第 1 层参数 的梯度可以写成:
重点是中间这一串 层间偏导矩阵的连乘 : 越靠近输入的层,连乘的次数越多 。 最浅的 要从输出层一路乘到底(N 层网络乘 N − 1 次),最靠近输出的层却几乎不经过连乘。
严格来说每一项都是矩阵,矩阵之间并不能直接比"大小"。 为了先建立直觉,我们把每一层的雅可比矩阵 近似成一个标量 —— 用它对梯度的整体 放大 / 缩小倍数 来代表它 (可以理解为矩阵范数或最大奇异值的量级),下文称作该层的 "乘数" 。 这样一来, 连乘的结果就完全取决于每一层"乘数"的大小——这正是问题的根源。
两种极端
激活函数是"连乘"中的一个关键因素
刚才的连乘中,每一层的"乘数"由 权重矩阵 W 与 激活函数的导数 共同决定。 权重矩阵的事情先放一放——附录里的 初始化 负责给权重一个合适的初始尺度, 下节的 BatchNorm 则从另一个角度(规范化中间层的激活值)来稳住连乘,两者管的并不是同一件事。 本节先看 激活函数 对梯度的贡献。
上行为三种激活函数本身,下行为对应导数,横轴均为 。 Sigmoid 与 Tanh 的导数图中, 橙色区为饱和区 ,此处导数趋近 0,连乘后梯度迅速消失; ReLU 导数图中 绿色区为正半轴 ,导数恒为 1,不衰减信号。
影响梯度的全部因素
把刚才的几条线索拼起来——以下因素共同决定了"梯度在深层网络中是否稳定":
- 网络深度 :连乘项数 N,越深问题越严重;
- 激活函数 :Sigmoid / Tanh 在饱和区导数接近 0,ReLU 系列要好得多;
- 参数初始化 :初值过大 → 偏向爆炸,初值过小 → 偏向消失;
- 中间层规范化 :中间层激活值分布的均值 / 方差是否合理,决定了"乘数"的整体尺度;
- 残差连接 :直接给梯度一条" 不经过连乘 "的旁路。
这五条不是相互独立的——它们都在 影响"连乘的乘数"或"绕过连乘" 。 理解这一点,对应到具体的缓解方法就会很自然。
缓解方法概览(不展开)
以下方法都将在后续课程中具体讲解,本节只先建立 "为什么需要它们" 的全局视角:
除了上面这些 结构性 的方法,工程上还有一招更直接的"治标"手段—— 梯度裁剪 (gradient clipping): 把反向传播得到的梯度按范数 截断到一个阈值以内 ,专门用来对付梯度爆炸。 CNN 训练里用得不多,但在后续的 RNN / Transformer 章节里几乎是默认操作,到时会再正式介绍。
附录:常见参数初始化方法
以下两种是工业界最常用的初始化方法。 本节正文不要求记忆公式 ,附录仅作参考。
Xavier 初始化(Glorot, 2010)
适用于 Sigmoid / Tanh 。 从均匀分布或正态分布中采样权重,方差为:
其中 、 分别为该层的输入维度和输出维度。 核心思想 :让前向激活值的方差在层间保持稳定,反向梯度的方差也大致稳定。
He 初始化(He et al., 2015)
适用于 ReLU 及其变体 。 ReLU 把负半轴的输入全部置零——对一个均值为 0 的输入分布而言,相当于丢掉了大约一半, 于是这一层输出的方差只剩输入方差的 一半 左右。 为了让信号逐层传播时方差不衰减,就要把权重的方差 放大一倍 来补偿:
- Xavier: nn.init.xavier_uniform_ / nn.init.xavier_normal_
- He: nn.init.kaiming_uniform_ / nn.init.kaiming_normal_
下一站:让中间层"稳一稳"
激活函数和初始化都已经在本系列的前面章节出现过。 本章 CNN 真正要重点讨论的两个缓解工具—— BatchNorm 与 残差连接 —— 将在接下来的两节里展开。