训练深度神经网络经常伴随着复杂的优化难题。随着梯度在多层网络中反向传播,其幅值可能发生显著变化,导致严重的训练难题。由此产生两个主要问题:梯度爆炸(梯度值变得过大)和梯度消失(梯度值变得无限小)。理解并缓解这些问题对于成功训练深度模型,特别是循环神经网络(RNN),非常重要。
深度反向传播的风险
反向传播通过链式法则计算梯度,逐层相乘导数。在一个有 L L L 层的深度网络中,损失 J J J 对第一层参数 θ 1 \theta_1 θ 1 的梯度涉及 L L L 个雅可比矩阵(或在更简单情况下的标量)的乘积:
∂ J ∂ θ 1 ∝ ∂ h L ∂ h L − 1 ∂ h L − 1 ∂ h L − 2 … ∂ h 2 ∂ h 1 ∂ h 1 ∂ θ 1 \frac{\partial J}{\partial \theta_1} \propto \frac{\partial h_L}{\partial h_{L-1}} \frac{\partial h_{L-1}}{\partial h_{L-2}} \dots \frac{\partial h_2}{\partial h_1} \frac{\partial h_1}{\partial \theta_1} ∂ θ 1 ∂ J ∝ ∂ h L − 1 ∂ h L ∂ h L − 2 ∂ h L − 1 … ∂ h 1 ∂ h 2 ∂ θ 1 ∂ h 1
这里,h i h_i h i 代表第 i i i 层的激活值。如果此乘积中的项(与权重和激活函数导数相关)持续大于1,则总梯度幅值会呈指数增长,导致梯度爆炸 。反之,如果这些项持续小于1,则梯度幅值会呈指数缩小,导致梯度消失 。
梯度消失
当梯度在反向传播过程中变得极其小时,靠近输入的较早层的权重更新会变得微不足道。这会使这些层无法有效学习有意义的表示。
原因: 这个问题在sigmoid和tanh等激活函数中尤为普遍,这些函数在较大的正或负输入时会饱和(导数接近零)。如果激活值经常落入这些饱和区域,梯度信号在反向传播时会迅速减弱。不佳的权重初始化也可能是一个因素。
影响: 导致网络较深部分的训练收敛极慢或完全停滞。在RNN中,这会妨碍模型获取序列数据中的长距离依赖,因为较早输入的效应会随时间消失。
虽然ReLU激活函数、残差连接以及细致初始化(在其他地方讨论)等方法是应对梯度消失的主要手段,但它们并非万无一失。
梯度爆炸
梯度爆炸表现为梯度值极其巨大。当这些大梯度用于参数更新步骤时,可能导致权重发生剧烈变化,从而可能抵消之前的学习效果,或导致数值溢出(表现为NaN或Inf损失值)。
原因: 网络中存在较大的权重值,或激活函数导数可能超过1。这在RNN中尤为常见,因为相同的权重矩阵在不同时间步长上重复应用。
影响: 训练变得不稳定。损失可能剧烈波动或突然急剧上升,从而阻碍收敛。优化算法可能大幅度越过最小值。
梯度流示意图。在反向传播过程中(红色路径),梯度通过局部导数相乘计算。重复相乘可能导致最终梯度(例如 ∂J/∂θ1)变得极其大或小。
梯度裁剪:控制梯度爆炸
梯度消失问题需要通过模型架构或初始化方案来解决,而梯度爆炸则常可直接使用梯度裁剪 技术来处理。核心思路很简单:如果在反向传播过程中,梯度的整体幅值(范数)或单个值超过预设的阈值,则对其进行重新缩放或限制,使其保持在规定范围内。这能防止单个巨大梯度导致灾难性的更新。
按范数裁剪
这是梯度裁剪最常见的方式。如果整个梯度向量 g \mathbf{g} g (包含所有参数 ∂ J ∂ θ \frac{\partial J}{\partial \theta} ∂ θ ∂ J 的梯度) 的L2范数 ∥ g ∥ = ∑ i g i 2 \|\mathbf{g}\| = \sqrt{\sum_i g_i^2} ∥ g ∥ = ∑ i g i 2 超过阈值 c c c ,则对其进行重新缩放。
其规则如下:
如果 ∥ g ∥ > c 则 g ← c ∥ g ∥ g \text{如果 } \|\mathbf{g}\| > c \text{ 则 } \mathbf{g} \leftarrow \frac{c}{\|\mathbf{g}\|} \mathbf{g} 如果 ∥ g ∥ > c 则 g ← ∥ g ∥ c g
机制: 如果范数过大,梯度向量会按比例缩小,使其新范数恰好等于阈值 c c c 。
优点: 它保留了梯度更新的方向 ,仅限制其幅值 。这通常是更优选的做法,因为方向常包含关于优化路径的重要信息。
阈值 (c c c ): 这是一个需要调整的超参数。常用值范围在1.0到10.0之间,但最佳值取决于模型和数据。它应足够大,以不干扰正常训练,但又足够小,以防止爆炸。在训练过程中监控梯度范数有助于设定一个合适的值。
按值裁剪
另一种方法是将每个单独的梯度分量 g i g_i g i 裁剪到特定范围 [ − c , c ] [-c, c] [ − c , c ] 内。
按元素应用的规则如下:
g i ← max ( − c , min ( g i , c ) ) g_i \leftarrow \max(-c, \min(g_i, c)) g i ← max ( − c , min ( g i , c ))
机制: 任何小于 − c -c − c 的梯度分量都会被设为 − c -c − c ,任何大于 c c c 的分量都会被设为 c c c 。
缺点: 如果裁剪了不同的分量,此方法可能会改变整体梯度向量的方向 。例如,如果 g = [ 10 , 0.1 ] \mathbf{g} = [10, 0.1] g = [ 10 , 0.1 ] 且 c = 1 c=1 c = 1 ,按值裁剪会得到 [ 1 , 0.1 ] [1, 0.1] [ 1 , 0.1 ] ,而按范数裁剪则大约得到 [ 0.995 , 0.00995 ] [0.995, 0.00995] [ 0.995 , 0.00995 ] 。因为它改变了更新方向,所以在现代深度学习实践中,按值裁剪的使用频率低于按范数裁剪,尽管它可能更易于实现。
实施方法
梯度裁剪通常在计算完一批(或小批量)中所有参数的梯度之后 ,但在优化器(如Adam或SGD)使用这些梯度更新权重之前 进行。大多数深度学习框架提供简单的函数来实施梯度裁剪(通常是按范数裁剪)。
训练迭代过程中的梯度L2范数。不进行裁剪时(蓝色虚线),偶尔会出现尖峰(梯度爆炸)。当按范数在5.0的阈值下裁剪时(红色实线),这些尖峰被限制,防止了不稳定,同时不影响较小的梯度。
总结
梯度爆炸和消失是训练深度网络时的重要障碍,源于反向传播的乘法特性。
梯度爆炸: 导致不稳定和发散。主要通过梯度裁剪 (通常按范数裁剪)来解决,它在优化器步骤之前限制梯度的最大幅值。
梯度消失: 导致早期层学习缓慢或停滞。主要通过细致的权重初始化 、使用非饱和激活函数 (如ReLU)、归一化层 (如批量归一化)以及架构改进 (如残差连接或LSTMs/GRUs)来解决。
梯度裁剪是一个实用的工具,特别是在RNN中,可确保训练稳定性。虽然它不能解决梯度爆炸的根本原因,但能有效防止其破坏优化过程。理解这两种现象及其各自的解决办法是训练复杂深度学习模型所需技能的一部分。