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

vanilla

(1 ) Gradient Descent

针对整个数据集

θ=θηθJ(θ) 特点:

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

(2 ) Stochastic Gradient Descent (SGD)

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

θ=θηJ(θ;x(i),y(i))

特点:

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

(3) Mini-Batch Gradient Descent (MBGD)

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

θ=θηJ(θ;x(i:i+n),y(i:i+n))

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 中是这样的:

v=ρv+gp=plrv=plrρvlrg 而其他的框架是

v=ρv+lrgp=pv=pρvlrg

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

人工设置学习率

  1. Momentum (heavy-ball method)

xt+1=xtαf(xt)+μ(xtxt1),μ[0,1],α>0

其中 μ 是动量因子,取值 0.9 左右。

优点:

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

缺点:

  1. Nesterov Accelerated Gradient

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

xt+1=xt+μ(xtxt1)γf(xt+μ(xtxt1)) 其中学习率γ, momentum系数μ 和参数的初始化x0(公式中没有显示出来)

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

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:自动调节学习率。

nt=nt1+gt2Δθt=ηnt+ϵgt

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

特点:

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

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

  1. Adadelta

intuition:

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

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

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

E[Δθ2]t=γE[Δθ2]t1+(1γ)Δθt2

然后是常用的平方根操作: RMS[Δθ]t=E[Δθ2]t+ϵ

所以将上述两个方面的更新整合起来: Δθt=RMS[Δθ]t1RMS[g]tgtθt+1=θt+Δθt

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

  1. RMSprop

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

E[g2]t=γE[g2]t1+(1γ)gt2θt+1=θtηE[g2]t+ϵgt 其中 Hinton推荐超参数 γ=0.9ϵ=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 vt, like Adadelta and RMSprop, Adam also keeps an exponentially decaying average of past gradients mt, similar to momentum.

mt=β1mt1+(1β1)gtvt=β2vt1+(1β2)gt2

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

于是进行了 bias-corrected m^t=mt1β1tv^t=vt1β2t 最后进行了梯度的更新: θt+1=θtηv^t+ϵm^t 作者推荐的超参数 β1=0.9β2=0.999, ϵ=108

特点:从实践的角度,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种学习率衰减策略