深度学习中优化器 (optimizer)讲解。根据学习率的情况分成三个阶段,基础版本Gradient Descent,然后是人工设置学习率阶段和自适应学习率阶段。最后对应pytorch,说明常用的几种学习率优化策略。

vanilla

(1 ) Gradient Descent

针对整个数据集

$$ \theta = \theta - \eta \cdot \nabla _ { \theta } J ( \theta ) $$ 特点:

  • 使用整个数据集计算梯度,计算起来非常慢
  • 能够找到全局最优点

(2 ) Stochastic Gradient Descent (SGD)

SGD 又走入了另外一个极端,SGD 拿到一个数据之后,马上计算梯度,然后对参数进行更新.

$$ \theta = \theta - \eta \cdot J \left( \theta ; x ^ { ( i ) } , y ^ { ( i ) } \right) $$

特点:

  • 无法使用矩阵加速运算
  • 收敛速度快

(3) Mini-Batch Gradient Descent (MBGD)

Mini-batch 的方法是在上述两个方法中取了个折衷,每次从全部的熟练数据中取一个 mini-batch 的数据计算。

$$ \theta = \theta - \eta \cdot J \left( \theta ; x ^ { ( i : i + n ) } , y ^ { ( i : i + n ) } \right) $$

batch size 的选择 n: 一般取值在 50~256

目前,mini-batch 的方法是深度学习中主流方法,各种深度学习工具默认也是这种方法。也可以把上述两种方法看成是 mini-batch 的特例,Batch 的方法,就是 mini-batch size 是整个数据集,SGD 方法就是 min-batch=1 的情况.

目前遇到的问题

  • learning rate 如何进行自动调整
  • 如何跳出马鞍点

在pytorch中的实现:

1
class torch.optim.SGD(params, lr=<required parameter>, momentum=0, dampening=0, weight_decay=0, nesterov=False)

功能: pytorch中实现的是带有动量优化的SGD,其中常见的参数:

  • params (iterable) 待优化的参数或者定义了参数组的dict
  • lr (float) 学习率
  • momentum (float 可选) -动量因子,默认是0
  • weight_decay (float, 可选) -权重衰减(L2 惩罚),默认是0

注意:pytorch中的使用SGD 和其他的框架不同,pytorch 中是这样的:

$$ \begin{split} v &= \rho * v+g \\
p &= p- lr * v \\
& = p- lr * \rho * v- lr * g \end{split} $$ 而其他的框架是

$$ \begin{split} v &=\rho * v+ lr * g \\
p &=p- v=p- \rho * v- lr * g \end{split} $$

其中 $\rho$ 是动量, $v$ 是速度, $g$ 是梯度, pytorch中将 $ \rho * v $这一项也是乘以了一个学习率。

人工设置学习率

  1. Momentum (heavy-ball method)

$$ x_{t+1}=x_{t}-\alpha \nabla f\left(x_{t}\right)+\mu\left(x_{t}-x_{t-1}\right), \quad \mu \in[0,1], \alpha>0 $$

其中 $\mu$ 是动量因子,取值 0.9 左右。

优点:

  • 可以加速 SGD, 并且抑制震荡

缺点:

  1. Nesterov Accelerated Gradient

对于上面 momentum出现的bad case, Nesterov 来背锅。

$$ x_{t+1}=x_{t}+\mu\left(x_{t}-x_{t-1}\right)-\gamma \nabla f\left(x_{t}+\mu\left(x_{t}-x_{t-1}\right)\right) $$ 其中学习率$\gamma$, momentum系数$\mu$ 和参数的初始化$x_0$(公式中没有显示出来)

两者的区别,一切尽在图中。(相同的颜色表示相同含义)

img Nesterov Accelerated Gradient的关键在于计算梯度之前加上了之前积累的梯度。Notice how the gradient step with Polyak’s momentum is always perpendicular to the level set. taken from here

两种的相同点:都是基于momentum的 optim,都是针对凸优化函数;不同点:momentum 对于简单强凸优化函数,比如导数是二次方的函数,可能出现bad case;相对于而言,Nesterov Accelerated Gradient在凸优化函数中适用性更广。

自适应学习率方法

  1. Adagrad (Adaptive gradient algorithm)

intuition:自动调节学习率。

$$ \begin{split} n_{t} &=n_{t-1}+g_{t}^{2} \\
\Delta \theta_{t} &=-\frac{\eta}{\sqrt{n_{t}+\epsilon}} * g_{t} \end{split} $$

其中 $n_t$表示从1到$t$ 形成的一个递推项,作为一个约束项, $\epsilon$ 用来保证分母非0,一般取 $1e-8$.实验表明,如果没有进行平方根操作,那么效果很差。

特点:

  • 不用人工去tune 学习率,但是仍然需要在开始时候,给定一个学习率(依赖全局初始化学习率)
  • 每次增加的嗾使一个正数(平方和),所以训练的中后期,学习率有可能变得非常小(缺点)

实际的使用效果:这个学习率的变化会受到梯度的大小和迭代次数的影响。梯度越大,学习率越小;梯度越小,学习率越大。对于稀疏数据集,效果很好。比如说在RNN 中训练词向量的过程,经常使用到。

  1. Adadelta

intuition:

  • 解决使用Adagrad训练中后期,梯度可能为0 的问题
  • 不依赖于初始化的learning rate

首先处理第一个问题:梯度为0. 不使用之前全部的梯度平方和,使用部分。这个时候一种是暴力的滑动窗口的相反,但是作者使用另一种方法: decaying。 $$ E[g ^2] _t=\gamma E[ g ^2] _{t-1}+(1-\gamma) g _t ^2 $$ 其中 $\gamma$ 是类似momentum中的超参数,大概在 0.9左右。

第二个问题是初始化学习率问题。定义了另一个decay 系数,只不过这个是应用在参数 $\theta$ 上的,而不是上面的 $g_t$上。

$$ E[\Delta \theta ^2] _t=\gamma E[\Delta \theta ^2 ] _{t-1}+(1-\gamma) \Delta \theta _t ^2 $$

然后是常用的平方根操作: $$ R M S[\Delta \theta] _t=\sqrt{E [\Delta \theta ^2 ] _t+\epsilon} $$

所以将上述两个方面的更新整合起来: $$ \begin{split} \Delta \theta _t &=-\frac{R M S[\Delta \theta] _{t-1}}{R M S[g] _{t}} g _t \\
\theta _{t+1} &=\theta _{t}+\Delta \theta _t \end{split} $$

特点:不用设置全局的学习率,学习率是随着迭代次数变化的

  1. RMSprop

RMSprop是 Geoff Hinton 在其课程中讲到的一种方法。intuition 也是为了解决 Adagrad 中的出现的梯度为0. 实际上 RMSprop 就是 Adadelta 中的第一个方面的公式。

$$ \begin{split} E[g ^2] _t &=\gamma E[ g ^2] _{t-1}+(1-\gamma) g _t ^2 \\
\theta _{t+1} &= \theta _t-\frac{\eta}{\sqrt{E\left[g^{2}\right] _{t}+\epsilon}} g _{t} \end{split} $$ 其中 Hinton推荐超参数 $\gamma =0.9$, $\epsilon =0.001$。(ps RMSprop 和 Adadelta解决的方案叫做: exponentially decaying average of past squared gradients)

特点:解决了 Adagrad 中出现的梯度为0的情况

1
class torch.optim.RMSprop(params, lr=0.01, alpha=0.99, eps=1e-08, weight_decay=0, momentum=0, centered=False)

参数:

  • params (iterable) 待优化参数的iterable 或者是低你够了参数组的dict
  • lr (float, 可选) 学习率 (默认是 1e-2)
  • momentum (float, 可选)动量因子(默认是 0)
  • alpha (float, 可选) 平滑因子(默认是 0.99)
  • eps (float,可选) 分母中的数值,可以增加数值的稳定性(默认是 1e-8)
  • weight_decay (float, 可选) 权重衰减(L2 惩罚) (默认是 0)

alpha:同样也称为学习率或步长因子,它控制了权重的更新比率(如0.001).较大的值(如0.3)在学习率更新前会更快的初始学习,而较小的值(如1E-5)会令训练收敛到更好的性能

beta1:一阶矩估计的指数衰减率(如0.9)

beta2:二阶矩估计的指数衰减率(如0.99).该超参数在系数梯度(如在NLP或计算机视觉任务中)中应该设置接近1的数

epsilon:该参数是非常小的数,其为了防止在实现中除以零(如1E-8)

  1. Adam:Adaptive Moment Estimation

intuition:如果是momentum机制类似一个 ball running down a slope, 那么 Adam behaves a heavy ball with friction。 这种性质是有利于处理非凸优化问题,可能中间出现了一些平滑的局部最优解。如同Adadelta 和 RMSprop,使用了梯度的二阶导数,如同momentum,使用了梯度的一阶导数,把这两者信息结合起来,就是momentum。

In addition to storing an exponentially decaying average of past squared gradients $v_t$, like Adadelta and RMSprop, Adam also keeps an exponentially decaying average of past gradients $m_t$, similar to momentum.

$$ \begin{split} m _{t} &=\beta _{1} m _{t-1}+(1-\beta _{1}) g _{t} \\
v _{t} &=\beta _{2} v _{t-1}+(1-\beta _{2}) g _{t} ^{2} \end{split} $$

其中 $m_t$和 $v_t$ 分别是对梯度的一阶矩估计 (first moment ,the mean) 和二阶矩估计(the second moment ,the uncentered variance),这个是 Adam名字的由来。实验中发现上述公式在初始化阶段,$m_t$ 和$v_t$都是趋向于0,尤其是当 $\beta_1$ 和$\beta_2$ 解决1的时候。

于是进行了 bias-corrected $$ \begin{split} \hat{m} _{t} &=\frac{m _{t}}{1-\beta _{1} ^{t}} \\
\hat{v} _{t} &=\frac{v _{t}}{1-\beta _{2} ^{t}} \end{split} $$ 最后进行了梯度的更新: $$ \theta _{t+1}=\theta _{t}-\frac{\eta}{\sqrt{\hat{v} _{t}}+\epsilon} \hat{m} _{t} $$ 作者推荐的超参数 $\beta_1 =0.9$, $\beta_2 =0.999$, $\epsilon =10 ^{-8}$

特点:从实践的角度,Adam是优于上述几种的。所以就不用选择了。

1
class torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)

参数

  • params (iterable) 待优化参数的iterable 或者是定义了参数组的dict
  • lr (float, 可选) 学习率(默认是 13-3)
  • betas (Tuple [float, float], 可选) 用于计算梯度以及梯度平方的运行平均值的系数
  • eps (float, 可选) 增加数值稳定性而加到分母中的项
  • weight_decay (float, 可选) 权重衰减(L2 惩罚) 默认是 0

Adam 的缺陷:

虽然Adam算法目前成为主流的优化算法,不过在很多领域里(如计算机视觉的对象识别、NLP中的机器翻译)的最佳成果仍然是使用带动量(Momentum)的SGD来获取到的。

修正指数移动均值:最近的几篇论文显示较低的$β_2$(如0.99或0.9)能够获得比默认值0.999更佳的结果,暗示出指数移动均值本身可能也包含了缺陷。

虽然Adam算法在实践中要比RMSProp更加优秀,但同时我们也可以尝试SGD+Nesterov动量作为Adam的替代。即我们通常推荐在深度学习模型中使用Adam算法或SGD+Nesterov动量法。

图例

如果空间中存在鞍点:

如果空间中存在若干和局部最优点:

如果… Mr9qmj.gif

learning rate scheduler

While training very large and deep neural networks, the model might overfit very easily. This becomes a larger issue when the dataset is small and simple.

当数据集比较简单的时候,容易出现过拟合。

Let’s say that we observe that the validation loss has not decreased for 5 consecutive epochs. Then there is a very high chance that the model is starting to overfit. In that case, we can start to decrease the learning rate, say, by a factor of 0.5.

基本思路一:当 validation loss 不再下降的时候, learning rate 开始 decrease。

基本思路二:希望初期学习率大一些,使得网络收敛迅速一些,在训练后期学习率小一些,这样网络能够收敛到最优解。

learning rate scheduler 也被称为 learning rate decay(学习率衰减)

img

固定步长衰减、指数衰减、多步衰减、余弦退火衰减

(1)pytorch 中optim 优化器

参数组(param groups)

optimizer 是通过 param_group 来管理参数组, param_group 中保存了参数组和相应的学习率,动量等。可以针对不同的参数设置不同的学习率。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
# 有两个`param_group`即,len(optim.param_groups)==2
optim.SGD([
                {'params': model.base.parameters()},
                {'params': model.classifier.parameters(), 'lr': 1e-3}
            ], lr=1e-2, momentum=0.9)

#一个参数组
optim.SGD(model.parameters(), lr=1e-2, momentum=.9)
# 获得学习率
print('learning rate: {}'.format(optimizer.param_groups[0]['lr']))
print('weight decay: {}'.format(optimizer.param_groups[0]['weight_decay']))

(2) pytorch中学习率调整策略

learning rate scheduler

PyTorch提供的学习率调整策略分为三大类,分别是

  • 有序调整:等间隔调整(Step),按需调整学习率(MultiStep),指数衰减调整(Exponential)和 余弦退火CosineAnnealing。
  • 自适应调整:自适应调整学习率 ReduceLROnPlateau。
  • 自定义调整:自定义调整学习率 LambdaLR。

有序调整

1). 等间隔(固定步长)调整学习率 StepLR

1
2
optimizer_StepLR = torch.optim.SGD(net.parameters(), lr=0.1)
StepLR = torch.optim.lr_scheduler.StepLR(optimizer_StepLR, step_size=step_size, gamma=0.65)

参数

  • step_size(int) - 学习率下降间隔数,若为 30,则会在 30、 60、 90…个 step 时,将学习率调整为 lr*gamma。
  • gamma(float)- 学习率调整倍数,默认为 0.1 倍,即下降 10 倍。
  • last_epoch(int)- 上一个 epoch 数,这个变量用来指示学习率是否需要调整。当last_epoch 符合设定的间隔时,就会对学习率进行调整。当为-1 时,学习率设置为初始值。

img

gamma 越大,那么总的变化越慢。

  1. 指数衰减调整学习率

需要先定义优化器,然后针对不同的优化器去执行不同的策略(下同)

1
2
optimizer_ExpLR = torch.optim.SGD(net.parameters(), lr=0.1)
ExpLR = torch.optim.lr_scheduler.ExponentialLR(optimizer_ExpLR, gamma=0.98)

参数

  • gamma- 学习率调整倍数的底,指数为 epoch,即 gamma**epoch
  • ast_epoch(int)- 上一个 epoch 数,这个变量用来指示学习率是否需要调整。当 last_epoch 符合设定的间隔时,就会对学习率进行调整。当为-1 时,学习率设置为初始 值

其中参数 gamma 表示衰减的底数,选择不同的 gamma 数值得到幅度不同的衰减曲线

img

gamma 越大,那么总的变化越慢。

  1. 多步长衰减

上述固定步长的衰减可以按照固定的区间长度进行学习率的更新,但有时我们希望不同的区间采用不同的更新频率,或者是有的区间更新学习率,有的区间不更新学习率,这个时候就需要使用 MultiStepLR 来实现冬天区间长度控制

1
2
3
optimizer_MultiStepLR = torch.optim.SGD(net.parameters(), lr=0.1)
torch.optim.lr_scheduler.MultiStepLR(optimizer_MultiStepLR,
                    milestones=[200, 300, 320, 340, 200], gamma=0.8)

img

从图中可以看出,学习率在区间[200, 400]内快速的下降,这就是milestones参数所控制的,在milestones以外的区间学习率始终保持不变。

  1. 余弦退火衰减

严格来说,余弦退火衰减并不应该算是学习率衰减策略,因为它使得学习率按照周期变化。余弦退火衰减可以分为两类:不带热重启和热重启。前者是按照余弦函数的样子衰减到 0 就停止了。后者还会再次变大。

1
2
optimizer_CosineLR = torch.optim.SGD(net.parameters(), lr=0.1)
CosineLR = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer_CosineLR, T_max=150, eta_min=0)

其包含的参数和余弦知识一致,参数T_max表示余弦函数周期;eta_min表示学习率的最小值,默认它是0表示学习率至少为正值。确定一个余弦函数需要知道最值和周期,其中周期就是T_max,最值是初试学习率。下图展示了不同周期下的余弦学习率更新曲线:

img

  1. LambdaLR

Sets the learning rate of each parameter group to the initial lr times a given function.

learning rate 是根据某个 lambda 函数去变化。

自适应调整

1). 按照指标调整学习率( ReduceLROnPlateau)

plateau (在一段时期的发展后)稳定期,停滞期

当某指标不再变化(下降或升高),调整学习率,这是非常实用的学习率调整策略。 例如,当验证集的 loss 不再下降时,进行学习率调整;或者监测验证集的 accuracy,当accuracy 不再上升时,则调整学习率。

1
torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10, verbose=False, threshold=0.0001, threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-08)
  • mode(str)- 模式选择,有 min 和 max 两种模式, min 表示当指标不再降低(如监测loss), max 表示当指标不再升高(如监测 accuracy)。
  • factor(float)- 学习率调整倍数(等同于其它方法的 gamma),即学习率更新为 lr = lr * factor
  • patience(int)- 忍受该指标多少个 step 不变化,当忍无可忍时,调整学习率。
  • threshold_mode(str)- 选择判断指标是否达最优的模式,有两种模式, rel 和 abs。当 threshold_mode == rel,并且 mode == max 时, dynamic_threshold = best * ( 1 +threshold );当 threshold_mode == rel,并且 mode == min 时, dynamic_threshold = best * ( 1 -threshold );当 threshold_mode == abs,并且 mode== max 时, dynamic_threshold = best + threshold ;当 threshold_mode == rel,并且 mode == max 时, dynamic_threshold = best - threshold;
  • eps(float)- 学习率衰减的最小值,当学习率变化小于 eps 时,则不调整学习率。

自定义调整

自定义调整学习率 LambdaLR

1
torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch=-1)
  • lr_lambda(function or list)- 一个计算学习率调整倍数的函数,输入通常为 step,当有多个参数组时,设为 list.
  • last_epoch (int) – 上一个 epoch 数,这个变量用来指示学习率是否需要调整。当 last_epoch 符合设定的间隔时,就会对学习率进行调整。当为-1 时,学习率设置为初始 值。

学习率衰减应该是在 optimizer 更新之后应用,代码写成:

1
2
3
4
5
scheduler =""
for epoch in range(100):
    train()
    validation()
    scheduler.step()

参考文献

Guide to Pytorch Learning Rate Scheduling

这个比较好,给出了代码、图例和数学表达式。

深度学习最全优化方法总结比较(SGD,Adagrad,Adadelta,Adam,Adamax,Nadam) An overview of gradient descent optimization algorithms

pytorch必须掌握的的4种学习率衰减策略