【深度学习】TensorFlow学习之路四:几种梯度下降优化算法


本系列文章主要是对OReilly的Hands-On Machine Learning with Scikit-learn and TensorFlow一书深度学习部分的阅读摘录和笔记。

训练一个规模庞大,层次较深的神经网络会相当消耗时间。为了加快神经网络的训练速度,我们已经介绍过,可以从以下几个方面入手进行优化:

  • 选择较合理的参数初始化方案,比如Xavier或He初始化方案;
  • 选择计算较快的激活函数,比如Relu及其衍生激活函数;
  • 使用Batch标准化;
  • 基于已经存在的,解决相似问题的神经网络的初始几层进行训练;
  • 选择更快速的梯度下降算法。

其中最后一点,就是本篇要详细介绍的几种可以优化梯度下降的方法

一、动量下降(Momentum)

如果我们把梯度下降过程想象成一个小球沿着曲面下滚的过程,最开始时小球的速度会比较慢,但在下降过程中,小球会不断积累动量,加速滑到最底部,这就是动量梯度下降的基本过程。相比较而言,梯度下降则更像是以匀速下降,所以下降过程会比动量下降慢很多。
梯度下降的计算过程为:每一次计算该点目标函数J对于参数 θ \theta θ的梯度 ∇ θ J ( θ ) \nabla_{\theta}J(\theta) θJ(θ),然后 θ \theta θ下降一个梯度的幅度,即 θ = θ − η ∇ θ J ( θ ) \theta = \theta - \eta\nabla_{\theta}J(\theta) θ=θηθJ(θ)。而动量下降的不同在于,每次计算的是该点累计的动量m,然后 θ \theta θ下降一个动量的幅度,公式为:
m = β m + η ∇ θ J ( θ ) θ = θ − m m = \beta m + \eta\nabla_{\theta}J(\theta) \\ \theta = \theta - m m=βm+ηθJ(θ)θ=θm
其中m就是每次累计的动量, β \beta β是一个超参,是一个0-1的取值,可以理解为是个控制摩擦项的参数,越小代表阻力越大,即对动量的损耗也越大。
那到底动量下降能比梯度下降快多少呢?我们可以做一个理论测算,假设每一步的梯度不变
m = β m + η ∇ = β ( β ( . . . ) + η ∇ ) + η ∇ = β n ∗ m 1 + η ∇ + β η ∇ + β 2 η ∇ + . . . + β n η ∇ m = \beta m + \eta\nabla \\=\beta (\beta (...) + \eta\nabla) + \eta\nabla \\= \beta^n*m_1 + \eta\nabla + \beta\eta\nabla + \beta^2\eta\nabla +... +\beta^n\eta\nabla m=βm+η=β(β(...)+η)+η=βnm1+η+βη+β2η+...+βnη
当n趋于无穷大时,m会最终收敛于 η ∇ ( 1 1 − β ) \eta\nabla(\frac{1}{1-\beta}) η(1β1),如果 β \beta β取0.9,那m就是梯度 ∇ \nabla 的十倍,所以动量下降一般最终会趋向于10倍于梯度下降的速度去收敛,由此使得动量下降能更快地走出梯度平原和局部最优解的位置。但另一方面,由于动量太大,动量下降算法会在最终最优解的地方来回摆动,所以我们需要 β \beta β项来加一些摩擦。
用TensorFlow执行动量下降非常简单:

optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate, momentum=0.9)

二、Nesterov加速梯度

Nesterov加速梯度方法是对动量下降进行了一些小小的改进,动量下降计算的是在当前点的动量,而Nesterov加速梯度方法则是在动量方向上前进一小步后再计算动量,公式如下:
m = β m + η ∇ θ J ( θ + β m ) θ = θ − m m = \beta m + \eta\nabla_{\theta}J(\theta+\beta m) \\ \theta = \theta - m m=βm+ηθJ(θ+βm)θ=θm
和动量下降唯一的不同就是计算梯度的时候是在 θ + β m \theta+\beta m θ+βm点上计算的。为什么这点改进会进一步加快收敛速度呢?因为动量向量m往往指向正确的方向,即指向最优点方向,所以在这个方向上前进一小步后再算梯度,再进一步算动量会更加精确。如下图所示,蓝色箭头表示当前点的梯度方向,绿色箭头表示在梯度方向上前进一小步后的梯度方向,明显在更新动量的时候,使用绿色箭头进行更新会让动量方向更加准确指向最优点。每一步,Nesterov加速梯度方法都会对动量方向进行一点点修正,那在不断下降过程中,收敛就会越来越快。
在这里插入图片描述
另外,当优化到越过最优点后,动量下降中的动量方向会进一步让参数走得更远,而Nesterov加速梯度方法会将动量方向修正到反向,加速震荡收敛的过程。
TensorFlow中使用该方法也非常简单,只需要在刚刚动量下降的函数中加个参数use_nesterov=True即可:

optimizer = tf.train.MomentumOptimizer(learning_rate=learning_rate, momentum=0.9, use_nesterov=True)

三、AdaGrad

Ada是adaptive的缩写,AdaGrad的算法基本思想就是调整梯度方向使其更具适应性。看如下图中的情况:
在这里插入图片描述
当不同变量间取值范围差异过大时,目标函数就会是这种扁长型,梯度方向就会被量级大的变量所带偏,导致每一步梯度的指向不一定是最优的。AdaGrad方法就是在算梯度的时候先把每个维度做个标准化,这样梯度虽然更平缓,但是却更能指向最优解方向(如图),具体公式如下:
s = s + ∇ θ J ( θ ) ⊗ ∇ θ J ( θ ) θ = θ − η ∇ θ J ( θ ) ⊘ s + ϵ s = s + \nabla_{\theta}J(\theta)\otimes\nabla_{\theta}J(\theta) \\ \theta = \theta - \eta\nabla_{\theta}J(\theta) \oslash \sqrt{s+\epsilon} s=s+θJ(θ)θJ(θ)θ=θηθJ(θ)s+ϵ
解释一下,第一步相当于算一下当前梯度每个维度上的方差,不过这里方差s是累加计算的,这样一方面是起到标准化的作用,一方面是逐步缩小学习率,有利于找到最优解。如果目标函数在某个维度上特别陡,那s值就会在迭代过程中越来越大。 ⊗ \otimes 符号表示向量中的每个对应元素相乘。第二步就是梯度的每个维度先标准化一下,再执行梯度下降, ⊘ \oslash 表示每个对应位置的元素相除。 ϵ \epsilon ϵ是个非常小的数,加它是为了防止除以0。
AdaGrad方法处理结构比较简答的网络效果还可以,但在处理深度神经网络的时候,由于梯度被标准化,所以总是会在找到最优解之前就停止了,因此,虽然TensorFlow里面有AdagradOptimizer函数,但不建议用来训练深度网络。

四、RMSProp

RMSProp方法是对AdaGrad方法的一种改进,他通过加了一个衰减系数 β \beta β使得算法只累加最近几次迭代的梯度,具体公式如下:
s = β s + ( 1 − β ) ∇ θ J ( θ ) ⊗ ∇ θ J ( θ ) θ = θ − η ∇ θ J ( θ ) ⊘ s + ϵ s = \beta s + (1-\beta) \nabla_{\theta}J(\theta) \otimes \nabla_{\theta}J(\theta) \\ \theta = \theta - \eta\nabla_{\theta}J(\theta) \oslash \sqrt{s+\epsilon} s=βs+(1β)θJ(θ)θJ(θ)θ=θηθJ(θ)s+ϵ
衰减系数 β \beta β一般设置为0.9,在TensorFlow中的实现如下:

optimizer = tf.train.RMSPropOptimizer(learning_rate=learning_rate,
									momentum=0.9, decay=0.9, epsilon=1e-10)

这种方法通常都会比AdaGrad算法表现更好,一般也会比动量下降方法效果好,一度是最优选择,直到Adam优化方法的出现。

五、Adam优化算法

Adam是Adaptive momentum estimation的缩写,看名字就知道是融合了动量下降和RMSProp的一种算法。该算法的具体计算过程如下:
1.   m = β 1 m + ( 1 − β 1 ) ∇ θ J ( θ ) 2.   s = β 2 s + ( 1 − β 2 ) ∇ θ J ( θ ) ⊗ ∇ θ J ( θ ) 3.   m = m 1 − β 1 T 4.   s = s 1 − β 2 T 5.   θ = θ − η m ⊘ s + ϵ 1.\ m = \beta_1 m + (1-\beta_1)\nabla_{\theta}J(\theta) \\ 2. \ s = \beta_2 s + (1-\beta_2) \nabla_{\theta}J(\theta) \otimes \nabla_{\theta}J(\theta) \\ 3. \ m = \frac{m}{1-\beta_1^T} \\ 4. \ s = \frac{s}{1-\beta_2^T} \\ 5. \ \theta = \theta-\eta m \oslash \sqrt{s+\epsilon} 1. m=β1m+(1β1)θJ(θ)2. s=β2s+(1β2)θJ(θ)θJ(θ)3. m=1β1Tm4. s=1β2Ts5. θ=θηms+ϵ
其中T代表了迭代的轮数。如果只看1,2,5步,会发现Adam算法和动量下降或RMSProp过程基本一样。其中4和5步是一个技术细节的操作,因为m和s初始化时都是0,所以最开始训练的几轮里,m和s都会偏向于0,4-5步的操作会放大m和s,从而加快收敛过程,越迭代到后面,4-5步的作用也越小。
动量衰减系数 β 1 \beta_1 β1一般设置为0.9,参数 β 2 \beta_2 β2一般设置为0.999, ϵ \epsilon ϵ一般设置为1e-8,这些都是TensorFlow里AdamOptimizer函数的默认值:

optimizer = tf.train.AdamOptimizer(learning_rate=learning_rate)

六、学习率优化方案

找到一个合适的学习率并非那么容易,如果学习率过大,会导致最终算法不收敛;如果学习率过小,会导致训练时间过长;如果学习率稍微大了一些,又会导致结果最最优解附近来回震荡,找不到最优解(AdaGrad和RMSProp和Adam算法不会出现这种状况)。
为了解决这些问题,我们完全可以不使用一个固定的学习率,而是让学习率在最开始的时候大一些,下降的快一些,到后面快找到最优解了就把学习率减小,以便于找到最优解。这里直接介绍一个最推荐的学习率优化方案,指数方案:
η ( t ) = η 0 ∗ 1 0 − t / r \eta(t) = \eta_0*10^{-t/r} η(t)=η010t/r
这个公式表示每过r轮,学习率就会减小为原来的十分之一。在TensorFlow中实现如下:

#eta0赋值
initial_learning_rate = 0.1
#r赋值
decay_steps = 10000
decay_rate = 1/10
#记录当前训练到多少轮
global_step = tf.Variable(0, trainable=False)
#构造一个变动的学习率
learning_rate = tf.train.exponential_decay(initial_learning_rate, global_step, decay_steps, decay_rate)
optimizer = tf.train.MomentumOptimizer(learning_rate, momentum=0.9)
training_op = optimizer.minimize(loss, global_step=global_step)

这里设定了 η 0 \eta_0 η0为0.1,r为10000,并且创建了一个global_step变量去追踪记录当前的训练轮数,然后通过tf.train.exponential_decay函数生成了一个变动的学习率。
当然,AdaGrad和RMSProp和Adam三种算法会自动在迭代过程中减小学习率,所以不必做这种学习率优化方案。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值