深度学习之常用优化策略(一阶)

对于优化算法,优化的目标是网络模型中的参数集合$\theta_1, \theta_2,\dots ,\theta_m$。目标函数为损失函数$L = \frac{1}{N}\sum L_i$(每个样本损失函数的叠加求均值)。在实际的训练过程中,参数$\theta$就相当于这个损失函数$L$的变量,而参与训练的样本集和训练方案则决定了最终能够学得的损失函数。

梯度下降法,是当今最流行的优化(optimization)算法,亦是至今最常用的优化神经网络的方法。深度学习之神经网络模型的基本工作原理中,用一个线性回归的例子,简单地讲解了梯度下降算法是如何对模型进行优化的。下面就机器学习中常用的各类优化策略进行系统地讲解与归纳,图文内容大部分整理自网络,出处附在文末。

梯度下降

最新的深度学习程序库都包含了各种优化梯度下降的算法(可以参见如lasagne、caffe及Kera等程序库的说明文档),但它们的算法不被公开,都作为黑箱优化器被使用,这也就是为什么它们的优势和劣势往往难以被实际地解释。

核心思想

梯度下降法的核心,是最小化目标函数$J(\theta)$,其中$\theta$是模型的参数,$\theta \in R_d$。它的方法是,在每次迭代中,对每个变量,按照目标函数在该变量梯度的相反方向,更新对应的参数值。其中,学习率$\eta$决定了函数到达(局部)最小值的迭代次数。换句话说,我们在目标函数的超平面上,沿着斜率下降的方向前进,直到我们遇到了超平面构成的「谷底」。

接下来讨论的三种梯度下降法的变体,它们的不同之处在于,一次性使用多少数据来计算目标函数的梯度。对于不同的数据量,我们需要在参数更新准确性参数更新花费时间两方面做出权衡

BGD

Vanilla 梯度下降法(Vanilla 是早期机器学习算法相关的名词,也是如今一个机器学习python程序库的名字,在该处指的是后者,参见:https://github.com/vinhkhuc/VanillaML),也就是大家所熟知的批量梯度下降(Batch Gradient Descent),在整个数据集上对每个参数$\theta$求目标函数$J(\theta)$的偏导数:$$\theta = \theta - \eta\cdot \nabla_{\theta}J(\theta)$$

优缺点

在该方法中,每次更新我们都需要在整个数据集上求出所有的偏导数。因此批量梯度下降法的速度会比较慢,甚至对于较大的、内存无法容纳的数据集,该方法都无法被使用。同时,梯度下降法不能以「在线」的形式更新我们的模型,也就是不能运行中加入新的样本进行运算

代码解读

for i in range(nb_epochs):
  params_grad = evaluate_gradient(loss_function, data, params)
  params = params - learning_rate * params_grad
View Code

对于给定的迭代次数,我们首先基于输入的罚函数 loss_function 对输入的参数向量 params 计算梯度向量 params_grad 。注意,最新的深度学习程序库中,提供了自动求导的功能,能够高效、快速地求给定函数对于特定参数的导数。如果你希望自己写代码求出梯度值,那么「梯度检查」会是一个不错的注意。然后,我们对参数减去梯度值乘学习率的值,也就是在反梯度方向,更新我们参数。当目标函数$J(\theta)$是一凸函数时,则批量梯度下降法必然会在全局最小值处收敛;否则,目标函数则可能会局部极小值处收敛

SGD

相比批量梯度下降法,随机梯度下降(Stochastic gradient descent)的每次更新,是对数据集中的一个样本$(x, y)$求出罚函数,然后对其求相应的偏导数:

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

优缺点

因为批量梯度下降法在每次更新前,会对相似的样本求算梯度值,所以它在较大的数据集上的计算会有些冗余(redundant)。而随机梯度下降法通过每次更新仅对一个样本求梯度,去除了这种冗余的情况。因而,它的运行速度被大大加快,同时也够「在线」学习随机梯度下降法更新值的方差很大,在频繁的更新之下,它的目标函数有着如下图所示的剧烈波动

相比批量梯度下降法的收敛会使目标函数落入一个局部极小值,SGD收敛过程中的波动,会帮助目标函数跳入另一个可能的更小的极小值。另一方面,这最终会让收敛到特定最小值的过程复杂化,因为该方法可能持续的波动而不停止。但是,当我们慢慢降低学习率的时候,SGD 表现出了与批量梯度下降法相似的收敛过程,也就是说,对非凸函数和凸函数,必然会分别收敛到它们的极小值和最小值。

代码解读

相比批量梯度下降法的代码,在如下的代码中,我们仅仅加入了一个循环,用以遍历所有的训练样本并求出相应的梯度值。注意,如这里所说,在每次迭代中,我们会打乱训练数据集。

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

Mini-Batch Gradient Descent

小批量梯度下降(Mini-batch gradient descent)集合了上述两种方法的优势,在每次更新中,对$n$个样本构成的一批数据,计算罚函数$J(\theta)$,并对相应的参数求导:$$\theta = \theta - \eta\cdot \nabla_{\theta}J(\theta;x^{(i:i+n)}; y^{(i:i+n)})$$

优点

这种方法,(a) 降低了更新参数的方差(variance),使得收敛过程更为稳定;(b) 能够利用最新的深度学习程序库中高度优化的矩阵运算器高效地求出每小批数据的梯度。

通常一小批数据含有的样本数量在$50$至$256$之间,但对于不同的用途也会有所变化。小批量梯度下降法,通常是我们训练神经网络首选算法。同时,有时候我们也会使用随机梯度下降法,来称呼小批量梯度下降法。

【注意:在下文中,我们就统一用SGD代称小批量随机梯度下降法。而对于随机梯度法优化的介绍中,为方便起见,我们也会省略式子中的参数$x(i:i+n), y(i:i+n)$】

缺点

  • 小批量梯度下降法并不能保证良好地收敛,我们对所有的参数都采用了相同的学习率。但如果我们的数据比较稀疏,同时特征有着不同的出现频率,那么我们不希望以相同的学习率来更新这些变量,我们希望对较少出现的特征有更大的学习率。这使得选择适当的学习率成为了一个难题。太小的学习率会导致较慢的收敛速度,而太大的学习率则会阻碍收敛,并会引起罚函数在最小值处震荡,甚至有可能导致结果发散。【我们可以设置一个关于学习率地列表,通过如退火的方法,在学习过程中调整学习率——按照一个预先定义的列表、或是当每次迭代中目标函数的变化小于一定阈值时来降低学习率。但这些列表或阈值,需要根据数据集地特性,被提前定义。】
  • 在对神经网络最优化非凸的罚函数时,另一个通常面临的挑战,是如何避免目标函数被困在无数的局部最小值中,以导致的未完全优化的情况。Dauphin等人认为,这个困难并不来自于局部最小值,而是来自于「鞍点」,也就是在一个方向上斜率是正的、在一个方向上斜率是负的点。这些鞍点通常由一些函数值相同的面环绕,它们在各个方向的梯度值都为0,所以SGD很难从这些鞍点中脱开(会在鞍点或者局部最小点震荡跳动,因为在此点处,如果是训练集全集带入即BGD,则优化会停止不动,如果是mini-batch或者SGD,每次找到的梯度都是不同的,就会发生震荡,来回跳动)。【鞍点就是:一个光滑函数的鞍点邻域的曲线,曲面,或超曲面,都位于这点的切线的不同边。例如这个二维图形,像个马鞍:在x-轴方向往上曲,在y-轴方向往下曲,鞍点就是$(0, 0)$】

代码解读

如下的代码所示,我们不再对每个样本进行循环,而是对每批带有50个样本的小批数据进行循环:

for i in range(nb_epochs):
  np.random.shuffle(data)
  for batch in get_batches(data, batch_size=50):
    params_grad = evaluate_gradient(loss_function, batch, params)
    params = params - learning_rate * params_grad
View Code

面临的挑战

总结三种梯度下降算法面临的问题与挑战,除了学习率局部极小值或鞍点这两方面,还有一个困扰神经网络训练的问题——病态曲率。 

虽然局部极小值和鞍点会阻碍我们的训练,但病态曲率会减慢训练的速度,以至于从事机器学习的人可能会认为搜索已经收敛到一个次优的极小值。让我们深入了解什么是病态曲率。

病态曲率

考虑以下损失曲线图

如你所知,我们在进入一个以蓝色为标志的像沟一样的区域之前是随机的。这些颜色实际上代表了在特定点上的损失函数的值,红色代表最高的值,蓝色代表最低的值。我们想要下降到最低点,因此,需要穿过峡谷。这个区域就是所谓的病态曲率。为了了解为何将其称为病态曲率,让我们再深入研究。放大了看,病态曲率就像这样...

要知道这里发生的事情并不难。梯度下降沿着峡谷的山脊反弹,向最小的方向移动的速度非常慢。这是因为山脊的曲线在$W1$方向上弯曲的更陡。

考虑山脊表面的$A$点。我们看到,梯度在这点可以分解为两个分量,一个沿着$w1$方向,另外一个沿着$w2$方向。如果$f$显著下降的唯一方向是低曲率的,那么优化可能会变得太慢而不切实际,甚至看起来完全停止,造成局部最小值的假象。  

正常情况下,我们使用一个较慢的学习率来解决这种山脊间反弹的问题,然而,这却产生了麻烦:当我们接近最小值时,慢下来是有意义的,我们想要收敛于它。但是考虑一下梯度下降进入病态曲率的区域,以及到最小值的绝对距离。如果我们使用较慢的学习率,可能需要花费更多的时间才能到达极小值点。事实上,有研究论文报道过使用足够小的学习率来阻值山脊间的反弹可能导致参与者以为损失根本没有改善,从而放弃训练。

「如果$f$显著下降的唯一方向是低曲率的,那么优化可能会变得太慢而不切实际,甚至看起来完全停止,造成局部最小值的假象。」  

也许我们想要的是能让我们慢慢进入病态曲率底部的平坦区域,然后在最小值的方向上加速。二阶导数可以帮助我们做到这一点。

梯度下降是一阶优化方法,牛顿法是针对二阶问题的方法,不能应用于高维数据集,不太适用于深度学习的应用场景,所以在这里不讨论。关于牛顿法的更详细内容见机器学习之优化策略(二阶)

优化算法

针对上述挑战,接下来列举一些应对的优化算法。它们被广泛应用于深度学习社区,我们将研究它们的解决这些挑战的动机及推导出更新规律(update rules)的过程。

Momentum

SGD方法的一个缺点是其更新方向完全依赖于当前batch计算出的梯度,因而十分不稳定。比如在陡谷(ravines)「一种在一个方向的弯曲程度远大于其他方向的表面弯曲情况的曲面」中,SGD很难找到正确的更新方向(如下图左所示,SGD 在陡谷的周围震荡,向局部极值处缓慢地前进)。

而这种陡谷,经常在局部极值中出现。在这种情况下,Momentum动量法借用了物理中的动量概念,它模拟的是物体运动时的惯性,即更新的时候在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。这样一来,不仅可以在一定程度上增加SGD的稳定性,减少震荡,而且可以帮助它在相关方向加速前进,从而学习地更快。同时,还有一定摆脱局部最优的能力(如上图右)。Momentum通过修改公式中,在原有项前增加一个折损系数$\gamma$来实现这样的功能:$$\begin{cases} v_t &= \gamma v_{t-1} + \eta\nabla_{\theta}J(\theta) \\ \theta &= \theta - v_t \end{cases}$$

一般超参数$\gamma$被设置为$0.9$或为其他差不多的值。

优缺点

从本质上说,Momentum就仿佛我们从高坡上推下一个球,小球在向下滚动的过程中积累了动量,在途中它变得越来越快(直到它达到了峰值速度,如果有空气阻力的话,$\gamma<1$)。在我们的算法中,相同的事情发生在我们的参数更新上:动量项在梯度指向方向相同的方向逐渐增大,对梯度指向改变的方向逐渐减小。由此,我们得到了更快的收敛速度以及减弱的振荡

缺点是这种情况相当于小球从山上滚下来时是在盲目地沿着坡滚,如果它能具备一些先知,例如快要上坡时,就知道需要减速了的话,适应性会更好

公式解析

修改后的梯度下降公式如下所示:$$\begin{align} &Repeat\ Until\ Convergence\ \{ \\ &\qquad \qquad  v_j \gets \gamma * v_j - \eta * \nabla_{\theta} J(\theta) \\ &\qquad \qquad \theta_j \gets \theta_j + v_j \\ &\} \end{align}$$

更新迭代的第一个式子有两项。第一项是上一次迭代的梯度$v_j$,乘上一个被称为「Momentum 系数」的值$\gamma$,可以理解为取上次梯度的比例。

我们设$v$的初始为$0$,动量系数为$0.9$,那么迭代过程如下:$$\begin{align} v_1 &= -G_1 \\ v_2 &= -0.9 * G_1 - G_2 \\ v_3 &= -0.9 * (0.9 * G_1 - G_2) - G_3 &= -0.81 * (G_1) - (0.9) * G_2 - G_3 \end{align}$$

我们可以看到之前的梯度会一直存在后面的迭代过程中,只是越靠前的梯度其权重越小。(说的数学一点,我们取的是这些梯度步长的指数平均)

这对我们的例子有什么帮助呢?观察下图,注意到大部分的梯度更新呈锯齿状。我们也注意到,每一步的梯度更新方向可以被进一步分解为$w1$和$w2$分量。如果我们单独的将这些向量求和,沿$w1$方向的的分量将抵消,沿$w2$方向的分量将得到加强。

深度学习优化入门:Momentum、RMSProp 和 Adam

对于权值更新来说,将沿着$w2$方向进行,因为$w1$方向已抵消。这就可以帮助我们快速朝着极小值方向更新。所以,动量也被认为是一种抑制迭代过程中锯齿下降问题的技术。

这种方法还可以提高收敛速度,但如果超过极小值,可能需要使用模拟退化算法。我们通常初始化动量为$0.5$,并且在一定循环次数后逐渐退火到$0.9$。

Nesterov Accelerated Gradient

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

Nesterov加速梯度法(NAG)是一种能给予梯度项上述「预测」功能的方法。我们知道,我们使用动量项$\gamma v_{t-1}$来「移动」参数项$\theta$。通过计算$\theta - \gamma v_{t-1}$,我们能够得到一个下次参数位置的近似值——也就是能告诉我们参数大致会变为多少。那么,通过基于未来参数的近似值而非当前的参数值计算相得应罚函数$J(\theta - \gamma v_{t-1})$并求偏导数,我们能让优化器高效地“前进”并收敛:$$\begin{align} v_t &= \gamma v_{t-1} + \eta\nabla_{\theta}J(\theta - \gamma v_{t-1}) \\ \theta &= \theta - v_t \end{align}$$

在该情况下,我们依然设定动量系数$\gamma$在$0.9$左右。

优缺点

如下图所示,动量法Momentum首先计算当前的梯度值(小蓝色向量),然后在更新的积累向量(大蓝色向量)方向前进一大步。但NAG法则首先(试探性地)在之前积累的梯度方向(棕色向量)前进一大步,再根据当前的情况修正,以得到最终的前进方向(绿色向量)。这种基于预测的更新方法,使我们避免过快地前进,并提高了算法的响应能力(responsiveness),大大改进了RNN在一些任务上的表现。

其实,Momentum和Nesterov都是为了使罚函数梯度值的更新更加灵活,并能相应地加速SGD。同样的,我们也希望能够对罚函数中的每个参数调整我们的更新值,基于它们的重要性以进行或大或小的更新。 比如人工设置的一些学习率总还是有些生硬,接下来介绍几种自适应学习率的方法。

Adagrad

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

在这之前,我们对于所有的参数使用相同的学习率进行更新。但Adagrad则不然,对不同的训练迭代次数$t$,Adagrad对每个参数都有一个不同的学习率。我们首先考察Adagrad每个参数的的更新过程,然后我们再使之向量化。为简洁起见,我们记在迭代次数$t$下,对参数$\theta_i$求目标函数$J(\theta)$梯度的结果为$g_t, i$:$$g_{t,i} = \nabla_{\theta}J(\theta_i)$$

如果是普通的SGD, 那么$θ_i$在每一时刻的梯度更新公式为:$$\theta_{t+1, i} = \theta_{t,i} - \eta\cdot g_{t,i}$$

而Adagrad将学习率$\eta$进行了修正,对迭代次数$t$,基于每个参数之前计算的梯度值,将每个参数的学习率$\eta$按如下方式修正:$$\theta_{t+1,i} = \theta_{t,i} - \frac{\eta}{\sqrt{G_{t,ii} + \epsilon}} \cdot g_{t,i}$$

其中$G_t$是一个对角阵,其中对角线上的元素$(G_t)_{ii}$对应参数$\theta_i$从第$1$轮到第$t$轮梯度的平方和。$\epsilon$是一个平滑项,以避免分母为$0$的情况,它的数量级通常在$1\mathrm{e}-8$。从公式可以看出,对$G_{t,ii}$进行一个递推形成一个约束项Regularizer$$-\frac{\eta}{\sqrt{G_{t,ii} + \epsilon}}$$

  • 前期$G_{t,ii}$较小的时候, Regularizer较大,能够放大梯度
  • 后期$G_{t,ii}$较大的时候,Regularizer较小,能够约束梯度
  • 适合处理稀疏梯度

超参数$\eta$通常选取$0.01$。

优缺点

Adagrad的主要优点是不需要对每个学习率手工地调。而大多数算法,只是简单地使用一个相同的默认值如$0.1$来避免这样地情况。

从公式可以看出,Adagrad仍依赖于人工设置一个全局学习率$\eta$,如果设置过大的话,会使Regularizer过于敏感,对梯度的调节太大。同时,它在分母上的$G_t$项中积累了平方梯度和,因为每次加入的项总是一个正值,所以累积的和将会随着训练过程而增大。到训练中后期,这会导致学习率不断缩小,并最终变为一个无限小值——此时,这个算法已经不能从数据中学到额外的信息。而下面的算法,则旨在解决这个问题。

Adadelta

Adadelta是Adagrad的一个改进,它旨在解决学习率不断单调下降的问题。相比Adagrad计算之前所有梯度值的平方和,Adadelta仅计算在一个大小为$w$的时间区间内梯度值的累积和

但该方法并不会存储之前$w$个梯度的平方值,而是将梯度值累积值按如下的方式递归地定义:它被定义为关于过去梯度值的衰减均值(decade average),当前时间$t$的梯度均值$E[g^2]_t$是基于过去梯度均值$E[g^2]_{t-1}$和当前梯度值平方$g_t^2$的加权平均,其中$\gamma$是类似上述动量项的权值。

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

与动量项的设定类似,我们设定$gamma$为以$0.9$左右的值。为明确起见,我们将我们的SGD更新规则写为关于参数更新向量$\Delta \theta_t$的形式:$$\begin{align} \Delta\theta_t &= -\eta \cdot g_{t,i} \\ \theta_{t+1} &= \theta_t + \Delta\theta_t \end{align}$$

由此,我们刚刚在Adagrad中推导的的参数更新规则的向量表示,变为如下形式:$$\Delta\theta_t = -\frac{\eta}{\sqrt{G_t + \epsilon}} \odot g_t$$

我们现在将其中的对角矩阵$G_t$用上述定义的基于过去梯度平方和的衰减均值$E[g^2]_t$替换:$$\Delta\theta_t = -\frac{\eta}{\sqrt{E[g^2]_t + \epsilon}} \odot g_t$$

因为分母表达式的形式与梯度值的方均根(root mean squared, RMS)形式类似,因而我们使用相应的简写来替换:$$\Delta\theta_t = -\frac{\eta}{RMS[g]_t} \odot g_t$$

作者还注意到,在该更新中(在 SGD、动量法或者 Adagrad 也类似)的单位并不一致,也就是说,更新值的量纲与参数值的假设量纲并不一致。为改进这个问题,他们定义了另外一种指数衰减的衰减均值,它是基于参数更新的平方而非梯度的平方来定义的:$$E[\Delta\theta^2]_t = \gamma E[\Delta\theta^2]_{t-1} + (1-\gamma)\Delta\theta^2_t$$

因此,对该问题的方均根为:$$RMS[\Delta\theta]_t = \sqrt{E[\Delta\theta^2]_t + \epsilon}$$

因为$RMS[\Delta\theta]_t$值未知,所以我们使用$t-1$时刻的方均根来近似。将前述规则中的学习率$\eta$替换为$RMS[\Delta\theta]_{t-1}$,我们最终得到了Adadelta法的更新规则

$$\begin{align} \Delta\theta_t &= -\frac{RMS[g]_{t-1}}{RMS[g]_t} g_t \\ \theta_{t+1} &= \theta_t + \Delta\theta_t \end{align}$$

借助Adadelta法,我们甚至不需要预设一个默认学习率,因为它已经从我们的更新规则中被删除了。

优缺点

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

RMSprop

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

RMSprop和Adadelta几乎同时被发展出来,都是为了解决 Adagrad 激进的学习率缩减问题。实际上,RMSprop和我们推导出的Adadelta第一个更规则相同:$$\begin{align} E[g^2]_t &= 0.9E[g^2]_{t-1} + 0.1g^2_t \\ \theta_{t+1} &= \theta_t - \frac{\eta}{\sqrt{E[g^2]_t + \epsilon}} g_t \end{align}$$

RMSprop也将学习率除以了一个指数衰减的衰减均值,旨在消除梯度下降中的摆动,与Momentum的效果一样,某一维度的导数比较大,则指数加权平均就大,某一维度的导数比较小,则其指数加权平均就小,这样就保证了各维度导数都在一个量级,进而减少了摆动。允许使用一个更大的学习率$\eta$。

Hinton建议设定$\gamma$为$0.9$,对$\eta$而言,$0.001$是一个较好的默认值。

RMSprop可以算作Adadelta的一个特例,当$\rho = 0.5$时,$E[g^2]_t = \rho * E[g^2]_{t-1} + (1-\rho) * g_t^2$就变为了求梯度平方和的平均数。

如果再求根的话,就变成了RMS(均方根):$$RMS|g|_t = \sqrt{E|g^2|_t + \epsilon}$$

此时,这个$RMS$就可以作为学习率$\eta$的一个约束:$$\Delta x_t = -\frac{\eta}{RMS|g|_t} * g_t$$

优缺点

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

Adaptive Moment Estimation

Adaptive Moment Estimation简称Adam,中文名适应性动量估计法,是另一种能对不同参数计算适应性学习率的方法。除了存储类似Adadelta或RMSprop中指数衰减的过去梯度平方均值$v_t$外,Adam也存储Momentum中的指数衰减的过去梯度值均值$m_t$ :$$\begin{align} m_t &= \beta_1m_{t-1} + (1-\beta_1)g_t \\ v_t &= \beta_2v_{t-1} + (1-\beta_2)g_t^2 \end{align}$$

$m_t$和$v_t$分别是梯度的一阶矩(均值)二阶矩(表示不确定度的方差),可以看做对期望$E[g_t]$和$E[g_t^2]$的近似。

Adam的作者观察到,当$m_t$和$v_t$一开始被初始化为$0$向量时,该方法会有趋向$0$的偏差,尤其是在最初的几步或是在衰减率很小(即$\beta_1$和$\beta_2$接近$1$)的情况下。所以,他们使用偏差纠正系数来修正一阶矩和二阶矩的偏差:$$\begin{align} \hat{m_t} &= \frac{m_t}{1-\beta_1^t} \\ \hat{v_t} &= \frac{v_t}{1-\beta_2^t} \end{align}$$

$\hat{m_t}$和$\hat{v_t}$是对$m_t, v_t$的校正,更新规则跟我们在Adadelta和RMSprop法中看到的一样,服从Adam的更新规则:$$\theta_{t+1} = \theta_t - \frac{\eta}{\sqrt{\hat{v_t}} + \epsilon}\hat{m_t}$$

可以看出,直接对梯度的矩估计对内存没有额外的要求,而且可以根据梯度进行动态调整,而$-\frac{\hat{m_t}}{\sqrt{\hat{n_t}}+\epsilon}$对学习率形成一个动态约束,而且有明确的范围。

作者建议参数的默认值应设为:$\beta_1 = 0.9, \beta_2 = 0.999, \epsilon = 10^{-8}$。他们的经验表明,Adam在实践中表现很好,和其他适应性学习算法相比也比较不错。

优缺点

数据比较稀疏的时候,adaptive的方法能得到更好的效果,例如Adagrad,RMSprop, Adam 等。而Adam 方法也会比 RMSprop方法收敛的结果要好一些, 所以在实际应用中 ,结合了Adagrad善于处理稀疏梯度和RMSprop善于处理非平稳目标的优点的Adam为最常用的方法,可以比较快地得到一个预估结果。

Adam为不同的参数计算不同的自适应学习率,而经过偏置校正后,每一次迭代学习率都有个确定范围,使得参数比较平稳。同时,它对内存的需求较小,也适用于大多非凸优化 - 适用于大数据集和高维空间

Adamax

Adamax是Adam的一种变体,此方法对学习率的上限提供了一个更简单的范围。公式的变化如下:$$\begin{align} n_t &= \max(v * n_{t-1}, |g_t|) \\ \Delta x &= -\frac{\hat{m_t}}{n_t + \epsilon} * \eta \end{align}$$

可以看出,Adamax学习率的边界范围更简单

Nadam

Nadam类似于带有Nesterov动量项的Adam。公式如下:$$\begin{align} \hat{g_t} &= \frac{g_t}{1 - \prod_{i=1}^{t}\mu_i} \\ m_t &= \mu_t * m_{t-1} + (1-\mu_t) * g_t \\ \hat{m_t} &= \frac{m_t}{1-\prod_{i=1}^{t+1}\mu_i} \\ n_t &= v * n_{t-1} + (1-v) * g_t^2 \\ \hat{n_t} &= \frac{n_t}{1-v_t} \\ overline{m}_t &= (1-\mu_t) * \hat{g_t} + \mu_{t+1} * \hat{m_t} \\ \Delta\theta_t &= -\eta * \frac{\overline{m}_t}{\sqrt{\hat{n_t}} + \epsilon}\end{align}$$

可以看出,Nadam对学习率有了更强的约束,同时对梯度的更新也有更直接的影响。一般而言,在想使用带动量的RMSprop或者Adam的地方大多可以使用Nadam取得更好的效果

算法可视化

如下的两个动画(图像版权:Alec Radford)给了我们关于特定优化算法在优化过程中行为的直观感受。你可以参见这里,以获取Karpathy对相同图像的一些描述,及另关于一些相关算法的细致讨论。

SGD optimization on loss surface contours

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

 

 

 

SGD optimization on saddle point

上图展现了各算法在鞍点附近的表现。如前述分析可知,这对于 SGD 、Momentum及NAG法制造了一个难题。他们很难打破「对称性」带来的壁垒,尽管最后两者设法逃脱了鞍点。而 Adagrad 法、RMSprop 法及 Adadelta 法都能快速的沿着负斜率的方向前进

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

如何选择优化器?

仅供参考的建议:

(1)如果你的输入数据较为稀疏(sparse),那么使用适应性学习率类型的算法会有助于你得到好的结果(即 Adagrad, Adadelta, RMSprop, Adam)。此外,使用该方法的另一好处是,你在不调参、直接使用默认值的情况下,就能得到最好的结果。

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

(3)在想使用带动量的RMSprop,或者Adam的地方,大多可以使用Nadam取得更好的效果

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

(5)尽管 Adam算法在论文中被认为是最有前景的算法,但是Momentum方法貌似更主流一些。实践结果表明,在给定损失函数的情况下,用带Momentum的SGD算法比 Adam算法找到的极小值更加平坦,而自适应方法往往会收敛到更加尖锐的极小值点。平坦的极小值通常好于尖锐的极小值

(6)尽管自适应算法有助于我们在复杂的损失函数上找到极小值点,但这还不够,特别是在当前网络越来越来越深的背景下。除了研究更好的优化方法之外,还有一些研究致力于构建产生更平滑损失函数的网络架构Batch-Normalization残差连接是其中的解决方法。

对SGD进行平行计算或分布式计算

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

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

Hogwild!

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

Downpour SGD

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

容忍延迟的 SGD 算法

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

TensorFlow

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

弹性平均梯度下降法(Elastic Averaging SGD)

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

优化 SGD 的其他手段

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

重排法(Shuffling)和递进学习(Curriculum Learning)

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

另一方面,在某些情况下,我们会需要解决难度逐步提升的问题。那么,按照一定的顺序遍历训练样本,会有助于改进学习效果及加快收敛速度。这种构建特定遍历顺序的方法,叫做递进学习(Curriculum Learning)。*这个词目前没有标准翻译,根据表意和意义翻译成这个。

Zaremba和Sutskever仅使用了递进学习法训练 LSTMs 来学习简单的项目,但结果表明,递进学习法使用的混合策略的表现好于朴素策略——后者不断地重排数据,反而增加了学习过程的难度。

批量标准化(Batch Normalization)

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

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

早停(Early Stopping)

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

梯度噪声(Gradient Noise)

Neelakentan等人在每次梯度的更新中,向其中加入一个服从合高斯分布$N(0,\sigma^2)$的噪声值:

$$g_{t, i} = g_{t, i} + N(0, \sigma^2_t)$$

They anneal the variance according to the following schedule:

$$\sigma^2_t = \dfrac{\eta}{(1 + t)^\gamma}$$

并按照如下的方式修正方差:

他们指出,这种方式能够提升神经网络在不良初始化前提下的鲁棒性,并能帮助训练特别是深层、复杂的神经网络。他们发现,加入噪声项之后,模型更有可能发现并跳出在深度网络中频繁出现的局部最小值。

结论

在本文中,我们首先分析了梯度下降法的三个变体,在其中小批量梯度下降法最受欢迎。接着,我们研究了常用的优化SGD的算法,包括:Momentum动量法、Nesterov accelerated gradient算法、Adagrad算法、Adadelta算法、RMSprop算法、Adam算法及其他优化异步SGD的算法。最终,我们讨论了另外一些改进SGD的策略,包括样本重排法(shuffling)、递进学习(curriculum learning)、批量标准化(Batch Normali·zation)及早停(early stopping)等。

(整理自网络)

参考资料:

https://ruder.io/optimizing-gradient-descent/index.html(首推这篇,很棒!中文版下载链接:bd网盘pdf文件

https://www.cnblogs.com/guoyaohua/p/8542554.html

https://www.leiphone.com/news/201807/l2zovxm7pbuua5og.html

https://zhuanlan.zhihu.com/p/22252270

posted @ 2020-12-07 21:09  箐茗  阅读(926)  评论(0编辑  收藏  举报