优化器

Momentum

模拟石头滚下山的状态。在平坦的函数平面上因为有加速度的存在,能够快速通过平坦区域。

width=400

optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9)

Nesterov Accelerated Gradient

width=400

在计算新的梯度时,考虑到已经加入的动量。

optimizer = keras.optimizers.SGD(lr=0.001, momentum=0.9, nesterov=True)

AdaGrad

width=400

梯度小的方向,大幅更新。梯度大的方向,小幅度更新。

上图中,水平方向较长,较平坦,梯度较小。竖直方向,梯度较大。常规的梯度下降法,会走蓝色路径。而 AdaGrad 会走橙黄色路径。

但是 AdaGrad 不断地对梯度值进行累加,最终导致上面式子中 s 的值过大,更新越来越慢。

RMSProp

width=400

RMSProp 修复了 AdaGrad 的问题,通过引入一个衰退系数,让 s 仅仅累加最近的梯度。

optimizer = keras.optimizers.RMSprop(lr=0.001, rho=0.9)

Adam and Nadam

width=400

Adam 综合了 RMSProp 和 Momentum。3 4 两个式子中 t 代表的是迭代次数,当迭代次数较小的时候,m 和 s 的值能够被放大,当迭代次数增大是分母也就很接近 1 了,m 和 s 的值就不会在被放大了。这是为了在迭代的初始时,m 和 s 的值为 0,通过这两个式子,可以对初试阶段进行加速。

optimizer = keras.optimizers.Adam(lr=0.001, beta_1=0.9, beta_2=0.999)

Nadam 是在 Adam 中加入了 Nesterov Accelerated Gradient 中的思想。

梯度消失 / 梯度爆炸

误差梯度在反向传播的过程中,变得越来越小,最后近乎消失,这导致网络的前面的层对应的参数得不到更新。相反,梯度也可能变得越来越大,在反向传播的过程中,梯度越变越大,最终模型无法收敛。

梯度消失的原因主要是使用 sigmoid 作为激活函数导致的,sigmoid 函数当输入很大或很小时,其梯度都接近于 0。

Relu 激活函数的问题在于,一旦某个 unit 输出小于 0,那么它之后就只会输出 0,而且梯度也会是 0,即 ReLU 也可能出现梯度消失的问题,此 unit 的权重将得不到更新。这个问题称为 Dead ReLUs。

梯度爆炸常常出现在循环神经网络中。为啥

Batch Normalization

使用 ReLU 及其变种,加上合适的参数初始化策略,在训练的初期可以很好地消除梯度消失/爆炸的问题,但不能保证在整个训练过程中都不出现梯度消失/爆炸的问题。Batch Normalization 对输入的整个 batch 的数据做标准化,可以持续减缓梯度消失/爆炸的问题。

Batch Normalization 需要调整的参数不多,momentum 用于计算动态调整的均值,它的值应该接近于 1。样本集越大,或者 batch-size 越小时,momentum 应该越接近于 1。

如果在输入层之后紧接一个 batch normalization 层,对数据做标准化操作就可以不用显式地完成了。

梯度裁剪

梯度裁剪是限制梯度的大小不超过某个阈值。在 RNN 中梯度裁剪尤其重要,因为 RNN 常常出现梯度爆炸的问题。

optimizer = keras.optimizers.SGD(clipvalue=1.0)
model.compile(loss="mse", optimizer=optimizer)

在 keras 中设置梯度裁剪尤其简单,以上代码将限制梯度的绝对值小于 1.0。对梯度的某个分量进行裁剪,会导致梯度方向的改变,比如原梯度为 [100, 1], 裁剪后变为 [1, 1],这极大地改变了梯度方向。要想保证梯度方向不变,可以对梯度的 L2 范数做限制,即限制梯度向量的模长。下面的设置保证梯度的模长不大于 1,否则各个分量都进行缩减,保证梯度方向不变。

optimizer = keras.optimizers.SGD(clipnorm=1.0)