optimizer优化器总结

SGD, Adagrad, RMSProp, Adam

Posted by weber on February 21, 2020

优化器总结

深度学习的优化目标都是最小化目标函数,方式为bp算法,深度学习框架如tensorflow,pytorch一般通过封装的优化器实现这一过程,本文详细总结了现有的优化器。

目录:

[toc]

0. 问题定义

  • 待优化参数:$\theta$,目标函数:$f(\theta)$,学习率:$\eta$

  • 第t个时刻参数的梯度:$g_t=\bigtriangledown f(\theta_t)$

tips:倒三角符号常用来表示梯度,也会用于表示散度和旋度,这里表示梯度。

  • 根据历史梯度计算的一阶动量:$m_t=\phi(g_1,g_2,…,g_t)$

tips:动量概念来自物理类比,假设是单位质量,速度向量(累积到t时刻的梯度)可以看作动量。

  • 根据历史梯度计算的二阶动量:$V_t=\psi(g_1^2,g_2^2,…,g_t^2)$

  • 优化通式:$\theta_t=\theta_{t-1}-q(g_t)$

其中,$q(g_t)$是梯度的函数。每个优化器都是根据 梯度大小和学习率大小 迈向最优解,区别就是在于如何定义q函数。

1. SGD

现在的随机梯度下降(Stochastic Gradient Descent,SGD)一般都是指 mini-batch gradient descent。SGD的学习率 $\eta$ 不变,每次计算一个batch内的数据的梯度并进行更新。

SGD的梯度下降过程,类似于一个小球从山坡上滚下,它的前进方向只与当前山坡的最大倾斜方向一致(最大负梯度方向),每一个时刻的初速度都为0。

1.1 公式

SGD的 $q(g_t)=\eta*g_t$

###1.2 缺点

  1. 选择合适的learning rate比较困难。太小的话收敛速度慢,太大的话会使loss在最小值附近震荡。
  2. SGD容易收敛到局部最优,在某些情况下可能被困在鞍点。
  3. learning rate对于所有的参数(特征)是相同的。如果我们的数据稀疏,我们希望对于不常出现的特征进行大一点的更新,对于常出现的特征更新的慢一些。

1.3 改进1 - Momentum - 减缓震荡

Momentum 即动量,该改进是在SGD的基础上考虑了动量的因素,通过动量值来克服SGD易于震荡的缺点: 这里 $q(g_t)=\mathbb{momentum} * m_{t-1} + \eta * g_{t}$,$m_{t-1}$(动量)是指数衰减的梯度平均。

通常momentum会取一个较大的值,如0.9。

理解指数衰减的加权平均:

假设momentum = 0.9,$\eta$ = 0.01,有:

$m_5=0.9m_4+0.01g_5$

$m_4=0.9m_3+0.01g_4$

$m_3=0.9m_2+0.01g_3$

$m_2=0.9m_1+0.01g_2$

则:$m_5=0.01*(g_5+0.9g_5+0.9^2g_3+0.9^3g_2+0.9^4g_1)$

可以看到第5次更新的梯度包含了前4次的梯度,且是一个指数衰减的过程。

SGD Momentum的梯度下降过程,类似于一个小球从山坡上滚下,它的前进方向由当前山坡的最大倾斜方向与之前的下降方向共同决定,小球具有初速度(动量)。

img

1.3 改进2 - NAG - 防止局部最优

Nesterov Accelerated Gradient 在 Momentum 的基础上进行改进,用于解决SGD容易陷入局部最优的缺点。

NAG的思想先用一个形象的事例来说明:当你走到一个盆地,四周都是略高的小山,你找不到下坡的方向,就只能呆这里了,但其实外面的世界很广阔,还有更低的地方可以去;所以如果你在进入盆地之前,看的更远一些,不光从当前的位置去审视下坡,还从未来的角度去审视下坡,就可以不进入这个盆地,从而下降的更多。

在Momentum算法中,由于历史累积动量的权值很大,t时刻的下降方向主要由累积动量$ m_{t-1}$决定。那么在计算t时刻的梯度时,可以多看一步,看看下一时刻的梯度(即如果跟着累积动量走了一步以后的梯度),用下一时刻的梯度与历史累积动量结合。

所以NAG计算梯度时,不是在当前位置,而是未来的位置上:

关于 $V_t$, 后面会在Adagrad解释

图示可以很好的解释momentum和NAG的区别:

image-20200524162228752

1.4 tf.keras.optimizer

tf.keras.optimizers.SGD(
    learning_rate=0.01, momentum=0.0, nesterov=False, name='SGD', **kwargs
)

目前为止,我们可以做到,在更新梯度时顺应 loss function 的梯度来调整速度,并且对 SGD 进行加速

我们还希望可以根据参数的重要性而对不同的参数进行不同程度的更新。

2. Adagrad

自适应梯度算法(Adaptive Gradient Algorithm,Adagrad)通过以往的梯度自适应更新学习率 $\eta$,使得不同的 $\theta_i$ 具有不同的学习率:常出现的特征学习率低,不常出现的特征学习率高,比SGD更容易处理稀疏数据,解决SGD的第三个问题。

从Adagrad开始,引入了二阶动量,也意味着“自适应率”优化算法的到来。我们希望根据参数的更新频率来对参数的学习率进行调整,如何去度量一个参数的历史更新频率呢?就是二阶动量(历史梯度平方和): 所以学习率变成了:$\eta_t = \frac{\eta}{\sqrt{V_t}}$,一般为了避免分母为零,会加一个较小的平滑项epsilon,即:$\eta_t = \frac{\eta}{\sqrt{V_t}+\epsilon}$

2.1 公式

需要说明的是,网上关于epsilon在根号下还是根号外写法不一致,虽然一个epsilon为极小值影响不大,但我还是去看了keras的源码,所有优化器的epsilon都是在外部的,而且因为是要个极小值,在外部也更加合理。

2.2 缺点

分母是个单调递增的值,会不断积累,学习率就会变得非常小,最终趋于0,提前停止学习。

2.3 tf.keras.optimizer

tf.keras.optimizers.Adagrad(
    learning_rate=0.001, initial_accumulator_value=0.1, epsilon=1e-07,
    name='Adagrad', **kwargs
)
//initial_accumulator_value是二阶动量的初始值

3. RMSProp/Adadelta

Adagrad中二阶动量不断增加,学习率单调递减的策略过于激进,因此RMSProp提出了一个时间窗口策略:

不累加全部的历史梯度,而只关注于过去一段时间的下降梯度。至于窗口,可以使用指数加权平均的方式,从而避免二阶动量的持续累积。

3.1 公式

RMSProp修改后的二阶动量的计算公式:

通常,$\rho=0.9$。

如果RMSProp想添加momentum的话,式为: 这样,既考虑了一阶动量来缓解loss震荡,又考虑了二阶动量来自适应学习率。

3.2 tf.keras.optimizer

tf.keras.optimizers.RMSprop(
    learning_rate=0.001, rho=0.9, momentum=0.0, epsilon=1e-07, centered=False,
    name='RMSprop', **kwargs
)
//centered:(旧版本keras无此参数表示是否通过估计的梯度的方差对梯度进行归一化
// 				  归一化有助于训练但消化更多计算和内存

4. Adam

Adam(Adaptive Moment Estimation)相当于 RMSProp + Momentum,是前述方法的集大成者,将一阶动量和二阶动量都进行了使用。Adam的一个改进点是Adam对一阶矩估计和二阶矩估计进行了校正,使其近似为对期望的无偏估计。

校正方式为:$\hat m_t = \frac{m_t}{1-\beta_1^t}$

偏差修正解释:

以一阶动量为例,初始化 $m_0=0$,$m_1=0.9m_0+0,1g_1=0.1g_1$,远小于期望值$g_1$;$m_2=0.9m_1+0.1g_2=0.09g_1+0.1g_2$,远小于期望值$0.9g_1+0.1g_2$。在迭代初期,这个偏差很大,当t足够大时,$\hat m_t=m_t$。

所以对一阶动量做修正:当t=1时,$\frac{m_1}{1-\beta_1}=\frac{m_1}{0.1}$,此时$m_1=\theta_1$,去除了偏差;当t很大的时候,$\beta^t$接近于0,偏差修正几乎没有作用。

同理,也可以对二阶动量进行修正:$\hat V_t=\frac{V_t}{1-\beta_2^t}$

4.1 公式

通常,$\beta_1=0.9,\beta_2=0.999,\epsilon=1e-7$

4.2 tf.keras.optimizer

tf.keras.optimizers.Adam(
    learning_rate=0.001, beta_1=0.9, beta_2=0.999, epsilon=1e-07, amsgrad=False,
    name='Adam', **kwargs
)
//amsgrad是ICLR2018最佳论文指出Adam的收敛问题并进行改进但普遍实践效果不佳一般不使用不再赘述

5. AdamW

Adam有很多的优点,但是在很多数据集上的最好效果还是用SGD with Momentum细调出来的。可见Adam的泛化性并不如SGD with Momentum。AdamW指出一个重要原因就是Adam中L2正则化项并不像在SGD中那么有效。

L2正则化:通过在损失中添加l2正则项来降低训练的过拟合

SGD中的l2正则化是通过weight_decay实现,推导如下:

假设$L=J(\hat y,y)+\frac{wd\sum_{k}(\theta_k^2)}{2}$,即最终损失由损失项+所有参数的正则项两部分组成,wd为l2超参数,则可以得到$g_t = f_t(\theta)+ wd\theta_{t-1}$,所以权重更新为: 可以看到,每次更新时,正则项损失等价于减去参数的一小部分,因此称为衰减。

但在Adam这种自适应学习率优化器中,L2正则和Weight Decay并不等价

在这里插入图片描述

5.2 tfa.optimizers.AdamW

//tfa是基于tensorflow的额外功能模块
tfa.optimizers.AdamW(
    weight_decay: Union[FloatTensorLike, Callable],
    learning_rate: Union[FloatTensorLike, Callable] = 0.001,
    beta_1: Union[FloatTensorLike, Callable] = 0.9,
    beta_2: Union[FloatTensorLike, Callable] = 0.999,
    epsilon: FloatTensorLike = 1e-07,
    amsgrad: bool = False,
    name: str = 'AdamW',
    **kwargs
)

6 . 其他

思考:调研优化器是因为在增量学习中遇到Adagrad比Adam效果好的情况,最终对Adam在稀疏数据上的表现进行调研,魔改了基于Adam的LazyAdam和mAdam进行实验,最终取得了更好的效果。

总结:对于Adam的改进优化器有很多,主要改进集中在两方面,一是对Adam存在的问题,二是对Adam的收敛进行加速。建议学习(常用)的有:

  • RAdam
  • LazyAdam
  • mAdam
  • Adamax,NAdam,…
  • 以及 FTRL(常用)

表面:调一个参数/换一个优化器(调参)

背后:对于所有优化器的原理,推导,源码有清晰的认识和实践,才能够根据实验结果,从容的更换参数,达到更好的效果。