常见优化器
深度学习中的常用框架,无论是PyTorch还是TensorFlow,都封装了很多优化器。那么各优化器之间有什么优点和缺点呢。下面我们就回顾一下主流的优化算法。
1. 前言
当前大部分的优化算法,其本质都是梯度下降(Gradient Descent),只是不同的算法,对梯度下降进行了不同的优化。那么什么是梯度呢,梯度就是一个函数对其参数求一介偏导数。梯度的特性就是,函数在该点处沿着梯度的方向变化最快。因此梯度下降算法被用于求无约束凸函数的最小值。
假设目标函数
J
(
θ
)
J(\theta)
J(θ),梯度下降算法流程如下:
- 计算目标函数关于参数 θ \theta θ的梯度: g t = ▽ θ t J ( θ ) g_t = \bigtriangledown_{\theta_t}J(\theta) gt=▽θtJ(θ)
- 根据历史梯度计算一介动量和二阶动量: m t = ϕ ( g 1 , g 2 , . . . g t ) , V t = ψ ( g 1 , g 2 , . . . g t ) m_t = \phi(g_1, g_2, ... g_t), V_t = \psi(g_1, g_2, ... g_t) mt=ϕ(g1,g2,...gt),Vt=ψ(g1,g2,...gt)
- 计算当前时刻的下降梯度: ▽ t = η × m t V t \bigtriangledown_t = \eta \times \frac{m_t}{\sqrt{V_t}} ▽t=η×Vtmt
- 根据下降梯度,更新 θ \theta θ: θ t + 1 = θ t − ▽ t \theta_{t+1} = \theta_t - \bigtriangledown_t θt+1=θt−▽t
2. Gradient Descent
GD算法是最传统的梯度下降算法,他没有动量的概念,也就是说
m
t
=
g
t
,
V
t
=
1
m_t = g_t, V_t = 1
mt=gt,Vt=1。参数更新方式为:
θ
t
+
1
=
θ
t
−
η
×
g
t
\theta_{t+1} = \theta_t - \eta \times g_t
θt+1=θt−η×gt。
沿着梯度的防线不断减小模型参数,从而最小化目标函数。如下图所示:
但是梯度下降也有一些不足:
- 训练速度慢,每次迭代都需要遍历所有样本,会使训练过程十分缓慢。
- 容易陷入局部最优,有些点梯度为0,导致参数不再发生变化,但不一定是目标函数的最优值(如鞍点)。
- 随着梯度的变小,参数更新很慢。
3. Batch Gradient Descent (BGD)
为了解决GD算法每输入一个样本就要更新一次参数,从而导致的效率低下的问题。BGD做了一些改进,不再是对每个样本都进行参数更新,而是对整体样本进行参数更新,假设现在有n各样本。BGD算法如下:
θ
t
+
1
=
θ
t
−
η
×
1
n
×
∑
i
=
0
n
▽
θ
t
J
(
θ
,
x
i
,
y
i
)
\theta_{t+1} = \theta_t - \eta \times \frac{1}{n} \times \sum_{i=0}^n \bigtriangledown_{\theta_t}J(\theta, x_i, y_i)
θt+1=θt−η×n1×i=0∑n▽θtJ(θ,xi,yi)
BGD的核心就是先算整体样本的梯度均值,在根据这个梯度均值进行参数更新。这样会大大加快参数更新速度,但还有更好的方法。
4. Stochastic Gradient Descent(SGD)
随机梯度下降,不再是用整体梯度的均值进行跟新了,而是从训练数据中选取一个样本进行参数更新:
θ
t
+
1
=
θ
t
−
η
×
▽
θ
t
J
(
θ
,
x
i
,
y
i
)
\theta_{t+1} = \theta_t - \eta \times\bigtriangledown_{\theta_t}J(\theta, x_i, y_i)
θt+1=θt−η×▽θtJ(θ,xi,yi)
从上边公式看跟GD算法一样,他们的主要区别是,SGD是从训练数据中随机选一个样本,而GD算法是所有训练数据都参与计算。SGD由于每次参数更新只需要一个样本,所以参数更新很快,但由于单个样本存在误差,不能很好代表整体数据,可能会导致梯度下降方向不是整体的最优方向,结果就是梯度下降的波动很大。
SGD的优点就是能更快的收敛,虽然波动很大,会走很多弯路,但一般总能收敛,而且速度要快得多。
但它依然没有解决局部最优的问题。
5. Mini-batch Gradient Descent(MBGD)
于是人们想到了一种办法,就是BGD和SGD的折中,选择一个mini_batch,batch_size为m:
θ
t
+
1
=
θ
t
−
η
×
1
m
×
∑
i
=
x
x
+
m
−
1
▽
θ
t
J
(
θ
,
x
i
,
y
i
)
\theta_{t+1} = \theta_t - \eta \times \frac{1}{m} \times \sum_{i=x}^{x+m-1}\bigtriangledown_{\theta_t}J(\theta, x_i, y_i)
θt+1=θt−η×m1×i=x∑x+m−1▽θtJ(θ,xi,yi)
MBGD即保证了训练速度,又保证了最优收敛的准确率。
但是以上所有优化算法都存在一个问题,就是learning rate,如果选的太小会导致收敛很慢,太大会让loss function在极小值处来回震荡,错过极小值。
6. Momentum
momentum的核心思想,参数更新时在一定程度上保留之前的更新方向,同时又用当前batch的梯度微调最终的更新方向。也就是通过积累之前的动量来加速当前的梯度。从这个算法开始我们就要引入动量的概念了。其中动量为:
m
t
+
1
=
β
1
×
m
t
+
(
1
−
β
1
)
×
g
t
m_{t+1} = \beta_1 \times m_t + (1-\beta_1) \times g_t
mt+1=β1×mt+(1−β1)×gt
其中
β
1
\beta_1
β1的经验值为0.9。参数更新公式为:
θ
t
+
1
=
θ
t
−
m
t
+
1
\theta_{t+1} = \theta_t - m_{t+1}
θt+1=θt−mt+1
从以上公式中可以看出,t时刻下降的方向不仅跟当前点的梯度有关,而且跟之前积累的梯度相关。该方法可以缓解梯度波动较大的问题,加速收敛。
7. Nesterov Accelerated Gradient
NAG算法可以说是之前momentum算法的变种,他在梯度更新时做了一个矫正,具体做法是在当前的梯度上添加上上一时刻的动量
β
1
m
t
\beta_1m_t
β1mt。公式如下:
m
t
+
1
=
β
1
m
t
+
(
1
−
β
1
)
×
▽
θ
t
J
(
θ
t
−
β
1
m
t
)
θ
t
+
1
=
θ
t
−
m
t
+
1
m_{t+1} = \beta_1m_t + (1-\beta_1)\times\bigtriangledown_{\theta_t}J(\theta_t-\beta_1m_t) \\ \theta_{t+1} = \theta_t - m_{t+1}
mt+1=β1mt+(1−β1)×▽θtJ(θt−β1mt)θt+1=θt−mt+1
加上nesterov后,梯度在经过大的跳跃之后,会对下一步的梯度计算进行矫正(
θ
t
−
β
1
m
t
\theta_t - \beta_1m_t
θt−β1mt)。
8. Adagrad
Adaptive Gradient(自适应梯度),从这之后就会介绍一些自适应学习率的优化方法。AdaGrad其实是对学习率进行了一个约束,对于经常更新的参数,我们已经积累了足够的关于他的信息,不希望被单个样本影响太大,所以希望学习速率慢一些;对于不经常更新的参数,我们了解的信息太少,希望能从每个偶然出现的样本中学习到更多的信息,因此希望学习速率高一些。在该方法中开始使用二介动量,标志着自适应学习率时代的到来。
那么二介动量是个啥呢,他可以用来衡量历史更新频率,定义是迄今为止所有梯度值的平方和:
V
t
=
∑
i
=
1
t
g
t
2
V_t = \sum_{i=1}^{t}g_t^2
Vt=∑i=1tgt2,其中
g
t
g_t
gt为t时刻的梯度。根据前言中的公式(
m
t
=
1
m_t=1
mt=1):
V
t
=
∑
i
=
1
t
g
t
2
V_t = \sum_{i=1}^{t}g_t^2
Vt=i=1∑tgt2
参数更新公式如下:
θ
t
+
1
=
θ
t
−
η
g
t
V
t
+
ϵ
\theta_{t+1} = \theta_t - \eta\frac{g_t}{\sqrt{V_t + \epsilon}}
θt+1=θt−ηVt+ϵgt
在梯度下降的基础上,对梯度增加了分母:梯度平方累积和的平方根。频繁更新的梯度,则累积的分母项逐渐偏大,那么更新的步长(stepsize)相对就会变小,而稀疏的梯度,则导致累积的分母项中对应值比较小,那么更新的步长则相对比较大。
然而这种优化方法仍然需要定义超参数
η
\eta
η,而且随着训练的增加会使参数更新量趋近0,导致训练提前结束,无法学习。
9. Adadelta
Adagrad的方式,参数更新量调整的方式过于激进,因此可以考虑调整二介动量的计算方式,使其变得平缓:不使用全部的历史梯度,而是使用过去一段时间窗口下的梯度,并且也不直接存储这些梯度值,仅仅是计算对应的平均值(滑动平均),从而避免二介动量持续累积,导致训练提前结束的问题出现。
V
t
=
β
2
V
t
−
1
+
(
1
−
β
2
)
(
▽
θ
t
J
(
θ
t
)
)
2
V_t = \beta_2 V_{t-1} + (1-\beta_2)(\bigtriangledown_{\theta_t}J(\theta_t))^2
Vt=β2Vt−1+(1−β2)(▽θtJ(θt))2
θ
t
+
1
=
θ
t
−
η
g
t
V
t
+
ϵ
\theta_{t+1} = \theta_t - \eta \frac{g_t}{\sqrt{V_t + \epsilon}}
θt+1=θt−ηVt+ϵgt
从上面公式可以发现,参数更新还是需要提供learning rate的。作者在上边的基础上做了进一步处理:
g
t
=
▽
θ
t
J
(
θ
t
)
g_t = \bigtriangledown_{\theta_t}J(\theta_t)
gt=▽θtJ(θt)
E
[
g
t
2
]
t
=
ρ
⋅
E
[
g
t
2
]
t
−
1
+
(
1
−
ρ
)
⋅
g
t
2
E[g_t^2]_t = \rho ·E[g_t^2]_{t-1} + (1-\rho) · g_t^2
E[gt2]t=ρ⋅E[gt2]t−1+(1−ρ)⋅gt2
▽
t
=
∑
i
=
1
t
−
1
Δ
θ
r
E
[
g
t
2
]
t
+
ϵ
\bigtriangledown_t = \frac{\sum_{i=1}^{t-1}\Delta\theta_r}{\sqrt{E[g_t^2]_t + \epsilon}}
▽t=E[gt2]t+ϵ∑i=1t−1Δθr
从上边公式可以看出,更新量
▽
t
\bigtriangledown_t
▽t不再以来learnging rate。
- 训练初中期,加速效果不错,很快
- 训练后期,反复在局部最小值附近抖动
10. RMSprop
RMSprop,将AdaGrad的梯度平方和累加修改为指数加权移动平均,可以是其在非凸设定下效果更好。
g
t
=
▽
θ
t
J
(
θ
t
)
g_t = \bigtriangledown_{\theta_t}J(\theta_t)
gt=▽θtJ(θt)
E
[
g
t
2
]
t
=
ρ
⋅
E
[
g
t
2
]
t
−
1
+
(
1
−
ρ
)
⋅
g
t
2
E[g_t^2]_t = \rho ·E[g_t^2]_{t-1} + (1-\rho) · g_t^2
E[gt2]t=ρ⋅E[gt2]t−1+(1−ρ)⋅gt2
▽
t
=
η
⋅
g
t
E
[
g
t
2
]
t
+
ϵ
\bigtriangledown_t = \frac{\eta · g_t}{\sqrt{E[g_t^2]_t + \epsilon}}
▽t=E[gt2]t+ϵη⋅gt
根据经验,有一些超参数的设定可以参考,
η
=
0.001
,
ρ
=
0.9
,
ϵ
=
1
e
−
9
\eta=0.001, \rho=0.9, \epsilon=1e-9
η=0.001,ρ=0.9,ϵ=1e−9。
- RMSprop依然以来learning rate
- RMSprop算是Adagrad的一种发展,和Adadelta的变体,效果趋于二者之间
- 适合处理非平稳目标(包括季节性、周期性),对RNN比较友好。
11. Adam
Adaptive Moment Estimation,结合了前面一阶动量,二阶动量的方法。算法伪代码如下:
首先初始化
m
0
,
v
0
,
t
m_0, v_0, t
m0,v0,t为0,
θ
0
\theta_0
θ0为初始化参数,然后循环执行如下步骤:
m
t
+
1
=
β
1
m
t
+
(
1
−
β
1
)
▽
θ
t
J
(
θ
t
)
m_{t+1} = \beta_1 m_t + (1-\beta_1)\bigtriangledown_{\theta_t}J(\theta_t)
mt+1=β1mt+(1−β1)▽θtJ(θt)
V
t
+
1
=
β
2
V
t
+
(
1
−
β
2
)
(
▽
θ
t
J
(
θ
t
)
)
2
V_{t+1} = \beta_2 V_t + (1-\beta_2) (\bigtriangledown_{\theta_t}J(\theta_t))^2
Vt+1=β2Vt+(1−β2)(▽θtJ(θt))2
由于m,v都是0初始化的,根据上边公式,
β
1
\beta_1
β1默认0.9的话,会使m趋近于0,尤其是训练初期,因此对其进行处理:
m
^
t
+
1
=
m
t
+
1
1
−
β
1
t
\hat{m}_{t+1} = \frac{m_{t+1}}{1-\beta_1^t}
m^t+1=1−β1tmt+1
v
^
t
+
1
=
v
t
+
1
1
−
β
2
t
\hat{v}_{t+1} = \frac{v_{t+1}}{1-\beta_2^t}
v^t+1=1−β2tvt+1
最终更新参数:
θ
t
+
1
=
θ
t
−
η
m
^
t
+
1
V
^
t
+
1
+
ϵ
\theta_{t+1} = \theta_t - \eta \frac{\hat{m}_{t+1}}{\sqrt{\hat{V}_{t+1} + \epsilon}}
θt+1=θt−ηV^t+1+ϵm^t+1
通常默认情况下,
β
1
=
0.9
,
β
2
=
0.9999
,
ϵ
=
1
0
−
8
\beta_1=0.9, \beta_2=0.9999, \epsilon=10^{-8}
β1=0.9,β2=0.9999,ϵ=10−8。Adam对超参数的选择比较鲁棒,在很多情况下算作默认工作性能比较优秀的优化器。