在机器学习训练过程中,如果梯度/权重变得过大,甚至变成 nan, 或者 loss 过大、异常跳跃,甚至变成 nan, 便出现了所谓的梯度爆炸问题. 梯度爆炸问题是指在训练过程中梯度范数的大幅度增加,从而导致网络无法从训练数据中学习,梯度下降无法执行,学不到最优解.

梯度爆炸的识别:

准确度上不去,

  1. 输出 loss 值, 看 loss 是否过大、有异常跳跃情况
  2. 输出梯度的范数, 看是否呈指数增长
  3. 调试看权重/梯度的大小

Solution to exploding gradients

梯度爆炸的基本解决方法有 3 种:

  1. L1 Regularization

  2. L2 Regularization

    L2 Regularization 相当于对网络中的损失函数应用 weight decay

  3. Gradient clipping

对于模型权重系数 求解是通过最小化目标函数实现的。正则化方法通过在 Loss 函数中加惩罚项来防止 overfitting。L1 正则化和 L2 正则化,二者分别相当于在 Loss 函数中增加权重的 L1 范数和 L2 范数。

\[ \begin{aligned} &l_1: \Omega(w)=\|w\|_1=\sum_i\left|w_i\right| \\ &l_2: \Omega(w)=\|w\|_2^2=\sum_i w_i^2 \end{aligned} \]

加了之后的权重优化公式如下:

\[ \begin{aligned} \mathbf{w}^* &=\arg \min _{\mathbf{w}} \sum_j\left(t\left(\mathbf{x}_j\right)-\sum_i w_i h_i\left(\mathbf{x}_j\right)\right)^2+\lambda \sum_{i=1}^k\left|w_i\right| \\ \mathbf{w}^* &=\arg \min _{\mathbf{w}} \sum_j\left(t\left(\mathbf{x}_j\right)-\sum_i w_i h_i\left(\mathbf{x}_j\right)\right)^2+\lambda \sum_{i=1}^k w_i^2 \end{aligned} \]

两个正则项区别:

L1 norm:对输入特征进行过滤(一部分特征保留,一部分特征移除),会把不重要的特征直接置零,产生稀疏性效果

L2 norm:

  1. 控制特征值的范围,使其不要过大,产生平滑效果
  2. L2 计算方便,而 L1 在特别是非稀疏向量上的计算效率就很低;
  3. L2 Norm 对大数的惩罚更大,对 outlier 更敏感,因为使用 L2 Norm 求出来的解是比较均匀的,而 L1 Norm 常常产生稀疏解。

实际应用过程中,L1 norm 几乎没有比 L2 norm 表现好的时候,优先使用 L2 norm 是比较好的选择。

Gradient clipping

而对权重/梯度进行约束裁剪,避免模型越过最优点,是解决梯度爆炸的有效方法之一.

梯度裁剪是通过在网络反向传播过程中,将梯度裁剪到一个阈值, 通过裁剪的梯度更新权重,实现对权重更新的缩放,从而降低溢出的可能。

梯度裁剪主要分为两种: Clipping-by-value, Clipping-by-norm

Gradient clipping-by-value

按照值裁剪: 定义 a minimum clip value 和 a maximum clip value, 将超出这个范围的梯度, 裁剪为对应阈值.(‖gradient‖是梯度的范数)

伪代码:

1
2
3
gradient ← ∂C/∂W
if ‖gradient‖ ≥ max_threshold or ‖gradient‖ ≤ min_threshold then
gradient = threshold (accordingly)

Gradient clipping-by-norm

clipping-by-norm 的思想类似于 clipping-by-value。不同之处在于, 我们通过将梯度的单位向量与阈值相乘来裁剪梯度。因为 g/‖g‖ 是一个单位向量, 在重新缩放之后, 使得梯度的 L2 norm 小于等于预设的 clip_norm.

伪代码:

1
2
3
4
5
g ← ∂C/∂W
if ‖g‖ ≥ threshold then
g = threshold * g/‖g‖
% or
% g /= max(1, ‖g‖/threshold)

深度学习框架

通常在 backward 得到梯度之后,step() 更新之前,使用梯度剪裁, 继而进行网络更新的过程。

tensorflow

在 tf1.0 中:

1
2
3
4
5
6
7
8
9
# Compute gradients.
gradients = tape.gradient(loss, trainable_variables)

# two methods, use one of them is enough.
# Clip-by-value on all trainable gradients
gradients = [(tf.clip_by_value(grad, clip_value_min=-1.0,clip_value_max=1.0)) for grad in gradients]

# clip-by-norm
gradients = [(tf.clip_by_norm(grad, clip_norm=2.0)) for grad in gradients]

在 tf2 中:

1
2
from tensorflow.keras import optimizers
sgd = optimizers.SGD(lr=0.01, clipvalue=0.5)

clipnorm : float or None. If set, clips gradients to a maximum norm. clipvalue: float or None. If set, clips gradients to a maximum value.

PyTorch

因为 PyTorch 是将梯度存储在参数中,所以这里直接将模型参数传到裁剪函数中.

1
2
3
4
5
6
7
8
loss.backward()

# Gradient Norm Clipping
#nn.utils.clip_grad_norm_(model.parameters(), max_norm=2.0, norm_type=2)
#Gradient Value Clipping
nn.utils.clip_grad_value_(model.parameters(), clip_value=1.0)

optimizer.step()

weight constraint

更加直接地,还可以约束权重的大小.

torch.clamp 可实现 tensor 一定范围约束

ealy stopping 缓解过拟合

early stopping,每个神经元激活函数在不同数值区间的性能是不同的,值较小时为线性区,适当增大后为非线性区,过度增大则为饱合区(梯度消失)。初始化时,神经元一般工作在线性区(拟合能力有限),训练时间增大时,部分值会增大进入非线性区(拟合能力提高),但是训练时间过大时,就会进入饱合区,神经元就“死掉”。所以应该在适当时间内就 stopping 训练。

推荐文章

A Gentle Introduction to Weight Constraints in Deep Learning

CS231n Convolutional Neural Networks for Visual Recognition