本文正在参加 人工智能创作者扶持计划 ”
深度学习基础入门篇[三]:优化策略梯度下降算法:SGD、MBGD、Momentum、Adam、AdamW
1.梯度下降算法(优化器)
1.1 原理解释
如果我们定义了一个机器学习模型,比如一个三层的神经网络,那么就需要使得这个模型能够尽可能拟合所提供的训练数据。但是我们如何评价模型对于数据的拟合是否足够呢?那就需要使用相应的指标来评价它的拟合程度,所使用到的函数就称为损失函数(Loss Function),当损失函数值下降,我们就认为模型在拟合的路上又前进了一步。最终模型对训练数据集拟合的最好的情况是在损失函数值最小的时候,在指定数据集上时,为损失函数的平均值最小的时候。
由于我们一般情况下很难直接精确地计算得到当模型的参数为何值时,损失函数最小,所以,我们可以通过让参数在损失函数的“场”中,向着损失函数值减小的方向移动,最终在收敛的时候,得到一个极小值的近似解。为了让损失函数的数值下降,那么就需要使用优化算法进行优化,其中,损失函数值下降最快的方向称为负梯度方向,所使用的算法称为梯度下降法,即最速下降法(steepest descent)。当前,几乎所有的机器学习优化算法都是基于梯度下降的算法。
总结的来讲优化器(例如梯度下降法)就是在深度学习反向传播过程中,指引损失函数(目标函数)的各个参数往正确的方向更新合适的大小,使得更新后的各个参数让损失函数(目标函数)值不断逼近全局最小。
举一个简单例子
优化问题可以看做是我们站在山上的某个位置(当前的参数信息),想要以最佳的路线去到山下(最优点)。首先,直观的方法就是环顾四周,找到下山最快的方向走一步,然后再次环顾四周,找到最快的方向,直到下山——这样的方法便是朴素的梯度下降——当前的海拔是我们的目标(损失)函数值,而我们在每一步找到的方向便是函数梯度的反方向(梯度是函数上升最快的方向,所以梯度的反方向就是函数下降最快的方向)。
使用梯度下降进行优化,是几乎所有优化器的核心思想。当我们下山时,有两个方面是我们最关心的:
-
首先是优化方向,决定“前进的方向是否正确”,在优化器中反映为梯度或动量。
-
其次是步长,决定“每一步迈多远”,在优化器中反映为学习率。
所有优化器都在关注这两个方面,但同时也有一些其他问题,比如应该在哪里出发、路线错误如何处理……这是一些最新的优化器关注的方向。
1.2 梯度下降法作用
梯度下降是机器学习中常见优化算法之一,梯度下降法有以下几个作用:
-
梯度下降是迭代法的一种,可以用于求解最小二乘问题(线性和非线性都可以),其他的问题,只要损失函数可导也可以使用梯度下降,比如交叉熵损失等等。
-
在求解机器学习算法的模型参数,即无约束优化问题时,主要有梯度下降法,牛顿法等。
-
在求解损失函数的最小值时,可以通过梯度下降法来一步步的迭代求解,得到最小化的损失函数和模型参数值。
-
如果我们需要求解损失函数的最大值,可通过梯度上升法来迭代。梯度下降法和梯度上升法可相互转换。
2.深度学习主流模型与梯度下降
下表列举了自然语言处理(NLP),计算机视觉(CV),推荐系统(Recommendation System,RS),强化学习(Reinforcement Learning,RL)这四个方向的主流模型使用优化器的情况,可以看出在NLP领域AdamW(AdamWeightDecayOptimizer)使用比较普遍,CV领域SGD和momentum使用比较普遍,推荐领域比较杂,强化学习领域Adam使用比较普遍。
依据计算目标函数梯度使用的数据量的不同,有三种梯度下降的变体,即批量梯度下降,随机梯度下降,Mini-batch梯度下降。根据数据量的大小,在参数更新的准确性和执行更新所需时间之间做了一个权衡。
2.1 批量梯度下降,BGD
标准的梯度下降,即批量梯度下降(batch gradient descent,BGD),在整个训练集上计算损失函数关于参数θ的梯度。
θ=θ−η∇θJ(θ)theta=theta-etanabla_theta J(theta)
其中θ是模型的参数,η是学习率,∇θJ(θ)∇θJ(θ)为损失函数对参数θ的导数。由于为了一次参数更新我们需要在整个训练集上计算梯度,导致 BGD 可能会非常慢,而且在训练集太大而不能全部载入内存的时候会很棘手。BGD 也不允许我们在线更新模型参数,即实时增加新的训练样本。
BGD 对于凸误差曲面(convex error surface)保证收敛到全局最优点,而对于非凸曲面(non-convex surface)则是局部最优点。
2.2 随机梯度下降,SGD
随机梯度下降( stotastic gradient descent, SGD )则是每次使用一个训练样本xix_i和标签yiy_i进行一次参数更新。
θ=θ−η⋅∇θJ(θ;xi;yi)theta=theta-etacdotnabla_theta J(theta;x^i;y^i)
其中θ是模型的参数,η是学习率,∇θJ(θ)为损失函数对参数θ的导数。BGD 对于大数据集来说执行了很多冗余的计算,因为在每一次参数更新前都要计算很多相似样本的梯度。SGD 通过一次执行一次更解决了这种冗余。因此通常 SGD 的速度会非常快而且可以被用于在线学习。SGD以高方差的特点进行连续参数更新,导致目标函数严重震荡.
BGD 能够收敛到(局部)最优点,然而 SGD 的震荡特点导致其可以跳到新的潜在的可能更好的局部最优点。已经有研究显示当我们慢慢的降低学习率时,SGD 拥有和 BGD 一样的收敛性能,对于非凸和凸曲面几乎同样能够达到局部或者全局最优点。
2.3 Mini-batch梯度下降,MBGD
Mini-batch gradient descent( mini-batch gradient descent, MBGD )则是在上面两种方法中采取了一个折中的办法:每次从训练集中取出batchsize个样本作为一个mini-batch,以此来进行一次参数更新
θ=θ−η⋅∇θJ(θ;x(i:i+n);y(i:i+n))theta=theta-etacdotnabla_theta J(theta;x^{(i:i+n);y^{(i:i+n)}})
其中θ
是模型的参数,η是学习率,∇θJ(θ;x(i:i+n);y(i:i+n)nabla_{theta}J(theta;x^{(i:i+n);y^{(i:i+n)}}为损失函数对参数θ的导数,n为Mini-bach的大小(batch size)。 batch size越大,批次越少,训练时间会更快一点,但可能造成数据的很大浪费;而batch size越小,对数据的利用越充分,浪费的数据量越少,但批次会很大,训练会更耗时。
2.3.1 优点
-
减小参数更新的方差,这样可以有更稳定的收敛。
-
利用现在最先进的深度学习库对矩阵运算进行了高度优化的特点,这样可以使得计算 mini-batch 的梯度更高效。
样本数目较大的话,一般的mini-batch大小为64到512,考虑到电脑内存设置和使用的方式,如果mini-batch大小是2的n次方,代码会运行地快一些,64就是2的6次方,以此类推,128是2的7次方,256是2的8次方,512是2的9次方。所以我经常把mini-batch大小设成2的次方。
MBGD 是训练神经网络时的常用方法,而且通常即使实际上使用的是 MBGD,也会使用 SGD 这个词来代替。
2.3.2MBGD面临的问题
- 学习率选择
选择一个好的学习率是非常困难的。太小的学习率导致收敛非常缓慢,而太大的学习率则会阻碍收敛,导致损失函数在最优点附近震荡甚至发散。相同的学习率被应用到所有参数更新中。如果我们的数据比较稀疏,特征有非常多不同的频率,那么此时我们可能并不想要以相同的程度更新他们,反而是对更少出现的特征给予更大的更新。为了能在学习期间自动调节学习率,根据先前定义好的一个规则来减小学习率,或者两次迭代之间目标函数的改变低于一个阈值的时候。然而这些规则和阈值也是需要在训练前定义好的,所以也不能做到自适应数据的特点
上图中,学习率设置过大,导致目标函数值沿着 “山谷” 周围大幅震荡,可能永远都到达不了最小值。
- 鞍点
对于神经网络来说,另一个最小化高度非凸误差函数的关键挑战是避免陷入他们大量的次局部最优点(suboptimal)。事实上困难来自于鞍点而不是局部最优点,即损失函数在该点的一个维度上是上坡(slopes up),而在另一个维度上是下坡(slopes down)。这些鞍点通常被一个具有相同误差的平面所包围,这使得对于 SGD 来说非常难于逃脱,因为在各个维度上梯度都趋近于 0 。
如图,鞍点得名于它的形状类似于马鞍。尽管它在 x 方向上是一个最小值点,但是它在另一个方向上是局部最大值点,并且,如果它沿着 x 方向变得更平坦的话,梯度下降会在 x 轴振荡并且不能继续根据 y 轴下降,这就会给我们一种已经收敛到最小值点的错觉。
3. Momentum
为了抑制SGD的震荡,SGDM认为梯度下降过程可以加入惯性。可以简单理解为:当我们将一个小球从山上滚下来时,没有阻力的话,它的动量会越来越大,但是如果遇到了阻力,速度就会变小。SGDM全称是SGD with momentum,在SGD基础上引入了一阶动量:
vt=γvt−1+η∇J(θ)v_t=gamma v_{t-1}+etanabla J(theta)
SGD-M参数更新公式如下,其中η是学习率,∇J(θ)是当前参数的梯度
θ=θ−vttheta=theta-v_t
一阶动量是各个时刻梯度方向的指数移动平均值,也就是说,t时刻的下降方向,不仅由当前点的梯度方向决定,而且由此前累积的下降方向决定。γ的经验值为0.9,这就意味着下降方向主要是此前累积的下降方向,并略微偏向当前时刻的下降方向。想象高速公路上汽车转弯,在高速向前的同时略微偏向,急转弯可是要出事的。 SGD 震荡且缓慢的沿着沟壑的下坡方向朝着局部最优点前进,如下图:
momentum能够加速SGD方法,并且能够减少震荡,如下图:
特点
-
加入了动量因素,SGD-M缓解了SGD在局部最优点梯度为0,无法持续更新的问题和振荡幅度过大的问题。
-
当局部沟壑比较深,动量加持用完了,依然会困在局部最优里来回振荡
4.NAG
SGD 还有一个问题是困在局部最优的沟壑里面震荡。想象一下你走到一个盆地,四周都是略高的小山,你觉得没有下坡的方向,那就只能待在这里了。可是如果你爬上高地,就会发现外面的世界还很广阔。因此,我们不能停留在当前位置去观察未来的方向,而要向前一步、多看一步、看远一些。
NAG全称Nesterov Accelerated Gradient,是在SGD、SGD-M的基础上的进一步改进,我们知道在时刻t的主要下降方向是由累积动量决定的,自己的梯度方向说了也不算,那与其看当前梯度方向,不如先看看如果跟着累积动量走了一步,那个时候再怎么走。因此,NAG不计算当前位置的梯度方向,而是计算如果按照累积动量走了一步,那个时候的下降方向:
vt=γvt−1+η∇θJ(θ−γvt−1)v_t=gamma v_{t-1}+etanabla_theta J(theta-gamma v_{t-1})
NAG参数更新公式如下,其中η是学习率, ∇θJ(θ−γvt−1)nabla_theta J(theta-gamma v_{t-1})是当前参数的梯度
θ=θ−vttheta=theta-v_t
然后用下一个点的梯度方向,与历史累积动量相结合,计算当前时刻的累积动量。
如上图,动量法首先计算当前梯度(图中的小蓝色向量),然后在更新累积梯度(updated accumulated gradient)方向上大幅度的跳跃(图中的大蓝色向量)。与此不同的是,NAG 首先在先前的累积梯度(previous accumulated gradient)方向上进行大幅度的跳跃(图中的棕色向量),评估这个梯度并做一下修正(图中的红色向量),这就构成一次完整的 NAG 更新(图中的绿色向量)。这种预期更新防止我们进行的太快,也带来了更高的相应速度,这在一些任务中非常有效的提升了 RNN 的性能。
特点
- 有利于跳出当前局部最优的沟壑,寻找新的最优值,但收敛速度慢
5.AdaGrad,自适应学习率开启
SGD系列的都没有用到二阶动量。二阶动量的出现,才意味着“自适应学习率”优化算法时代的到来。SGD及其变种以同样的学习率更新每个参数,但深度神经网络往往包含大量的参数,这些参数并不是总会用得到。对于经常更新的参数,我们已经积累了大量关于它的知识,不希望被单个样本影响太大,希望学习速率慢一些;对于偶尔更新的参数,我们了解的信息太少,希望能从每个偶然出现的样本身上多学一些,即学习速率大一些。因此,Adagrad 非常适用于稀疏数据。
Dean 等人发现 Adagrad 能够大幅提高 SGD 的鲁棒性,并在 Google 用其训练大规模神经网络,这其中就包括 在 YouTube 中学习识别猫。除此之外,Pennington 等人用 Adagrad 来训练 GloVe 词嵌入,因为罕见的词汇需要比常见词更大的更新。
AdaGrad算法就是将每一个参数的每一次迭代的梯度取平方累加后在开方,用全局学习率除以这个数,作为学习率的动态更新。对于不同的参数动态的采取不同的学习率,让目标函数更快的收敛。为了简洁,我们用gtg_t来表示t时刻的梯度,gt,ig_{t,i}就是目标函数的偏导数:
gt,i=∇θJ(θt,i)g_{t,i}=nabla_{theta}J(theta_{t,i})
SGD在在每个时刻t对参数θiθi的更新为:
θt+1,i=θt,i−η⋅gt,itheta_{t+1,i}=theta_{t,i}-etacdot g_{t,i}
Adagrad修改了t时刻对于每个参数θiθi的学习率η:
θt+1,i=θt,i−ηGt,ii+ϵ⋅gt,itheta_{t+1,i}=theta_{t,i}-dfrac{eta}{sqrt{G_{t,ii}+epsilon}}cdot g_{t,i}
其中Gt∈Rd×dG_t∈R_d×d是对角矩阵,其中每一个对角元素i,i是θiθi在时刻t的梯度平方和,一般为了避免分母为0,会在分母上加一个小的平滑项,用符号ϵ表示,通常为10−8 左右。因此Gt+ϵsqrt{G_{t}+epsilon}恒大于0,而且参数更新越频繁,二阶动量越大,学习率就越小。有趣的是,如果去掉开方操作,算法性能会大幅下降
-
优点
-
在稀疏数据场景下表现非常好
-
此前的SGD及其变体的优化器主要聚焦在优化梯度前进的方向上,而AdaGrad首次使用二阶动量来关注学习率(步长),开启了自适应学习率算法的里程。大多数实现使用一个默认值 0.01 。
-
-
缺点
- Gt+ϵsqrt{G_{t}+epsilon}是单调递增的,会使得学习率单调递减至0,可能会使得训练过程提前结束,即便后续还有数据也无法学到必要的知识。
6. AdaDelta
由于AdaGrad单调递减的学习率变化过于激进,考虑一个改变二阶动量计算方法的策略:不累积全部历史梯度,而只关注过去一段时间窗口的下降梯度。这也就是AdaDelta名称中Delta的来历。Adadelta是 Adagrad 的扩展,旨在帮助缓解后者学习率单调下降的问题
-
优点
- 避免了二阶动量持续累积、导致训练过程提前结束的问题了
7.RMSProp(2012)
RMSProp 算法(Hinton,2012)修改 AdaGrad 以在非凸情况下表现更好,它改变梯度累积为指数加权的移动平均值,从而丢弃距离较远的历史梯度信息。RMSProp 与 Adadelta 的移动均值更新方式十分相似
E[g2]t=0.9E[g2]t−1+0.1gt2E[g^2]_{t}=0.9E[g^2]_{t-1}+0.1g_{t}^{2}
RMSProp参数更新公式如下,其中η是学习率, gtg_t是当前参数的梯度
θt+1=θt−ηE[g2]t+ϵgttheta_{t+1}=theta_t-dfrac{eta}{sqrt{E[g^2]_t+epsilon}}g_t
RMSprop将学习速率除以梯度平方的指数衰减平均值。Hinton建议γ设置为0.9,默认学习率η为0.001
8.Adam
Adam最开始是由 OpenAI 的 Diederik Kingma 和多伦多大学的 Jimmy Ba提出的。Adam使用动量和自适应学习率来加快收敛速度。SGD-M在SGD基础上增加了一阶动量,AdaGrad和AdaDelta在SGD基础上增加了二阶动量(二阶矩估计)。把一阶动量和二阶动量都用起来,就是Adam了——Adaptive + Momentum。
-
优点
-
通过一阶动量和二阶动量,有效控制学习率步长和梯度方向,防止梯度的振荡和在鞍点的静止。
-
实现简单,计算高效,对内存需求少
-
参数的更新不受梯度的伸缩变换影响
-
超参数具有很好的解释性,且通常无需调整或仅需很少的微调
-
更新的步长能够被限制在大致的范围内(初始学习率)
-
能自然地实现步长退火过程(自动调整学习率)
-
很适合应用于大规模的数据及参数的场景
-
适用于不稳定目标函数
-
适用于梯度稀疏或梯度存在很大噪声的问题
-
Adam在很多情况下算作默认工作性能比较优秀的优化器。
-
-
缺点
-
可能不收敛:二阶动量是固定时间窗口内的累积,随着时间窗口的变化,遇到的数据可能发生巨变,使得VtV_t可能会时大时小,不是单调变化。这就可能在训练后期引起学习率的震荡,导致模型无法收敛。
vt=max(β2⋅vt−1+(1−β2)gt2,vt−1)v_t=max(beta_2cdot v_{t-1}+(1-beta_2)g_t^2,v_{t-1})quadtext{}
修正的方法。由于Adam中的学习率主要是由二阶动量控制的,为了保证算法的收敛,可以对二阶动量的变化进行控制,避免上下波动。
- 可能错过全局最优解:自适应学习率算法可能会对前期出现的特征过拟合,后期才出现的特征很难纠正前期的拟合效果。后期Adam的学习率太低,影响了有效的收敛。
9.Adamax
10.Nadam
Adam可以被看作是融合了RMSProp和momentum,RMSprop 贡献了历史平方梯度的指数衰减的平均值vtv_t,而动量则负责历史梯度的指数衰减平均值mtm_t,Nadam在Adam的基础上加入了一阶动量的累积,即Nesterov + Adam = Nadam,为了把NAG融入到Adam中,我们需要修改momentum的项mtm_t
11.AMSGrad(2018)
AMSGrad在ICLR 2018年被提出来,并获得了最佳论文。AMSGrad是一个随机梯度下降优化方法,它试图解决基于Adam的优化器的收敛问题。AMSGrad使用最大化过去平方梯度vt
来更新参数,而不是使用指数平均,这样就降低了指数衰减平均,造成重要历史信息快速丢失的影响。
mt=β1mt−1+(1−β1)gtvt=β2vt−1+(1−β2)gt2begin{array}{c}m_t=beta_1m_{t-1}+(1-beta_1)g_t\ v_t=beta_2v_{t-1}+(1-beta_2)g_t^2end{array}
上面的两个公式跟Adam是一样的,求的是一阶矩和二阶矩,gtg_t是当前参数的梯度,β1β1为一阶矩估计的指数衰减率,β2β2是二阶矩估计的指数衰减率,前者控制一阶矩估计,后者控制二阶矩估计。
v^t=max(v^t−1,vt)hat{v}_t=max(hat{v}_{t-1},v_t)
上式求过去最大的平方梯度vtv^t,参数的更新公式如下:
θt+1=θt−ηv^t+ϵmttheta_{t+1}=theta_t-dfrac{eta}{sqrt{hat{v}_t}+epsilon}m_t
从上面的公式可以看出,参数更新公式与Adam没有啥区别,但是求vtv^t有区别。AMSGRAD不增加步长,避免了ADAM和RMSPROP算法的缺陷。
12.AdaBound
AdaBound算法训练速度比肩Adam,性能媲美SGD。SGD现在后期调优时还是经常使用到,但SGD的问题是前期收敛速度慢。SGD前期收敛慢的原因: SGD在更新参数时对各个维度上梯度的放缩是一致的,并且在训练数据分布极不均很时训练效果很差。而因为收敛慢的问题应运而生的自适应优化算法Adam、AdaGrad、RMSprop 等,但这些自适应的优化算法虽然可以在训练早期展现出快速的收敛速度,但其在测试集上的表现却会很快陷入停滞,并最终被 SGD 超过。
13.AdamW
L2 正则化是减少过拟合的经典方法,它会向损失函数添加由模型所有权重的平方和组成的惩罚项,并乘上特定的超参数以控制惩罚力度。加入L2正则以后,损失函数就变为:
从上面的公式可以看出,AdamW本质上就是在损失函数里面加入了L2正则项,然后计算梯度和更新参数的时候都需要考虑这个正则项。AdamW使用在hugging face版的transformer中,BERT,XLNET,ELECTRA等主流的NLP模型,都是用了AdamW优化器
14.RAdam
RAdam(Rectified Adam)是Adam优化器的一个变体,它引入了一项来纠正自适应学习率的方差,试图解决Adam的收敛性差的问题。
15.Lookahead
Lookahead是一种梯度下降优化器,它迭代的更新两个权重集合,”fast”和”slow”。直观地说,该算法通过向前看由另一个优化器生成的快速权值序列来选择搜索方向。 梯度下降的时候,走几步会退回来检查是否方向正确。避免突然掉入局部最低点。
Lookahead的算法描述如下:
- Fast weights
它是由内循环优化器(inner-loop)生成的k次序列权重;这里的优化器就是原有的优化器,如SGD,Adam等均可;其优化方法与原优化器并没有区别,例如给定优化器A,目标函数L,当前训练mini-batch样本d,这里会将该轮循环的k次权重,用序列都保存下来。
- Slow Weights:
在每轮内循环结束后,根据本轮的k次权重,计算等到Slow Weights;这里采用的是指数移动平均(exponential moving average, EMA)算法来计算,最终模型使用的参数也是慢更新(Slow Weights)那一套,因此快更新(Fast Weights)相当于做了一系列实验,然后慢更新再根据实验结果选一个比较好的方向,这有点类似 Nesterov Momentum 的思想。
本文正在参加 人工智能创作者扶持计划 ”