深度学习中的梯度下降优化算法综述

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cdknight_happy/article/details/84835809

1 简介

梯度下降算法是最常用的神经网络优化算法。常见的深度学习库也都包含了多种算法进行梯度下降的优化。但是,一般情况下,大家都是把梯度下降系列算法当作是一个用于进行优化的黑盒子,不了解它们的优势和劣势。

本文旨在帮助读者构建各种优化算法的直观理解,以帮助你在训练神经网络的过程中更好的使用它们。本文第二部分先简单叙述了常见的梯度下降优化算法;第三部分叙述了神经网络训练过程中存在的挑战;第四部分,我们将讨论一系列常见的优化算法,研究它们解决这些挑战的动机及如何推导出更新准则;第五部分,我们将探讨如何在并行和分布式环境下如何优化梯度下降的算法和结构;第六部分,我们将探讨其他有助于优化梯度下降法的策略。

梯度下降法,目的是最小化目标函数 J ( θ ) J(\theta) θ R d \theta \in R^d 表示一个模型的参数,核心思想是沿着目标函数相对于参数的梯度 θ J ( θ ) \nabla_{\theta}J(\theta) 的反方向持续移动以更新参数。学习率 η \eta 表示每次更新时的移动步长。总之,梯度下降可以想象成在由目标函数形成的“山峰”(超平面)上,沿着各点处下降最快的方向持续前进,直到我们遇到由超平面构成的“谷底”。

2 梯度下降变体

常见的有三种梯度下降的变体算法,相互之间的区别是每次使用多少样本计算目标函数相对于参数的梯度。根据每次更新时使用的样本数量,我们其实是在参数更新的准确性和每次参数更新的耗时之间进行权衡。

2.1 批梯度下降算法

批梯度下降,每次基于全部的训练样本集计算损失函数相对于参数的梯度。公式为:
θ = θ η . θ J ( θ ) \theta = \theta - \eta . \nabla_{\theta}J(\theta)
每次更新时都需要使用全部的训练样本计算梯度,好处是梯度计算准确,劣势是梯度计算量大,更新速度慢。特别是对于太大的无法全部加载到内存中的数据集,无法使用批梯度下降法进行更新。批梯度下降法也无法进行参数的在线更新,这是因为不能在运行中加入新的样本进行计算。

批梯度下降法的伪代码为:

for i in range(nb_epochs):
    param_grad = evaluate_gradient(loss_function,data,param)
    params = params - learning_rate * param_grad

对于预先定义的epoch数量,我们首先计算整个训练样本集的损失函数相对于参数param的梯度向量param_grad。注意,在最新的深度学习库中,提供了自动求导功能,可以高效、快速地求得函数对于特定参数的梯度。如果自己实现求梯度的代码,则需要进行“梯度检查”(注:CS231n中给出了关于梯度检查的指导)。

计算出梯度之后,沿着梯度的反方向,以学习率乘梯度为步长,进行梯度的更新。

批梯度下降法可以保证凸函数收敛到全局最小值,或者是非凸函数收敛到局部极小值。当然在深度学习中,损失函数一般是一个非凸函数,目前学者们猜想,对于足够大的神经网络而言,大部分局部极小值都具有很小的代价函数,我们能不能找到真正的全局最小点并不重要,而是需要在参数空间中找到一个代价很小(但不是最小)的点,因此收敛到一个局部最小值也是一个可以接受的结果。(摘抄自 《深度学习》)

2.2 随机梯度下降算法

随机梯度下降(SGD)和批梯度下降算法相反,每次只使用单个样本进行进行参数更新。使用单个样本对 ( x ( i ) , y ( i ) ) (x^{(i)},y^{(i)}) ,首先计算出损失函数值,然后按照下式进行参数更新:
θ = θ η . θ J ( θ ; x ( i ) ; y ( i ) ) \theta = \theta - \eta.\nabla_{\theta}J(\theta;x^{(i)};y^{(i)})
因此批梯度下降法在每次更新参数前,会对相似的样本计算梯度,因此在较大数据集上计算会有些冗余。而SGD每次更新仅对单个样本求梯度,去除了这些冗余,因而更新速度较快并可进行在线学习。

SGD的缺点是每次更新值的方差很大,在频繁更新下,目标函数值存在着剧烈的波动,如下图所示。
图1图 1
通俗解释下上图,假如当前选中的样本在当前参数下预测label和真实label很接近,损失函数值很小,那么本次的参数更新就不需要大幅度的变动;但是,假如当前选中的样本在更新后的参数下预测label和真实label差距很大,损失函数值很大,那么就需要进行大幅度的参数更新。表现出来就是损失函数值无法持续的稳定下降,而是存在大范围的波动。

相比于批梯度下降有可能会收敛到一个局部极小值,SGD收敛过程中的波动,有可能会帮助目标函数跳到一个可能更小的局部极小值。但是,SGD也会使得收敛的过程变得复杂,因为该方法可能持续的波动而不会停止。实验结果也表明,当慢慢减小学习率时,SGD可以取得和批梯度下降同样的收敛效果,使得非凸函数收敛到局部极小值、凸函数收敛到全局最小值。

下面的伪代码也表明了SGD是在批梯度下降的基础上加了一个对每个样本的遍历,计算每个样本的损失函数,然后使用损失函数相对于参数的梯度进行更新。不过要注意的是在开始每个epoch的更新之前,先对样本集进行了shuffle操作。

for i in range(nb_epochs):
    np.random.shuffle(data)
    for example in data:
        param_grad = evaluate_gradient(loss_function,example,param)
        params = params - learning_rate * param_grad

2.3 Mini-Batch梯度下降

mini-batch梯度下降每次使用大小为n的小批次训练样本进行参数更新。更新公式为:
θ = θ η . θ J ( θ ; x ( i : i + n ) ; y ( i : i + n ) ) \theta = \theta - \eta.\nabla_{\theta}J(\theta;x^{(i:i+n)};y^{(i:i+n)})

好处是:

  • 减少了参数更新中的方差,可以取得较稳定的收敛效果;
  • 可以使用目前常用的深度学习库中高度优化的矩阵乘法操作提升梯度计算的效率。

常用的mini-batch大小为50-256,但是也可以根据具体的应用进行调整。

小批量梯度下降算法,通常是我们训练神经网络的首选算法。现在,我们一般就用SGD来表示小批量梯度下降算法。下文对于随机梯度下降算法的介绍中,为方便起见,就省略了子式中的参数x(i:i+n),y(i:i+n)。

小批量梯度下降算法的伪代码为:

for i in range(nb_epochs):
    np.random.shuffle(data)
    for batch in get_batchs(data,batch_size = 50):
        param_grad = evaluate_gradient(loss_function,batch,param)
        params = params - learning_rate * param_grad

2.4 小结

目前经常使用小批量梯度下降算法,而不是批量梯度下降算法的原因是:n个样本均值的标准差是 σ n \frac{\sigma}{\sqrt n} ,其中 σ \sigma 是样本值真实的标准差。分母 n \sqrt n 表明使用更多样本来估计梯度的方法的回报是低于线性的。比较两个假象的梯度计算,一个基于100个样本,另一个基于10000个样本,后者需要的计算量是前者的100倍,但却只降低了10倍的均值标准差。如果能够快速地计算出梯度估计值,而不是缓慢地计算准确值,那么大多数优化算法会收敛地更快。

小批量的大小通常由以下几个因素决定:

  • 更大的批量会计算更精确的梯度估计,但是回报却是小于线性的;
  • 极小批量通常难以充分利用多核架构。这促使我们使用一些绝对最小批量,低于这个值的小批量处理不会减少计算时间;
  • 如果批量处理中的所有样本可以并行地处理(通常确是如此),那么内存消耗和批量大小会正比。对于很多硬件设施,这是批量大小的限制因素;
  • 在某些硬件上使用特定大小的数组时,运行时间会更少。尤其是在使用 GPU 时,通常使用 2 的幂数作为批量大小可以获得更少的运行时间。一般, 2 的幂数的取值范围是 32 到 256, 16 有时在尝试大模型时使用;
  • 可能是由于小批量在学习过程中加入了噪声,它们会有一些正则化效果 。 泛化误差通常在批量大小为 1 时最好。因为梯度估计的高方差,小批量训练需要较小的学习率以保持稳定性。因为降低的学习率和消耗更多步骤来遍历整个训练集都会产生更多的步骤,所以会导致总的运行时间非常大。

3 挑战

小批量梯度下降算法,也无法保证良好的收敛效果,同时也带来了一系列需要解决的问题:

  • 选择适当的学习率是一个难题。太小的学习率会导致较慢的收敛速度,而太大的学习率则会阻碍收敛,并会引起损失函数在最小值处震荡,甚至有可能导致结果发散;
  • 我们可以设置一个关于学习率的列表,如通过退火的方法,在学习过程中调整学习率 —— 按照一个预先定义的列表、或是当相邻迭代epoch中损失函数的变化小于一定阈值时来降低学习率。但这些列表或阈值,需要根据数据集的特性,提前定义;
  • 此外,我们对所有的参数都采用了相同的学习率。但如果我们的数据比较稀疏,同时特征有着不同的出现频率,那么我们不希望以相同的学习率来更新这些变量,我们希望对较少出现的特征有更大的学习率;
  • 在对神经网络最优化非凸的罚函数时,另一个通常面临的挑战,是如何避免目标函数被困在无数的局部最小值中,以导致的未完全优化的情况。Dauphin 认为,这个困难并不来自于局部最小值,而是来自于“鞍点”,也就是在一个方向上斜率是正的、在一个方向上斜率是负的点。这些鞍点通常由一些函数值相同的面环绕,它们在各个方向的梯度值都为0,所以SGD很难从这些鞍点中逃离。

4 梯度下降优化算法

下面介绍目前深度学习中常用的解决上述挑战的优化算法。这里没有介绍不适合于高维数据集的优化算法,例如牛顿法等二阶优化算法。

4.1 动量

SGD很难在具有陡峭边的峡谷 —— 一种在一个方向的弯曲程度远大于其他方向的弯曲程度的区域中实现良好的优化,但是这种情况在局部极小值处很常见。在峡谷中,如下图黑线所示,SGD在峡谷的窄轴上震荡,向局部极值处缓慢地前进。
在这里插入图片描述图 2

动量法,如图2中红线所示,会帮助SGD在正确优化方向上加速前进,并减小震荡。实现上是通过在原有项前添加一个参数 γ \gamma ,迭代公式如下所示:
v t = γ . v t 1 + η . θ J ( θ ) θ = θ v t v_t = \gamma.v_{t-1} + \eta.\nabla_{\theta}J(\theta) \\ \\ \theta = \theta - v_t
动量参数 γ \gamma 一般会设置为0.9或其他差不多的值。

动量算法累积了之前梯度指数级衰减的移动平均,并且继续沿着该方向移动。在拟合一组含有噪声但是有明显分布点的分布趋势时,可以用到指数加权平均这一方法,即每在第n次计算当前值时,都将前n-1的值通过加权的方式求和,使计算当前值能够保存前面的数据分布趋势,而又由于加权衰减,越早的数据影响越小,比直接用平均值的方式更优。

v 0 = 0 v t = γ . v t 1 + η . θ J ( θ ) v_0 = 0\\ v_t = \gamma.v_{t-1} + \eta.\nabla_{\theta}J(\theta)
假如 γ \gamma =0.9,则 v 1 = 0.9 v 0 + η . θ J ( θ ) , v 2 = 0.9 v 1 + η . θ J ( θ ) = ( 0.9 ) 2 v 0 + 0.9 v 1 + η . θ J ( θ ) , . . . v_1 = 0.9*v_0 + \eta.\nabla_{\theta}J(\theta),v_2=0.9*v_1 + \eta.\nabla_{\theta}J(\theta) = (0.9)^2*v_0 + 0.9*v_1 + \eta.\nabla_{\theta}J(\theta),...

由于对于小数 ε \varepsilon ,有 ( 1 ε ) 1 ε 0.35 1 e (1-\varepsilon)^{\frac 1 \varepsilon}\approx0.35\approx \frac 1e ,同时由于 v t v_t 实际上是由一个指数衰减函数和数据点做内积的结果,而当这个指数衰减减小到 1 e \frac 1e 后就可以近似忽略内积的值了,因此取到 t 之前一直到之前 1 ε \frac{1}{\varepsilon} 个数据点的平均就是 v t v_t 的近似了。由于, γ = 1 ε \gamma = 1-\varepsilon ,因此 v t v_t 约为前面 1 1 γ \frac{1}{1 - \gamma} 的数据点的指数衰减平均。指数加权平均的作用就是可以通过调节 γ \gamma 参数来调整当前 v t v_t 保存了前多少个t的信息。

当我们使用Mini Batch而是Batch梯度下降时,由于一次梯度下降的计算是由一部分数据样本获得的,因此在Cost平面中,这一次梯度下降移动的方向不一定是朝着全局最优点前进的,而是有一定偏差,这些偏差的结果就是梯度下降的路线有很多噪声和振荡,导致学习速度降低。

动量梯度下降就是为了解决振荡导致学习速度降低的问题。引入物理学中动量的概念,每一次梯度下降的方向和距离都受到之前梯度下降的方向和速度的影响,如下图所示,使用普通的梯度下降,每次下降的方向只与当前点计算的导数有关,存在振荡;而引入动量之后,W方向的分量由于一直是右移,因此积累的动量方向也是一直向右,从而使优化路线更快的走向右侧的最优点,而b方向的分量由于一直上下振荡,因此累积的动量基互相抵消,使优化路线更少的向b方向移动,结合在一起就能够提高学习速度。因此,我们得到了更快的收敛速度以及减弱的震荡。

在这里插入图片描述
一个直观感受是,寻找最优值的过程是小球滚到山底的过程,传统的梯度下降没有速度,即给小球一个推力,小球下降一段距离(梯度大小)就停止了,没有惯性,梯度是什么方向小球就往什么方向滚动;而动量梯度下降是小球在下降的过程中不断积累速度,每次算出的梯度是给小球往梯度方向碰了一下(加了一个冲量),最终小球的运行方向是有他以前积累的速度和冲量方向合成得到的。

如果前面 1 1 γ \frac{1}{1 - \gamma} 的梯度都是固定值g,那么 γ = 0.9 \gamma = 0.9 对应着最大速度10倍于梯度下降算法。

特点:

  • 下降初期时,使用上一次参数更新,下降方向一致,乘上较大的 γ \gamma 能够进行很好的加速;
  • 下降中后期时,在局部最小值来回震荡的时候,gradient -> 0,前期动量使得更新幅度增大,跳出陷阱;
  • 在梯度改变方向的时候,前期动量能够减少更新。总而言之,momentum项能够在相关方向加速SGD,抑制振荡,从而加快收敛。

4.2 Nesterov动量

当一个小球从山谷上滚下的时候,盲目的沿着斜率方向前行,其效果并不令人满意。我们需要有一个更“聪明”的小球,它能够知道它在往哪里前行,从而在知道斜率再度上升的时候减速。

Nesterov加速梯度法(NAG)是一种能给予梯度项上述“预测”功能的方法。我们将用 γ v t 1 \gamma v_{t-1} 来更新参数 θ \theta 。通过计算 θ γ v t 1 \theta - \gamma v_{t-1} ,我们能够得到一个下次参数位置的近似值,也就是告诉我们参数大致会变成多少。那么,通过基于未来参数的近似值而非当前参数计算损失函数 J ( θ γ v t 1 ) J(\theta - \gamma v_{t-1}) 并求偏导数,我们能够让优化器高效地“前进”并收敛。

更新准则为:
v t = γ v t 1 + η J ( θ γ v t 1 ) θ = θ v t v_t = \gamma v_{t-1} + \eta J(\theta - \gamma v_{t-1}) \\ \theta = \theta - v_t

一般情况下, γ \gamma 参数也设置为0.9。

在这里插入图片描述
如上图所示,动量法首先计算当前的梯度值(小蓝色向量),然后在更新的积累向量(大蓝色向量)方向前进一大步。但是NAG算法首先(试探性地)在之前积累的梯度方向(棕色向量)前进一大步,然后再根据当地的情况修正,以得到最终的前进方向(绿色向量)。这种基于预测的更新方法,使我们避免过快地前进,并提高了算法的响应能力,大大改进了RNN在一些任务上的表现。
在这里插入图片描述
现在我们现在能够根据损失函数的梯度值调整我们的参数更新,并能使用动量相应地加速SGD,但这样做的代价是引入了一个新的超参数。在这种情况下,我们自然会问有没有其他方法。如果我们相信方向敏感度在某种程度是轴对齐的,那么每个参数设置不同的学习率,在整个学习过程中自动适应这些学习率就是有道理的。

4.3 AdaGrad

Adagrad是一个基于梯度的优化算法,它的主要功能是:他对不同的参数调整学习率,具体而言,对低频出现的参数进行大的更新,对高频出现的参数进行小的更新。Dean等人发现,Adagrad法大大提升了SGD的鲁棒性,并在谷歌使用它训练大规模的神经网络,其诸多功能包括识Youtube视频中的猫。此外,Pennington等人使用它训练GloVe单词向量映射(Word Embedding),在其中不频繁出现的词语需要比频繁出现的词语需要更大的更新值。

在这之前,我们对于所有的 θ \theta 向量中的每一个参数 θ i \theta_i 使用相同的学习率进行更新。但Adagrad 则不然,对不同的训练迭代次数 t ,adagrad 对每个参数都有一个不同的学习率。我们首先考察adagrad每个参数的的更新过程,然后我们再使之向量化。为简洁起见,我们记在迭代次数 t 下,损失函数对参数 θ i θ_i 求梯度的结果为 g t , i g_{t,i}
g t , i = θ t J ( θ t , i ) g_{t,i} = \nabla_{\theta_t}J(\theta_{t},i)
那么,普通SGD参数更新的准则为:
θ t + 1 , i = θ t , i η . g t , i \theta_{t+1,i} = \theta_{t,i} - \eta.g_{t,i}
AdaGrad的更新规则是,不再使用固定的学习率,而是对迭代次数t,基于 θ i \theta_i 的历史梯度值决定每个参数的更新学习率:
θ t + 1 , i = θ t , i η G t , i i + ϵ . g t , i \theta_{t+1,i} = \theta_{t,i} - \frac{\eta}{\sqrt{G_{t,ii}} + \epsilon}.g_{t,i}
其中, G t R d × d G_t \in R^{d \times d} 是一个对角阵,其对角线上的元素 G t i i G_{t_{ii}} 是从开始到 t 时刻损失函数对于参数 θ i \theta_i 梯度的平方和。 ϵ \epsilon 是一个平滑项,避免分母为0,它的数量级通常是 1e-8。但是,如果不开方的话,这个算法会表现得很糟糕。

因为 G t G_t 在其对角线上,含有过去损失函数相对于参数 θ i \theta_i 梯度得平方和,我们可以利用一个 G t G_t g t g_t 之间元素对元素的向量乘法:
在这里插入图片描述
Adagrad主要优势之一,是它不需要对每个学习率手工地调节。大多数情况下只需要设置初始学习率为0.01,然后让其自适应进行学习率调整。

在这里插入图片描述

Adagrad的主要劣势,是他在分母上的 G t G_t 项中积累了平方梯度和。因为每次加入的项总是一个正值,所以累积的和将会随着训练过程而增大会导致有效学习率过早和过量的减小。因而,这会导致学习率不断缩小,并最终变为一个无限小值——此时,这个算法已经不能从数据中学到额外的信息。而下面的算法,则旨在解决这个问题。

特点:

  • 前期 g t g_t 较小的时候, regularizer较大,能够放大梯度;
  • 后期 g t g_t 较大的时候,regularizer较小,能够约束梯度;
  • 适合处理稀疏梯度.

缺点:

  • 由公式可以看出,仍依赖于人工设置一个全局学习率;
  • η \eta 设置过大的话,会使regularizer过于敏感,对梯度的调节太大;
  • 中后期,分母上梯度平方的累加将会越来越大,使gradient -> 0,使得训练提前结束.

4.4 Adadelta

Adadelta是AdaGrad的延伸,旨在解决AdaGrad中学习率单调下降的问题。相比于AdaGrad计算之前所有梯度的平方,Adadelta方法只计算一个大小为w的区间内梯度值的累计和。

Adadelta不会存储之前w个梯度的平方值,而是将梯度累积值按如下方式递归地定义:它被定义为关于过去梯度的衰减均值,当前时间 t 的梯度均值 E [ g 2 ] t E[g^2]_t 是基于过去梯度均值 E [ g 2 ] t 1 E[g^2]_{t-1} 和当前梯度值平方 g t 2 g^2_t 的加权平均,其中 γ \gamma 是类似于动量方法中的权重值。
E [ g 2 ] t = γ E [ g 2 ] t 1 + ( 1 γ ) g t 2 E[g^2]_t = \gamma E[g^2]_{t-1} + (1 - \gamma)g^2_t
与动量算法一样,我们设置参数 γ \gamma 为0.9左右的值。

所以,Adadelta的每次更新量为:
在这里插入图片描述
由于分母的表达式形式与梯度的方均根(root mean squared,RMS)形式类似,因而使用相应的简写来替换:
在这里插入图片描述
作者还注意到,在该更新中(在SGD、动量法或者Adagrad也类似)的单位并不一致,也就是说,更新值的量纲与参数值的假设量纲并不一致。为改进这个问题,他们定义了另外一种指数衰减的衰减均值,他是基于参数更新的平方而非梯度的平方来定义的:
在这里插入图片描述
因此,对该问题的方均根为:
在这里插入图片描述
因为 R M S [ Δ θ ] t RMS[\Delta \theta]_t 值未知,所以我们使用 t-1 时刻的方均根来近似。将前述规则中的学习率 η \eta 替换为 R M S [ Δ θ ] t 1 RMS[\Delta \theta]_{t-1} ,我们最终得到了Adadelta法的更新规则:
在这里插入图片描述
借助 Adadelta 法,我们甚至不需要预设一个默认学习率,因为它已经从我们的更新规则中被删除了。

特点:

  • 训练初中期,加速效果不错,很快;
  • 训练后期,反复在局部最小值附近抖动。

4.5 RMSprop

RMSprop 是由Geoff Hinton 在他 Coursera 课程中提出的一种适应性学习率方法,至今仍未被公开发表。

RMSprop 法和 Adadelta 法几乎同时被发展出来。他们 解决 Adagrad 激进的学习率缩减问题。实际上, RMSprop和我们推导出的 Adadelta 法第一个更规则相同:
在这里插入图片描述
RMSprop 也将学习率除以了一个指数衰减的衰减均值。Hinton 建议设定 γ \gamma 为0.9,对 η \eta 而言,0.001是一个较好的默认值。

经验上,RMSprop已经证明是一种有效且实用的深度神经网络优化算法。目前它是深度学习从业者经常采用的优化方法之一。
在这里插入图片描述
在这里插入图片描述
特点:

  • 其实RMSprop依然依赖于全局学习率;
  • RMSprop算是Adagrad的一种发展,和Adadelta的变体,效果趋于二者之间;
  • 适合处理非平稳目标- 对于RNN效果很好 。

4.6 Adam

Adam,“adaptive moments”,适应性动量估计法(Adam)是另一种能对不同参数计算适应性学习率的方法。除了存储类似 Adadelta 法或RMSprop 中指数衰减的过去梯度平方均值外,Adam 法也存储像动量法中的指数衰减的过去梯度值均值 :
在这里插入图片描述
m t m_t v t v_t 分别是梯度的一阶矩(均值)和二阶矩(方差),这也是Adam名字的由来。当 m t m_t v t v_t 一开始被初始化为0向量时,Adam的作者观察到,该方法会有趋向0的偏差,尤其是在最初的几步或是在衰减率很小(即 β 1 \beta_1 β 2 \beta_2 接近1)的情况下。

他们使用偏差纠正系数,来修正一阶矩和二阶矩的偏差:
在这里插入图片描述
他们使用这些来更新参数,更新规则很像我们在Adadelta 和 RMSprop 法中看到的一样,服从Adam的更新规则:
在这里插入图片描述
作者认为参数的默认值应设为: β 1 = 0.9 , β 2 = 0.99 , ϵ = 1 e 8 \beta_1 = 0.9,\beta_2 = 0.99,\epsilon = 1e-8 。经验表明,Adam在实践中表现很好,和其他适应性学习算法相比也比较不错。

在这里插入图片描述
特点:

  • 结合了Adagrad善于处理稀疏梯度和RMSprop善于处理非平稳目标的优点;
  • 对内存需求较小;
  • 为不同的参数计算不同的自适应学习率;
  • 也适用于大多非凸优化- 适用于大数据集和高维空间 。

4.7 AdaMax

Adam更新规则中计算 v t v_t 的时候,使用了 l 2 l_2 范数。这里将系数和范数都进行泛化,得到:
在这里插入图片描述
当p值很大时,会造成数值不稳定,这也是为什么实践中 l 1 l_1 l 2 l_2 最常见的原因。但是, l l_{\infty} 取向量的最大值,是比较稳定的。基于这个原因,就有了AdaMax优化算法,定义:
在这里插入图片描述
那么,AdaMax的更新规则为:
在这里插入图片描述
比较好的默认值是 η = 0.002 β 1 = 0.9 β 2 = 0.999 \eta = 0.002,\beta_1 = 0.9,\beta_2 = 0.999

4.8 Nadam

正如前面所示,Adam可以看作是RMSprop和动量的结合:RMSprop贡献了过去梯度平方的指数衰减平均,动量贡献了过去梯度的指数衰减平均。但是在前面的描述中,我们已经看到NAG(Nesterov动量)是优于动量方法的。

因此,Nadam是Adam和NAG的结合。为了将NAG应用到Adam,需要修改其动量项 m t m_t

首先,使用新的符号回忆下动量的更新公式:
(4.8.1) \tag{4.8.1}
上面的式子中, J J 是损失函数, γ \gamma 是动量衰减系数, η \eta 是学习率。把前两个式子代入第三个式子,可得:
在这里插入图片描述 (4.8.2) \tag{4.8.2}
上式再次表明,动量的更新受累计动量和当前梯度的综合影响。

NAG先更新动量,然后再计算梯度。也就是说基于预测的动量当前的近似位置来计算损失函数,再计算梯度,这样的更新过程会更加精确,其更新公式为:
在这里插入图片描述 (4.8.3) \tag{4.8.3}

对动量算法进行修改,不再是在单轮更新过程中两次使用动量 —— 一次用来更新梯度,另一次用来更新参数 θ t + 1 \theta_{t+1} ,而是用动量的预测值直接更新当前参数,更新公式为:
在这里插入图片描述 (4.8.4) \tag{4.8.4}

相比于公式(4.8.2)所示的动量算法的更新公式,修改后的动量算法,使用当前的动量 m t m_t 而不是之前的动量 m t 1 m_{t-1} 进行参数更新。

为了在Adam中添加NAG,我们也可以用当前动量替代原有动量。首先,回忆下Adam的更新公式:

在这里插入图片描述 (4.8.5) \tag{4.8.5}
将上式1,2代入3中可得:
在这里插入图片描述 (4.8.6) \tag{4.8.6}
注意上式中, β 1 m t 1 1 β 1 t \frac{\beta_1 m_{t-1}}{1 - \beta_1^t} 是前一次向量的偏差修正项。我们可以用 m ^ t 1 \hat{m}_{t-1} 进行代替,得到:
在这里插入图片描述 (4.8.7) \tag{4.8.7}
就像公式(4.8.4)所示的对动量法的修改一样,我们用修正后的当前动量 m ^ t \hat{m}_t 代替 m ^ t 1 \hat{m}_{t-1} ,得到Nadam的更新规则:
在这里插入图片描述 (4.8.8) \tag{4.8.8}

4.9 上述优化算法可视化

在这里插入图片描述在上图中,我们可以看到,在损失函数的等高线图中,优化器的位置随时间的变化情况。注意到,Adagrad、 Adadelta 及 RMSprop 几乎立刻就找到了正确前进方向并以相似的速度很快收敛。而动量法和 NAG 法,则找错了方向,让小球沿着梯度下降的方向前进。但 NAG 法能够很快改正它的方向向最小指出前进, 因为他能够往前看并对前面的情况做出响应。

在这里插入图片描述上图展现了各算法在鞍点附近的表现。如上面所说,这对对于SGD 法、动量法及 NAG 法制造了一个难题。他们很难打破”对称性“带来的壁垒,尽管最后两者设法逃脱了鞍点。而 Adagrad 法、RMSprop 法及 Adadelta 法都能快速的沿着负斜率的方向前进。

如我们所见,自适应性学习率方法,也就是 Adagrad 法、Adadelta 法 、RMSprop 法及 Adam 法最适合处理上述情况,并有最好的收敛效果。

在这里插入图片描述

4.10 如何选择优化算法

那么,我们该如何选择优化器呢?如果你的输入数据较为稀疏(sparse),那么使用适应性学习率类型的算法会有助于你得到好的结果。此外,使用该方法的另一好处是,你在不调参、直接使用默认值的情况下,就能得到最好的结果。

总的来说,RMSprop 法是一种基于Adagrad 法的拓展,他从根本上解决学习率骤缩的问题。Adadelta 法于 RMSprop 法大致相同,除了前者分子中使用了更新量的RMS。而Adam法,则基于RMSprop法添加了偏差修正项和动量项。在我们地讨论范围中,RMSprop、Adadelta及Adam法都是非常相似地算法,在相似地情况下都能做的很好。Kingma及其他人展示了他们的偏差修正项帮助Adam法,在最优化过程快要结束、梯度变得越发稀疏的时候,表现略微优于RMSprop法。总的来说,Adam也许是总体来说最好的选择。

有趣的是,很多最新的论文,都直接使用了(不带动量项的)SGD法,配合一个简单的学习率(退火)列表。如论文所示,这些 SGD 最终都能帮助他们找到一个最小值,但会花费远多于上述方法的时间。并且这些方法
非常依赖于鲁棒的初始化值及退火列表。因此,如果你非常在你的模型能快速收敛,或是你需要训练一个深度或复杂模型,你可能需要选择上述的自适应性学习率模型。

经验之谈

  • 对于稀疏数据,尽量使用学习率可自适应的优化方法,不用手动调节,而且最好采用默认值;
  • SGD通常训练时间更长,但是在好的初始化和学习率调度方案的情况下,结果更可靠;
  • 如果在意更快的收敛,并且需要训练较深较复杂的网络时,推荐使用学习率自适应的优化方法;
  • Adadelta,RMSprop,Adam是比较相近的算法,在相似的情况下表现差不多;
  • 在想使用带动量的RMSprop,或者Adam的地方,大多可以使用Nadam取得更好的效果。

5 平行式和分布式SGD

现如今,大规模数据集随处可见、小型计算机集群也易于获得。因而,使用分布式方法进一步加速 SGD 是一个惯常的选择。

SGD 它本事是序列化的:通过一步一步的迭代,我们最终求到了最小值。运行它能够得到不错的收敛结果,但是它的运行速度很慢,特别是对于大规模的数据集。相比而言,异步 SGD 的运行速度相对较快,但在不同的工作机之间的关于非完全优化的沟通可能会导致较差的收敛结果。此外,我们能够对 SGD 进行平行运算而不需要一个计算机集群。下文讨论了相关的算法或架构,它们或关于平行计算或者对其进行了分布式优化。

5.1 Hogwild!

Niu 等人提出了一种叫做 Hogwild! 的更新规则,它允许在CPU 上进行 平行 SGD 更新。处理器可以在不独占参数操作权的情况下访问共享内存。这仅能在输入数据集是稀疏时起效,在每次更新过程中仅会修正一部分的参数值。他们展示了,在这种情况下,这个更新规则达到了最优化的收敛速度,因为处理器不太会覆盖有用的信息。

5.2 Downpour SGD

Downpour SGD 是一个异步的 SGD 法变体,它被 Dean 等人用在了谷歌的 DistBelief 架构中(它是TensorFlow的前身)。他对训练集的子集同步地运行模型的多个副本。这些模型将它们的更新值发送到参数服务器,服务器被分为了许多台主机。每一台主机都负责存储和更新模型的一部分参数。但是,副本之间却没有相互的通信——例如,共享权重值或者更新值——模型参数面临着发散的风险,会阻止收敛。

5.3 容忍延迟的 SGD 算法

McMahan 和 Streeter 改良了 AdaGrad 法使之能够用于平行运算的场景。通过实现延迟容忍的算法,它不仅能能够适应于过去的梯度,还能够适应于更新的延迟。在实践中,它的表现很好。

5.4 TensorFlow

TensorFlow是谷歌最近开源的一个实现和部署大规模机器学习模型的架构。它基于他们之前对于使用DistBelief 的经验,并已在内部被部署在一系列的移动设备及大规模的分布式系统上进行计算。为了分布式执行,
一个计算图被分为了许多子图给不同的设备,设备之间的通信使用了发送和接受节点对。

5.5 弹性平均梯度下降法

张等人提出了弹性平均梯度下降法(EASGD),他使不同工作机之间不同的SGD以一个”弹性力“连接,也就是一个储存于参数服务器的中心变量。这允许局部变量比中心变量更大地波动,理论上允许了对参数空间更多的探索。他们的经验表明,提高的探索能力有助于在寻找新的局部极值中提升(优化器的)表现。

6 优化SGD的其他手段

最后,我们将讨论一些其他手段,他们可以与前述的方法搭配使用,并能进一步提升SGD的效果。

6.1 Shuffling和Curriculum Learning

总体而言,我们希望避免训练样本以某种特定顺序传入到我们的学习模型中,因为这会向我们的算法引入偏差。因此,在每次迭代后,对训练数据集中的样本进行重排(shuffling),会是一个不错的注意。

另一方面,在某些情况下,我们会需要解决难度逐步提升的问题。那么,按照一定的顺序遍历训练样本,会有助于改进学习效果及加快收敛速度。这种构建特定遍历顺序的方法,叫做Curriculum Learning.

6.2 批归一化

我们通常设置我们参数初值的均值和方差分别为0和单位值,以帮助模型进行学习。随着学习过程的进行,每个参数被不同程度地更新,相应地,参数的正则化特征也随之失去了。因此,随着训练网络的越来越深,训练的速度会越来越慢,变化值也会被放大。

批量标准化对每小批数据都重新进行标准化,并也会在操作中逆传播(back­propgate)变化量。在模型中加入批量标准化后,我们能使用更高的学习率且不要那么在意初始化参数。此外,批量正则化还可以看作是一种正则化手段,能够减少(甚至去除)留出法的使用。

6.3 early stop

诚如Geoff Hinton所言:“Early stopping (is) beautiful free lunch(早停是美妙的免费午餐,又简单效果又好)”(NIPS 2015 Tutorial Sildes, Slide 63)。在训练过程中,你应该时刻关注模型在验证集上的误差情况,并且
在该误差没有明显改进的时候停止训练。

6.4 梯度噪声

Neelakentan等人在每次梯度的更新中,向其中加入一个服从合高斯分布 N ( 0 , σ 2 ) N(0,σ^2) 的噪声值:
在这里插入图片描述
并按照如下的方式修正方差:
在这里插入图片描述
他们指出,这种方式能够提升神经网络在不良初始化前提下的鲁棒性,并能帮助训练特别是深层、复杂的神经网络。他们发现,加入噪声项之后,模型更有可能发现并跳出在深度网络中频繁出现的局部最小值。

7 参考

《An overview of gradient descent optimization algorithms》
https://blog.csdn.net/JeremyCzh/article/details/79800824
https://zhuanlan.zhihu.com/p/43438597
《深度学习》

猜你喜欢

转载自blog.csdn.net/cdknight_happy/article/details/84835809