梯度下降优化算法概述1

梯度下降优化算法概述

原文作者简介:Sebastian Ruder 是我非常喜欢的一个博客作者,是 NLP 方向的博士生,目前供职于一家做 NLP 相关服务的爱尔兰公司 AYLIEN,博客主要是写机器学习、NLP 和深度学习相关的文章。

  • 本文原文是 An overview of gradient descent optimization algorithms,同时作者也在 arXiv 上发了一篇同样内容的 论文
  • 本文结合了两者来翻译,但是阅读原文我个人建议读博客中的,感觉体验更好点。
  • 文中括号中或者引用块中的 斜体字 为对应的英文原文或者我自己注释的话(会标明 译者注),否则为原文中本来就有的话。
  • 为方便阅读,我在引用序号后加了所引用论文的题目,用 斜体字 表示,例如 Learning rate schedules [11,A stochastic approximation method] 。
  • 水平有限,如有错误欢迎指出。翻译尽量遵循原文意思,但不意味着逐字逐句。
  • 本文也放在 我 GitHub 上的 awesome-posts 项目 上,选取数据科学和机器学习领域内比较好的英文文章进行翻译,欢迎各位 star 。

Abstract

梯度下降算法虽然最近越来越流行,但是始终是作为一个「黑箱」在使用,因为对他们的优点和缺点的实际解释(practical explainations)很难实现。这篇文章致力于给读者提供这些算法工作原理的一个直观理解。在这篇概述中,我们将研究梯度下降的不同变体,总结挑战,介绍最常见的优化算法,介绍并行和分布式设置的架构,并且也研究了其他梯度下降优化策略。


Introduction

梯度下降是最流行的优化算法之一,也是目前优化神经网络最常用的算法。同时,每一个最先进的深度学习库都包含了梯度下降算法的各种变体的实现(例如 lasagnecaffekeras)。然而始终是作为一个「黑箱」在使用,因为对他们的优点和缺点的实际解释很难实现。这篇文章致力于给读者提供这些算法工作原理的一个直观理解。我们首先介绍梯度下降的不同变体,然后简单总结下在训练中的挑战。接着,我们通过展示他们解决这些挑战的动机以及如何推导更新规则来介绍最常用的优化算法。我们也会简要介绍下在并行和分布式架构中的梯度下降。最后,我们会研究有助于梯度下降的其他策略。

梯度下降是一种最小化目标函数 J(θ)J(θ) 来实现的。而学习率(learning rate)则决定了在到达(局部)最小值的过程中每一步走多长。换句话说,我们沿着目标函数的下坡方向来达到一个山谷。如果你对梯度下降不熟悉,你可以在 这里 找到一个很好的关于优化神经网络的介绍。


Gradient descent variants

依据计算目标函数梯度使用的数据量的不同,有三种梯度下降的变体。根据数据量的大小,我们在参数更新的准确性和执行更新所需时间之间做了一个权衡。

Batch gradient descent

标准的梯度下降,即批量梯度下降(batch gradient descent)( 译者注:以下简称 BGD ),在整个训练集上计算损失函数关于参数 θθ 的梯度。

θ=θηθJ(θ)θ=θ−η⋅∇θJ(θ)

由于为了一次参数更新我们需要在整个训练集上计算梯度,导致 BGD 可能会非常慢,而且在训练集太大而不能全部载入内存的时候会很棘手。BGD 也不允许我们在线更新模型参数,即实时增加新的训练样本。

下面是 BGD 的代码片段:

for i in range(nb_epochs):
    params_grad = evaluate_gradient(loss_function, data, params)
    params = params - learning_rate * params_grad
 
 
  • 1
  • 2
  • 3

其中 nb_epochs 是我们预先定义好的迭代次数(epochs),我们首先在整个训练集上计算损失函数关于模型参数 params 的梯度向量 params_grad。其实目前最新的深度学习库都已经提供了关于一些参数的高效自动求导。如果你要自己求导求梯度,那你最好使用梯度检查(gradient checking),在 这里 查看关于如何进行合适的梯度检查的提示。

然后我们在梯度的反方向更新模型参数,而学习率决定了每次更新的步长大小。BGD 对于凸误差曲面(convex error surface)保证收敛到全局最优点,而对于非凸曲面(non-convex surface)则是局部最优点。

Stochastic gradient descent

随机梯度下降( 译者注:以下简称 SGD )则是每次使用一个训练样本 x(i)x(i) 进行一次参数更新。

θ=θηθJ(θ;xx(i);y(i))θ=θ−η⋅∇θJ(θ;xx(i);y(i))

BGD 对于大数据集来说执行了很多冗余的计算,因为在每一次参数更新前都要计算很多相似样本的梯度。SGD 通过一次执行一次更新解决了这种冗余。因此通常 SGD 的速度会非常快而且可以被用于在线学习。SGD 以高方差的特点进行连续参数更新,导致目标函数严重震荡,如图 1 所示。


图 1:SGD 震荡,来自 Wikipedia

BGD 能够收敛到(局部)最优点,然而 SGD 的震荡特点导致其可以跳到新的潜在的可能更好的局部最优点。已经有研究显示当我们慢慢的降低学习率时,SGD 拥有和 BGD 一样的收敛性能,对于非凸和凸曲面几乎同样能够达到局部或者全局最优点。

代码片段如下,只是加了个循环和在每一个训练样本上计算梯度。注意依据 这里 的解释,我们在每次迭代的时候都打乱训练集。

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
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

Mini-batch gradient descent

Mini-batch gradient descent( 译者注:以下简称 MBGD )则是在上面两种方法中采取了一个折中的办法:每次从训练集中取出 nn 个样本作为一个 mini-batch,以此来进行一次参数更新。

θ=θηθJ(θ;x(i:i+n);y(i:i+n))θ=θ−η⋅∇θJ(θ;x(i:i+n);y(i:i+n))

这样做有两个好处:

  • 减小参数更新的方差,这样可以有更稳定的收敛。
  • 利用现在最先进的深度学习库对矩阵运算进行了高度优化的特点,这样可以使得计算 mini-batch 的梯度更高效。

通常来说 mini-batch 的大小为 50 到 256 之间,但是也会因为任务的差异而不同。MBGD 是训练神经网络时的常用方法,而且通常即使实际上使用的是 MBGD,也会使用 SGD 这个词来代替。注意:在本文接下来修改 SGD 时,为了简单起见我们会省略参数 x(i:i+n);y(i:i+n)x(i:i+n);y(i:i+n)

代码片段如下,我们每次使用 mini-batch 为 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
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

Challenges

标准的 MBGD 并不保证好的收敛,也提出了一下需要被解决的挑战:

  • 选择一个好的学习率是非常困难的。太小的学习率导致收敛非常缓慢,而太大的学习率则会阻碍收敛,导致损失函数在最优点附近震荡甚至发散。
  • Learning rate schedules [11,A stochastic approximation method] 试图在训练期间调整学习率即退火(annealing),根据先前定义好的一个规则来减小学习率,或者两次迭代之间目标函数的改变低于一个阈值的时候。然而这些规则和阈值也是需要在训练前定义好的,所以也不能做到自适应数据的特点 [10,Learning rate schedules for faster stochastic gradient search]。
  • 另外,相同的学习率被应用到所有参数更新中。如果我们的数据比较稀疏,特征有非常多不同的频率,那么此时我们可能并不想要以相同的程度更新他们,反而是对更少出现的特征给予更大的更新。
  • 对于神经网络来说,另一个最小化高度非凸误差函数的关键挑战是避免陷入他们大量的次局部最优点(suboptimal)。Dauphin 等人 [19,Identifying and attacking the saddle point problem in high-dimensional non-convex optimization] 指出事实上困难来自于鞍点而不是局部最优点,即损失函数在该点的一个维度上是上坡(slopes up)( 译者注:斜率为正 ),而在另一个维度上是下坡(slopes down)( 译者注:斜率为负 )。这些鞍点通常被一个具有相同误差的平面所包围,这使得对于 SGD 来说非常难于逃脱,因为在各个维度上梯度都趋近于 0 。

Gradient descent optimization algorithms

接下来,我们将会概述一些在深度学习社区常用的算法,这些算法解决了我们前面提到的挑战。我们不会讨论实际上在高维数据集上不可行的算法,例如二阶方法中的 牛顿法

Momentum

SGD 在遇到沟壑(ravines)会比较困难,即在一个维度上比另一个维度更陡峭的曲面 [1,Two problems with backpropagation and other steepest-descent learning procedures for networks] ,这些曲面通常包围着局部最优点。在这些场景中,SGD 震荡且缓慢的沿着沟壑的下坡方向朝着局部最优点前进,如图 2 所示。

SGD without momentum
图 2:不带动量的 SGD

动量(Momentum)[2,On the momentum term in gradient descent learning algorithms] 是一种在相关方向加速 SGD 的方法,并且能够减少震荡,如图 3 所示。

SGD without momentum
图 3:带动量的 SGD

它在当前的更新向量中加入了先前一步的状态:

vtθ=γvt1+ηθJ(θ)=θvtvt=γvt−1+η∇θJ(θ)θ=θ−vt

注意:一些实现可能改变了公式中的符号。动量项 γγ 通常设置为 0.9 或者相似的值。

本质上来说,当我们使用动量时,类似于我们把球推下山的过程。在球下山的过程中,球累积动量使其速度越来越快(直到达到其最终速度,如果有空气阻力的话,即 γ<1γ<1)。相同的事情也发生在我们的参数更新中:对于梯度指向方向相同的维度动量项增大,对于梯度改变方向的维度动量项减小。最终,我们获得了更快的收敛并减少了震荡。

Nesterov accelerated gradient

然而,一个球盲目的沿着斜坡下山,这不是我们希望看到的。我们希望有一个聪明的球,他知道将要去哪并可以在斜坡变成上坡前减速。

Nesterov accelerated gradient( 译者注:以下简称 NAG )[7,A method for unconstrained convex minimization problem with the rate of convergence o(1/k2)] 就是这样一种给予我们的动量项预知能力的方法。我们知道我们使用动量项 γvt1γvt−1 下一个位置而不是当前位置的梯度来实现「向前看」。

vtθ=γvt1+ηθJ(θγvt1)=θvtvt=γvt−1+η∇θJ(θ−γvt−1)θ=θ−vt

我们仍然设置 γγ 为 0.9。动量法首先计算当前梯度(图 4 中的小蓝色向量),然后在更新累积梯度(updated accumulated gradient)方向上大幅度的跳跃(图 4 中的大蓝色向量)。与此不同的是,NAG 首先在先前的累积梯度(previous accumulated gradient)方向上进行大幅度的跳跃(图 4 中的棕色向量),评估这个梯度并做一下修正(图 4 中的红色向量),这就构成一次完整的 NAG 更新(图 4 中的绿色向量)。这种预期更新防止我们进行的太快,也带来了更高的相应速度,这在一些任务中非常有效的提升了 RNN 的性能 [8,Advances in Optimizing Recurrent Networks] 。

Nesterov update
图 4:Nesterov 更新,来自 G. Hinton’s lecture 6c

可以在 这里 查看对 NAG 的另一种直观解释,此外 Ilya Sutskever 在他的博士论文中也给出了详细解释 [9,Training Recurrent neural Networks] 。

现在我们已经能够依据误差函数的斜率来调整更新,并加快 SGD 的速度,此外我们也想根据每个参数的重要性来决定进行更大还是更小的更新。

Adagrad

Adagrad [3,Adaptive Subgradient Methods for Online Learning and Stochastic Optimization] 就是这样一种解决这个问题的基于梯度的优化算法:根据参数来调整学习率,对于不常见的参数给予更大的更新,而对于常见的给予更小的更新。因此,Adagrad 非常适用于稀疏数据。Dean 等人 [4,Large Scale Distributed Deep Networks] 发现 Adagrad 能够大幅提高 SGD 的鲁棒性,并在 Google 用其训练大规模神经网络,这其中就包括 在 YouTube 中学习识别猫。除此之外,Pennington 等人 [5,Glove: Global Vectors for Word Representation] 使用 Adagrad 来训练 GloVe 词嵌入,因为罕见的词汇需要比常见词更大的更新。

前面我们在对所有参数 θθ 时的梯度:

gt,i=θJ(θi)gt,i=∇θJ(θi)

SGD 在每个时间点 tt 的更新变为:

θt+1,i=θt,iηgt,iθt+1,i=θt,i−η⋅gt,i

在这个更新规则里,Adagrad 在每个时间点 tt

θt+1,i=θt,iηGt,ii+ϵ−−−−−−−√gt,iθt+1,i=θt,i−ηGt,ii+ϵ⋅gt,i

其中 GtRd×dGt∈Rd×d 左右。有趣的是,如果去掉开方操作,算法性能会大幅下降。

由于 GtGt

θt+1=θtηGt+ϵ−−−−−√gtθt+1=θt−ηGt+ϵ⊙gt

Adagrad 最大的一个优点是我们可以不用手动的调整学习率。大多数实现使用一个默认值 0.01 。

Adagrad 主要的缺点是分母中累积的平方和梯度:由于每一个新添加的项都是正的,导致累积和在训练期间不断增大。这反过来导致学习率不断减小,最终变成无限小,这时算法已经不能再继续学习新东西了。下面的这个算法就解决了这个问题。

Adadelta

Adadelta [6,ADADELTA: An Adaptive Learning Rate Method] 是 Adagrad 的扩展,旨在帮助缓解后者学习率单调下降的问题。与 Adagrad 累积过去所有梯度的平方和不同,Adadelta 限制在过去某个窗口大小为 ww 的大小内的梯度。

存储先前 ww 仅仅取决于先前的平均值和当前的梯度:

E[g2]t=γE[g2]t1+(1γ)g2tE[g2]t=γE[g2]t−1+(1−γ)gt2

其中 γγ 来重写一般的 SGD 更新公式:

Δθtθt+1=ηgt,i=θt+ΔθtΔθt=−η⋅gt,iθt+1=θt+Δθt

先前我们推导过的 Adagrad 的参数更新向量是:

Δθt=ηGt+ϵ−−−−−√gtΔθt=−ηGt+ϵ⊙gt

我们现在用过去梯度平方和的衰减平均 E[g2]tE[g2]t

Δθt=ηE[g2]t+ϵ−−−−−−−−√gtΔθt=−ηE[g2]t+ϵgt

由于分母只是一个梯度的均方误差(Root Mean Squared,RMS),我们可以用缩写来代替:

Δθt=ηRMS[g]tgtΔθt=−ηRMS[g]tgt

作者注意到这个更新中的单位(units)不匹配(SGD,动量和 Adagrad 也是),即更新向量应该具有和参数一样的单位。为解决这个问题,他们首先定义了另一个指数衰减平均,但是这次不是关于梯度的平方,而是关于参数更新的平方:

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

那么参数更新的均方误差是:

RMS[Δθ]t=E[Δθ2]t+ϵ−−−−−−−−−√RMS[Δθ]t=E[Δθ2]t+ϵ

由于 RMS[Δθ]tRMS[Δθ]t 就得到了 Adadelta 最终的更新规则:

Δθtθt+1=RMS[Δθ]t1RMS[g]tgt=θt+ΔθtΔθt=−RMS[Δθ]t−1RMS[g]tgtθt+1=θt+Δθt

使用 Adadelta 时我们甚至不需要指定一个默认的学习率,因为它已经不在更新规则中了。

RMSprop

RMSprop 是一种未发布的自适应学习率的方法,由 Geoff Hinton 在 Lecture 6e of his Coursera Class 中提出。

RMSprop 和 Adadelta 在同一时间被独立地发明出来,都是为了解决 Adagrad 的学习率递减问题。事实上 RMSprop 与我们上面讨论过的 Adadelta 的第一个更新向量一模一样:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值