Skip to content

Latest commit

 

History

History
575 lines (346 loc) · 57.5 KB

2.improving-deep-neural-networks-2.optimization-algorithms.md

File metadata and controls

575 lines (346 loc) · 57.5 KB

深度神经网络调参和优化(二)

优化算法

Mini-Batch 梯度下降

本周将学习优化算法,让你的神经网络运行得更快。机器学习的应用是高度依赖经验的,伴随着大量迭代的过程,你需要训练多个模型,才能找到合适的。所以,优化算法能够帮助你快速训练模型。

其中一个难点在于,深度学习没有在大数据领域发挥最大的效果,我们可以利用一个巨大的数据集来训练神经网络,而在大数据集的情况下,训练速度很慢。因此,使用好用的优化算法能够提高你和团队的效率。我们先来谈谈mini-batch梯度下降法。

之前学过,向量化能够有效地对所有 m 个样本进行计算,允许你处理整个训练集。所以我们要把训练样本放到巨大的矩阵 X 当中去, X = [x(1) x(2) x(3) ... x(m)]Y 也是如此, Y = [y(1) y(2) y(3) ... y(m)]X 的维数是 (nx, m)Y 的维数是 (1, m) ,向量化能够让你相对较快地处理所有 m 个样本。但如果 m 很大的话,处理速度仍然缓慢。比如说,如果 m 是500万或5000万或者更大的一个数,在对整个训练集执行梯度下降法时,你要做的是,你必须处理完整个训练集,然后才能做一步梯度下降法,然后你需要再重新处理500万个训练样本,才能进行下一步梯度下降法。所以如果你在处理完整个500万样本的训练集之前,先让梯度下降法处理一部分,那么算法速度会更快。准确地说,这是你可以做如下的操作。

你可以把训练集分割为小一点的子集训练,这些子集被取名为mini-batch,假设每一个子集中只有1000个样本,那么把其中的 x(1)x(1000) 取出来,将其称为第一个子训练集,也叫做mini-batch,然后你再取出接下来的1000个样本,从 x(1001)x(2000) ,然后再取1000个样本,以此类推。

接下来我要说一个新的符号,把 x(1)x(1000) 称为 X{1}x(1001)x(2000) 称为 X{2} ,如果你的训练样本一共有500万个,每个mini-batch都有1000个样本,也就是说,你有5000个mini-batch。你共有5000个mini-batch,所以最后得到是 X {5000} 

Y 也要进行相同处理,你也要相应地拆分 Y 的训练集,所以这是 Y{1} ,然后从 y(1001)y(2000) ,这个叫 Y{2} ,一直到 Y {5000}

mini-batch的数量 t 组成了 X{t}Y{t} ,这就是1000个训练样本,包含相应的输入输出对。

介绍一下mini-batch用的符号:之前我们使用了上角小括号 (i) 表示训练集里的值,所以 x(i) 是第 i 个训练样本。我们用了上角中括号 [l] 来表示神经网络的层数, z[l] 表示神经网络中第 l 层的 z 值,我们现在引入了大括号 {t} 来代表不同的mini-batch,所以我们有 X {t}Y{t} ,检查一下自己是否理解无误。

X{t}Y{t} 的维数

  • 如果 X{1} 是一个有1000个样本的训练集,或者说是1000个样本的 x 值,所以维数应该是 (nx,1000)X{2} 的维数应该是 (nx,1000) ,以此类推。
  • 因此所有的子集维数都是 (nx,1000) ,而这些( Y{t} )的维数都是 (1,1000)

关于mini-batch算法的名称:batch梯度下降法指的是我们之前讲过的梯度下降法算法,就是同时处理整个训练集,这个名字就是来源于能够同时看到整个batch训练集的样本被处理,这个名字不怎么样,但就是这样叫它。相比之下,mini-batch梯度下降法,指的是我们在下面会讲到的算法,你每次同时处理的单个的mini-batch X{t}Y{t} ,而不是同时处理全部的 XY 训练集。

Mini-Batch梯度下降法算法

  • 在训练集上运行mini-batch梯度下降法,你运行for t = 1 ... 5000,因为我们有5000个各有1000个样本的组,在for循环里你要做得基本就是对 X{t}Y{t} 执行一步梯度下降法。假设你有一个拥有1000个样本的训练集,而且假设你已经很熟悉一次性处理完的方法,你要用向量化去几乎同时处理1000个样本。
  • 首先对输入也就是 X{t} ,执行前向传播, z[1] = W[1]X + b[1] ,之前我们这里只有 X,但是现在你不是在处理整个训练集,你在处理第一个mini-batch,在处理mini-batch时它变成了 X {t} ,即 Z[1] = W[1]X t + b[1] ,然后执行 A[1]{k} = g[1](Z[1]) ,之所以用大写的 Z 是因为这是一个向量内涵,以此类推,直到 A[L] = g[L](Z[L]) ,这就是你的预测值。注意这里你需要用到一个向量化的执行命令,这个向量化的命令一次性处理1000个而不是500万个样本。
  • 接下来你要计算损失成本函数 J ,因为子集规模是1000, J = ( 1 / 1000 i = 1lL(hat y(i),y(i)) ,说明一下,这( L(ŷ(i), y(i)) )指的是来自于mini-batch X{t}Y{t} 中的样本。如果用到了正则化,也可以使用正则化的术语, J = ( 1 / 1000 i = 1lL(hat y(i),y(i)) + ( λ / 2 1000 l||w[l]||*F2 ,因为这是一个mini-batch的损失,所以我将 J 损失记为上角标 t ,放在大括号里( J{t} = ( 1 / 1000 i = 1lL(hat y(i),y(i)) + ( λ / 2 1000 l||w[l]||F2 )。你也会注意到,我们做的一切似曾相识,其实跟之前我们执行梯度下降法如出一辙,除了你现在的对象不是 XY ,而是 X{t}Y{t}
  • 接下来,执行反向传播来计算 J{t} 的梯度,你只是使用 X{t}Y{t} ,然后你更新加权值, W 实际上是 W[l] ,更新为 W[l]: = W[l] - αdW[l] ,对 b 做相同处理, b[l]: = b[l] - αdb[l] 。这是使用mini-batch梯度下降法训练样本的一步,写下的代码也可被称为进行“一代”(1 epoch)的训练。一代这个词意味着只是一次遍历了训练集。

Python伪代码如下

# in one epoch, there are 5000 iterations
for t in range(5000):
    # forward prop on X^{t}
    Z = W @ X[t] + b

    # compute cost J
    J[t] = compute_cost_J(X[t], Y[t], W)

    # backprop to compute gradients according to J[t]
    W = W - alpha * dW
    b = b - alpha * db

使用batch梯度下降法,一次遍历训练集只能让你做一个梯度下降,使用mini-batch梯度下降法,一次遍历训练集,能让你做5000个梯度下降。当然正常来说你想要多次遍历训练集,还需要为另一个while循环设置另一个for循环。所以你可以一直处理遍历训练集,直到最后你能收敛到一个合适的精度。

如果你有一个大型的训练集,mini-batch梯度下降法比batch梯度下降法运行地更快,所以几乎每个使用深度学习的人在训练大型数据集时都会用到mini-batch。

理解Mini-Batch梯度下降法

在上节中,你知道了如何利用mini-batch梯度下降法来开始处理训练集和开始梯度下降,即使你只处理了部分训练集,即使你是第一次处理,本节中,我们将进一步学习如何执行梯度下降法,更好地理解其作用和原理。

使用batch梯度下降法时,每次迭代你都需要历遍整个训练集,可以预期每次迭代成本都会下降,所以如果画出成本函数 J 和迭代次数的关系图,J 应该随着迭代次数增加而减少。如果 J 在某次迭代中增加了,那肯定出了问题,也许你的学习率太大。

使用mini-batch梯度下降法,如果你作出成本函数在整个过程中的图,则并不是每次迭代都是下降的。在每次迭代中,你要处理的是 X{t}Y{t} ,如果要作出成本函数 J{t} 的图,而 J{t} 只和 X{t}Y{t} 有关,也就是每次迭代下你都在训练不同的样本集或者说训练不同的mini-batch。如果你要作出成本函数 J 的图,你很可能会看到上图右侧这样的结果,走向朝下,但有很多的噪声。因为在训练mini-batch梯度下降法时,会经过多代。没有每次迭代都下降是不要紧的,但走势应该向下,噪声产生的原因在于也许 X{1}Y{1} 是比较容易计算的mini-batch,因此成本会低一些。不过也许出于偶然, X{2}Y{2} 是比较难运算的mini-batch,或许你需要一些残缺的样本,这样一来,成本会更高一些,所以才会出现这些摆动,因为你是在运行mini-batch梯度下降法作出成本函数图。

选择Mini-Batch Size

你需要决定的变量之一是mini-batch的大小, m 就是训练集的大小,极端情况下,如果mini-batch的大小等于 m ,其实就是batch梯度下降法,在这种极端情况下,你就只有mini-batch X{1}Y{1} ,并且该mini-batch等于整个训练集,所以把mini-batch大小设为 m 可以得到batch梯度下降法。

另一个极端情况,假设mini-batch大小为1,就有了新的算法,叫做随机梯度下降法,每个样本都是独立的mini-batch,当你看第一个mini-batch,也就是 X{1}Y{1} ,如果mini-batch大小为1,它就是你的第一个训练样本,这就是你的第一个训练样本。接着再看第二个mini-batch,也就是第二个训练样本,采取梯度下降步骤,然后是第三个训练样本,以此类推,一次只处理一个。

看在两种极端下成本函数的优化情况,如果这是你想要最小化的成本函数的轮廓,最小值在那里,batch梯度下降法从某处开始,相对噪声低些,幅度也大一些,你可以继续找最小值。(下图蓝色线)

相反,在随机梯度下降法中,从某一点开始,我们重新选取一个起始点,每次迭代,你只对一个样本进行梯度下降,大部分时候你向着全局最小值靠近,有时候你会远离最小值,因为那个样本恰好给你指的方向不对,因此随机梯度下降法是有很多噪声的,平均来看,它最终会靠近最小值,不过有时候也会方向错误,因为随机梯度下降法永远不会收敛,而是会一直在最小值附近波动,但它并不会在达到最小值并停留在此。(下图紫色线)

实际上你选择的mini-batch大小在二者之间,大小在1和 m 之间,而1太小了, m 太大了,原因在于如果使用batch梯度下降法,mini-batch的大小为 m ,每个迭代需要处理大量训练样本,该算法的主要弊端在于特别是在训练样本数量巨大的时候,单次迭代耗时太长。如果训练样本不大,batch梯度下降法运行地很好。

相反,如果使用随机梯度下降法,如果你只要处理一个样本,那这个方法很好,这样做没有问题,通过减小学习率,噪声会被改善或有所减小,但随机梯度下降法的一大缺点是,你会失去所有向量化带给你的加速,因为一次性只处理了一个训练样本,这样效率过于低下,所以实践中最好选择不大不小的mini-batch尺寸,实际上学习率达到最快。你会发现两个好处,一方面,你得到了大量向量化,上节中我们用过的例子中,如果mini-batch大小为1000个样本,你就可以对1000个样本向量化,比你一次性处理多个样本快得多。另一方面,你不需要等待整个训练集被处理完就可以开始进行后续工作,再用一下上节的数字,每次训练集允许我们采取5000个梯度下降步骤,所以实际上一些位于中间的mini-batch大小效果最好。

Batch Size 1 in (1, m) m
实际效果 SGD 随机梯度下降 BGD 批量梯度下降
梯度下降 下图紫色 下图绿色 下图蓝色
缺点 不能利用向量化加速算法 训练集大,每次迭代耗时久
优点 比SGD速度快;比BGD单次迭代耗时短

用mini-batch梯度下降法,我们从这里开始,一次迭代这样做,两次,三次,四次,它不会总朝向最小值靠近,但它比随机梯度下降要更持续地靠近最小值的方向,它也不一定在很小的范围内收敛或者波动,如果出现这个问题,可以慢慢减少学习率,我们在下节会讲到学习率衰减,也就是如何减小学习率。(上图绿色线)

Mini-Batch Size的指导原则

如果mini-batch大小既不是1也不是 m ,应该取中间值,那应该怎么选择呢?其实是有指导原则的。

首先,如果训练集较小,直接使用batch梯度下降法,样本集较小就没必要使用mini-batch梯度下降法,你可以快速处理整个训练集,所以使用batch梯度下降法也很好,这里的少是说小于2000个样本,这样比较适合使用batch梯度下降法。

其次,如果样本数目较大,一般的mini-batch大小为64到512,考虑到电脑内存设置和使用的方式,如果mini-batch大小是2的 n 次方,代码会运行地快一些,64就是2的6次方,以此类推,128是2的7次方,256是2的8次方,512是2的9次方。所以我经常把mini-batch大小设成2的次方。在上一节里,我的mini-batch大小设为了1000,建议你可以试一下1024,也就是2的10次方。也有mini-batch的大小为1024,不过比较少见,64到512的mini-batch比较常见。

最后需要注意的是在你的mini-batch中,要确保 X{t}Y{t} 是可以放进你得到CPU**/GPU内存中的,取决于你的应用以及训练集的大小。如果你处理的mini-batch和CPU/**GPU内存不相符,不管你用什么方法处理数据,你会注意到算法的表现急转直下变得惨不忍睹(甚至直接出错退出),所以希望你对一般大家使用的mini-batch大小有一个直观了解。事实上mini-batch大小是另一个重要的变量,你需要做一个快速尝试,才能找到能够最有效地减少成本函数的那个,我一般会尝试几个不同的值,几个不同的2次方,然后看能否找到一个让梯度下降优化算法最高效的大小。希望这些能够指导你如何开始找到这一数值。

你学会了如何执行mini-batch梯度下降,令算法运行得更快,特别是在训练样本数目较大的情况下。不过还有个更高效的算法,比梯度下降法和mini-batch梯度下降法都要高效的多,我们在接下来将为大家一一讲解。

指数加权平均数

我想向你展示几个优化算法,它们比梯度下降法快,要理解这些算法,你需要用到指数加权平均,在统计中也叫做指数加权移动平均(Exponentially weighted averages),我们首先讲这个,然后再来讲更复杂的优化算法。

比如去年伦敦的每日温度,1月1号温度是4摄氏度,1月2号9摄氏度等等。在年中的时候,也就是5月末,温度是15摄氏度等等。夏季温度转暖,然后冬季降温。

你用数据作图,可以得到上图的结果,看起来有些杂乱,如果要计算趋势的话,也就是温度的局部平均值,或者说移动平均值。你要做的是,首先使 v0 = 0 ,每天,需要使用0.9的加权数之前的数值加上当日温度的0.1倍。

  • 第一天,即 v1 = 0.9v0 + 0.1θ1
  • 第二天,0.9乘以之前的值加上当日的温度0.1倍,即 v2 = 0.9v1 + 0.1θ2 ,以此类推。
  • 第二天值加上第三日数据的0.1,如此往下。大体公式就是某天的 v 等于前一天 v 值的0.9加上当日温度的0.1。

所以得到:

  • V0 = 0
  • V1 = 0.9V0 + 0.1θ1
  • V2 = 0.9V1 + 0.1θ2
  • V3 = 0.9V2 + 0.1θ3
  • ...
  • Vt = 0.9Vt - 1 + 0.1θt

如此计算,然后用红线作图的话,如下图红线。

你得到了移动平均值,每日温度的指数加权平均值。

看一下公式, vt = 0.9vt - 1 + 0.1θt ,我们把0.9这个常数变成 β ,将之前的0.1变成 (1 - β) ,即

  • vt = β vt - 1 + (1 - β)θt

由于以后我们要讲的原因,在计算时,你可以认为 vt 近似为是过去 ( 1 / (1 - β) ) 天的平均温度,如果 β 是0.9,你会想,这是十天的平均值,也就是红线部分。

来试试别的,将 β 设置为接近1的一个值,比如0.98,计算 ( 1 / (1 - 0.98) ) = 50 ,这就是粗略平均了一下,过去50天的温度(下图中绿线)。

β 的值很大时,你得到的曲线要平坦一些,原因在于你多平均了几天的温度,所以这个曲线,波动更小,更加平坦,缺点是曲线进一步右移,因为现在平均的温度值更多,要平均更多的值,指数加权平均公式在温度变化时,适应地更缓慢一些,所以会出现一定延迟,因为当 β = 0.98 ,相当于给前一天的值加了太多权重,只有0.02的权重给了当日的值,所以温度变化时,温度上下起伏,当 β 较大时,指数加权平均值适应地更缓慢一些。

我们可以再换一个值试一试,如果 β 是另一个极端值,比如说0.5,根据右边的公式( ( 1 / (1 - β) ) ),这是平均了两天的温度。

得到下图中黄线:

由于仅平均了两天的温度,平均的数据太少,所以得到的曲线有更多的噪声,有可能出现异常值,但是这个曲线能够更快适应温度变化。

指数加权平均数经常被使用,再说一次,它在统计学中被称为指数加权移动平均值,我们就简称为指数加权平均数。通过调整这个参数( β )。你会发现这是一个很重要的参数,可以取得稍微不同的效果,往往中间有某个值效果最好, β 为中间值时得到的红色曲线,比起绿线和黄线更好地平均了温度。

理解指数加权平均数

本节中,我希望进一步探讨指数加权平均数算法的本质作用。回忆一下这个计算指数加权平均数的关键方程。

vt = β vt - 1 + (1 - β)θ t

β = 0.9 的时候,得到的结果是红线,如果它更接近于1,比如0.98,结果就是绿线,如果 β 小一点,如果是0.5,结果就是上图中黄线。我们进一步地分析,来理解如何计算出每日温度的平均值。

使 β = 0.9 ,写下相应的几个公式,所以在执行的时候, t 从0到1到2到3, t 的值在不断增加,为了更好地分析,我写的时候使得 t 的值不断减小,然后继续往下写。

  • V1 = 0.9V99 + 0.1θ100
  • V2 = 0.9V98 + 0.1θ99
  • V98 = 0.9V97 + 0.1θ98
  • ...

首先看第一个公式,理解 v100 是什么?利用上述公式,可得

  • v100 = 0.1θ100 + 0.1 × 0.9 θ99 + 0.1 × (0.9)2θ98 + 0.1 × (0.9)3θ97 + 0.1 × (0.9)4θ96 + ...

所以这是一个加和并平均,100号数据,也就是当日温度。我们分析 v100 的组成,也就是在一年第100天计算的数据,但是这个是总和,包括100号数据,99号数据,97号数据等等。画图的一个办法是,假设我们有一些日期的温度,所以这是数据,这是 t ,所以100号数据有个数值,99号数据有个数值,98号数据等等, t 为100,99,98等等,这就是数日的温度数值。

然后我们构建一个指数衰减函数,从0.1开始,到 0.1 × 0.9 ,到 0.1 × (0.9)2 ,以此类推,所以就有了这个指数衰减函数。

计算 v100 是通过,把两个函数对应的元素,然后求和,用这个数值100号数据值乘以0.1,99号数据值乘以0.1乘以 (0.9)2 ,这是第二项,以此类推,所以选取的是每日温度,将其与指数衰减函数相乘,然后求和,就得到了 v100

结果是,稍后我们详细讲解,不过所有的这些系数( 0.1、0.1 × 0.9、0.1 × (0.9)2、0.1 × (0.9)3 ... ),相加起来为1或者逼近1,我们称之为偏差修正,下节会涉及。

最后也许你会问,到底需要平均多少天的温度。实际上 (0.9)10 大约为0.35,这大约是 ( 1 / e ) ,e是自然算法的基础之一。大体上说,如果有 1 - ϵ ,在这个例子中, ϵ = 0.1 ,所以 1 - ϵ = 0.9(1 - ϵ)(1/ ϵ ) 约等于 (1/e) ,大约是0.34,0.35,换句话说,10天后,曲线的高度下降到 (1/3) ,相当于在峰值的 (1/e)

又因此当 β = 0.9 的时候,我们说仿佛你在计算一个指数加权平均数,只关注了过去10天的温度,因为10天后,权重下降到不到当日权重的三分之一。

相反,如果,那么0.98需要多少次方才能达到这么小的数值? (0.98)50 大约等于 ( 1 / e ) ,所以前50天这个数值比 ( 1 / e ) 大,数值会快速衰减,所以本质上这是一个下降幅度很大的函数,你可以看作平均了50天的温度。因为在例子中,要代入等式的左边, ϵ = 0.02 ,所以 ( 1 / ϵ ) 为50,我们由此得到公式,我们平均了大约 ( 1 / (1 - β) ) 天的温度,这里 ϵ 代替了 1 - β ,也就是说根据一些常数,你能大概知道能够平均多少日的温度,不过这只是思考的大致方向,并不是正式的数学证明。

最后讲讲如何在实际中执行,还记得吗?我们一开始将 v0 设置为0,然后计算第一天 v1 ,然后 v2 ,以此类推。

解释一下算法,可以将 v0v1v2 等等写成明确的变量,不过在实际中执行的话,你要做的是,一开始将 v 初始化为0,然后在第一天使 v: = β v + (1 - β)θ1 ,然后第二天,更新 v 值, v: = β v + (1 - β)θ2 ,以此类推,有些人会把 v 加下标,来表示 v 是用来计算数据的指数加权平均数。Python实现如下:

v = 0
for i in range(100):
    v = beta * v + (1 - beta) * theta[i]

再说一次,但是换个说法, vθ = 0 ,然后每一天,拿到第 t 天的数据,把 v 更新为 v: = β vθ + (1 - β)θt

指数加权平均数公式的好处之一在于,它占用极少内存,电脑内存中只占用一行数字而已,然后把最新数据代入公式,不断覆盖就可以。正因为这个原因,其效率,它基本上只占用一行代码,计算指数加权平均数也只占用单行数字的存储和内存,当然它并不是最好的,也不是最精准的计算平均数的方法。如果你要计算移动窗,你直接算出过去10天的总和,过去50天的总和,除以10和50就好,如此往往会得到更好的估测。但缺点是,如果保存所有最近的温度数据,和过去10天的总和,必须占用更多的内存,执行更加复杂,计算成本也更加高昂。

接下来,我们会计算多个变量的平均值,从计算和内存效率来说,这是一个有效的方法,所以在机器学习中会经常使用,更不用说只要一行代码,这也是一个优势。

现在你学会了计算指数加权平均数,你还需要知道一个专业概念,叫做偏差修正,下一节我们会讲到它,接着你就可以用它构建更好的优化算法,而不是简单直接的梯度下降法。

指数加权平均的偏差修正

你学过了如何计算指数加权平均数,有一个技术名词叫做偏差修正,可以让平均数运算更加准确,来看看它是怎么运行的。

vt = β vt - 1 + (1 - β )θ t

在上一节中,这个(红色)曲线对应 β 的值为0.9,这个(绿色)曲线对应的 β = 0.98,如果你执行写在这里的公式,在 β 等于0.98的时候,得到的并不是绿色曲线,而是紫色曲线,你可以注意到紫色曲线的起点较低,我们来看看怎么处理。

计算移动平均数的时候,初始化 v0 = 0v1 = 0.98v0 + 0.02θ1 ,但是 v0 = 0 ,所以这部分没有了( 0.98v0 ),所以 v1 = 0.02θ1 ,所以如果一天温度是40华氏度,那么 v1 = 0.02θ1 = 0.02 × 40 = 8 ,因此得到的值会小很多,所以第一天温度的估测不准。

v2 = 0.98v1 + 0.02θ2 ,如果代入 v1 ,然后相乘,所以 v2 = 0.98 × 0.02θ1 + 0.02θ2 = 0.0196θ1 + 0.02θ2 ,假设 θ1θ2 都是正数,计算后 v2 要远小于 θ1θ2 ,所以 v2 不能很好估测出这一年前两天的温度。

有个办法可以修改这一估测,让估测变得更好,更准确,特别是在估测初期,也就是不用 vt ,而是用 ( vt / 1 - βt ) ,t就是现在的天数。举个具体例子,当 t = 2 时, 1 - βt = 1 - 0.982 = 0.0396 ,因此对第二天温度的估测变成了 ( v2 / 0.0396 ) = ( 0.0196θ1 + 0.02θ2 / 0.0396 ) ,也就是 θ1θ2 的加权平均数,并去除了偏差。你会发现随着 t 增加, βt 接近于0,所以当 t 很大的时候,偏差修正几乎没有作用,因此当 t 较大的时候,紫线基本和绿线重合了。不过在开始学习阶段,你才开始预测热身练习,偏差修正可以帮助你更好预测温度,偏差修正可以帮助你使结果从紫线变成绿线。

在机器学习中,在计算指数加权平均数的大部分时候,大家不在乎执行偏差修正,因为大部分人宁愿熬过初始时期,拿到具有偏差的估测,然后继续计算下去。如果你关心初始时期的偏差,在刚开始计算指数加权移动平均数的时候,偏差修正能帮助你在早期获取更好的估测。

所以你学会了计算指数加权移动平均数,我们接着用它来构建更好的优化算法吧!

Momentum动量梯度下降法

还有一种算法叫做Momentum,或者叫做动量梯度下降法,运行速度几乎总是快于标准的梯度下降算法。简而言之,基本想法是计算梯度的指数加权平均数,并利用该梯度更新你的权重。看看它是如何计算的。

例如,如果你要优化成本函数,函数形状如图,红点代表最小值的位置,假设你从下图最左的蓝色点开始梯度下降法,如果进行梯度下降法的一次迭代,无论是batch或mini-batch下降法,再计算下一步梯度下降,然后再计算一步,再一步,计算下去,你会发现梯度下降法要很多计算步骤对吧?(见下图)

成本函数慢慢摆动到最小值,这种上下波动减慢了梯度下降法的速度,就无法使用更大的学习率,如果你要用较大的学习率(紫色箭头),结果可能会偏离函数的范围。为了避免摆动过大,要用一个较小的学习率。

另一个看待的角度是:在纵轴上,希望学习慢一点,因为不想这些摆动;在横轴上,希望加快学习,希望快速从左向右移最小值(红点)。使用动量梯度下降法,需要做的是,在每次迭代中,确切来说在第 t 次迭代的过程中,你会计算微分 dWdb ,这里省略了上标 [l] 。用当前的mini batch计算 dWdb 。如果你使用的是Batch梯度下降法,mini-batch就是全部的数据集,这个算法对于Batch梯度下降法的效果是一样的。如果现有的mini-batch就是整个训练集,这个算法的效果也不错。你要做的是计算 vdW = β vdW + (1 - β)dW ,这跟我们之前的计算相似,也就是 v = β v + (1 - β)θtdW 的移动平均数,接着同样地计算 vdbvdb = β vdb + (1 - β)db ,然后重新赋值权重, W = W - α vdW ,同样 b = b - α vdb ,这样就可以减缓梯度下降的幅度。

在上几个导数中,你会发现:

  • 纵轴上的摆动平均值接近于零,所以在纵轴方向,你希望放慢一点,平均过程中,正负数相互抵消,所以平均值接近于零。
  • 在横轴方向,所有的微分都指向横轴方向,因此横轴方向的平均值仍然较大,因此用算法几次迭代后,你发现动量梯度下降法,最终纵轴方向的摆动变小了,横轴方向运动更快,因此你的算法走了一条更加直接的路径,在抵达最小值的路上减少了摆动。

动量梯度下降法的一个本质就是,如果你要最小化碗状函数(Bowl-shape function)。它们能够最小化碗状函数,这些微分项(dwdb),想象它们为你从山上往下滚的一个球,提供了加速度,Momentum项相当于速度。

想象你有一个碗,你拿一个球,微分项给了这个球一个加速度,此时球正向山下滚,球因为加速度越滚越快,而因为 β 稍小于1,表现出一些摩擦力,所以球不会无限加速下去,所以不像梯度下降法,每一步都独立于之前的步骤,你的球可以向下滚,获得动量,可以从碗向下加速获得动量。这个球从碗滚下的比喻,物理知识理解力好的人接受得比较好,但不是所有人都能接受,如果球从碗中滚下这个比喻,你理解不了,也别担心。

最后我们来看具体如何计算,算法在此:

  • On iteration t:
    • vdW = β vdW + (1 - β)dW
    • vdb = β vdb + (1 - β)db
    • W = W - α vdW
    • b = b - α vdb
  • 超参数:α、β = 0.9

Python实现:

for i in range(1, iterations):
    v_dw[i] = beta * v_dw[i-1] + (1 - beta) * dw[i-1]
    v_db[i] = beta * v_db[i-1] + (1 - beta) * db[i-1]
    w = w - alpha * v_dw[i]
    b = b - alpha * v_db[i]

所以你有两个超参数,学习率 a 以及参数 ββ 控制着指数加权平均数。 β 最常用的值是0.9。在上节的例子里,约等于平均过去十天的温度,现在是约等于平均了前十次迭代的梯度。实际上 β 为0.9时,效果不错,你可以尝试不同的值,可以做一些超参数的研究,不过0.9是很棒的鲁棒数。

关于偏差修正,理论上你要拿 vdWvdb 除以 1 - βt 。实际上人们不这么做,因为10次迭代之后,你的移动平均已经过了初始阶段。实际中,在使用梯度下降法或动量梯度下降法时,人们不会受到偏差修正(Bias correction)的困扰。当然 vdW 初始值是0,要注意到这是和 dW 拥有相同维数的零矩阵,也就是跟 W 拥有相同的维数, vdb 的初始值也是向量零,所以和 db 拥有相同的维数,也就是和 b 是同一维数。

最后说一点,如果你查阅动量梯度下降法相关资料,你经常会看到 1 - β 被删除了,最后得到的是 vdW = β vdW + dW 。所以 vdW 缩小了 1 - β 倍,相当于乘以 (1/1 - β) ,所以你要用梯度下降最新值的话, α 要根据 (1/1 - β) 相应变化。实际上,二者效果都不错,只会影响到学习率 α 的最佳值。知识我觉得这个公式用起来没有那么直观,因为有一个影响,如果你最后要调整超参数 β ,就会影响到 vdWvdb ,你也许还要修改学习率 α ,所以我更喜欢有 1 - β 的公式,而不是删去了 1 - β 的公式,但是两个公式都将 β 设置为0.9,是超参数的常见选择,只是在这两个公式中,学习率 α 的调整会有所不同。

所以这就是动量梯度下降法,这个算法肯定要好于没有Momentum的梯度下降算法。

RMSprop

上节你知道了动量(Momentum)可以加快梯度下降,还有一个叫做RMSprop的算法,全称是Root Mean Square Prop(均方根传递)算法,它也可以加速梯度下降,我们来看看它是如何运作的。

回忆一下我们之前的例子,如果你执行梯度下降,虽然横轴方向正在推进,但纵轴方向会有大幅度摆动,为了分析这个例子,假设纵轴代表参数 b ,横轴代表参数 W ,可能有 W1W2 或者其它重要的参数,为了便于理解,被称为 bW

所以,你想减缓 b 方向的学习,即纵轴方向,同时加快横轴方向的学习,RMSprop算法可以实现这一点。

在第 t 次迭代中,该算法会照常计算当下mini-batch的微分 dWdb ,所以我会保留这个指数加权平均数,我们用到新符号 SdW ,而不是 vdW ,因此 SdW = β SdW + (1 - β)(dW)2 ,澄清一下,这个平方的操作是针对这一整个符号的,这样做能够保留微分平方的加权平均数,同样 Sdb = β Sdb + (1 - β)(db)2 ,再说一次,平方是针对整个符号的操作。接着RMSprop会这样更新参数值, W = W - α( dW / sqrt(SdW))b = b - α( db / sqrt(Sdb) )

最后我们来看具体如何计算,算法在此:

  • On iteration t:
    • SdW = β SdW + (1 - β)(dW)2
    • Sdb = β Sdb + (1 - β)(db)2
    • W = W - α( dW / sqrt(SdW))
    • b = b - α( db / sqrt(Sdb) )
  • 超参数:α、β = 0.9

Python实现:

for i in range(1, iterations):
    S_dw[i] = beta * S_dw[i-1] + (1 - beta) * np.power(dw[i-1], 2)
    S_db[i] = beta * S_db[i-1] + (1 - beta) * np.power(db[i-1], 2)
    w = w - alpha * dw / np.sqrt(S_dw[i])
    b = b - alpha * db / np.sqrt(S_db[i])

来理解一下其原理:

  • 在横轴方向或者在例子中的 W 方向,希望学习速度快。
  • 在垂直方向,也就是例子中的 b 方向,我们希望减缓纵轴上的摆动,所以有了 SdWSdb
  • 希望 SdW 会相对较小,因此这里除以的是一个较小的数
  • Sdb 相对又较大,所以我们要除以较大的数字,这样就可以减缓纵轴上的变化。

你看这些微分,垂直方向的要比水平方向的大得多,所以斜率在 b 方向特别大,所以这些微分中, db 较大, dW 较小。因为函数的倾斜程度,在纵轴上(b方向)要大于在横轴上( W 方向)。 db 的平方较大,所以 Sdb 也会较大,而相比之下, dW 会小一些,亦或 dW 平方会小一些,因此 SdW 会小一些,结果就是纵轴上的更新要被一个较大的数相除,就能消除摆动,而水平方向的更新则被较小的数相除。

RMSprop的影响就是你的更新最后会变成这样(上图绿色线),纵轴方向上摆动较小,而横轴方向继续推进。还有个影响就是,你可以用一个更大学习率 α ,然后加快学习,而无须在纵轴上垂直方向偏离。

要说明一点,我一直把纵轴和横轴方向分别称为 bW ,只是为了方便展示而已。实际中,你会处于参数的高维度空间,所以需要消除摆动的垂直维度,实际上是参数 W1W2 等的合集,水平维度可能 W3W4 等等。上面例子把 Wb 分开只是方便说明。实际中 dW 是一个高维度的参数向量, db 也是一个高维度参数向量,但是你的直觉是,在你要消除摆动的维度中,最终你要计算一个更大的和值,这个平方和微分的加权平均值,所以你最后去掉了那些有摆动的方向。所以这就是RMSprop,全称是均方根传递,因为你将微分进行平方,然后最后使用平方根。

最后说下算法的细节。下一节中,我们会将RMSpropMomentum结合起来,我们在Momentum中采用超参数 β ,为了避免混淆,现在不用 β ,而采用超参数 β2 以保证在MomentumRMSprop中采用同一超参数。要确保你的算法不会除以0,如果 SdW 的平方根趋近于0怎么办?得到的答案就非常大,为了确保数值稳定,在实际操练的时候,你要在分母上加上一个很小很小的 ϵϵ 是多少没关系, 10 - 8 是个不错的选择,这只是保证数值能稳定一些,无论什么原因,你都不会除以一个很小很小的数。所以RMSpropMomentum有很相似的一点,可以消除梯度下降中的摆动,包括mini-batch梯度下降,并允许你使用一个更大的学习率 α ,从而加快你的算法学习速度。

这就是RMSprop,是给另一个给学习算法加速的方法。关于RMSprop的一个有趣的事是,它首次提出并不是在学术论文中,而是在多年前Jeff Hinton在Coursera的课程上。我想Coursera并不是故意打算成为一个传播新兴的学术研究的平台,但是却达到了意想不到的效果。就是从Coursera课程开始,RMSprop开始被人们广为熟知,并且发展迅猛。

Adam

在深度学习的历史上,许多知名研究者提出了多种优化算法,并很好地解决了一些问题,但随后这些算法被指出并不能很好地一般化,不适用于多种神经网络。时间久了,深度学习圈子开始质疑全新的优化算法。很多人觉得动量(Momentum)梯度下降法很好用,很难再想出更好的优化算法。RMSpropAdam算法(Adaptive Moment Estimation)便是少有的经受住了人们考验的算法,已被证明适用于不同的深度学习结构。

Adam优化算法基本上就是将MomentumRMSprop结合在一起,来看看如何使用Adam算法。

Adam算法:

  1. 初始化:
    • vdW = 0SdW = 0vdb = 0Sdb = 0
  2. 在第 t 次迭代中,要计算微分,用当前的mini-batch计算 dWdb ,一般用mini-batch梯度下降法
  3. 然后计算Momentum指数加权平均数
    • vdW = β1vdW + (1 - β1)dW
    • 使用 β1 ,这样就不会跟超参数 β2 混淆,因为后面RMSprop要用到 β2 。使用Momentum时也用这个公式,但现在不叫它 β ,而是 β1
    • vdb = β1vdb + ( 1 - β1 )db
  4. 接着用RMSprop进行更新,即用不同的超参数 β2
    • SdW = β2SdW + ( 1 - β2)(dW)2
      • 这里是对整个微分 dW 进行平方处理,
    • Sdb = β2Sdb + ( 1 - β2 )(db)2
  5. 相当于Momentum更新了超参数 β1RMSprop更新了超参数 β2 。一般使用Adam算法的时候,要计算偏差修正, vdWcorrected ,修正也就是在偏差修正之后
    • vdWcorrected = ( vdW / 1 - β1t )
    • vdbcorrected = ( vdb / 1 - β1t )
    • SdWcorrected = ( SdW / 1 - β2t )
    • Sdbcorrected = ( Sdb / 1 - β2t )
  6. 最后更新权重
    • W = W - ( α vdWcorrected / sqrt(SdWcorrected) + ϵ )
      • 如果只用Momentum,使用 vdW 或者修正后的 vdW ,但现在加入了RMSprop的部分,所以要除以修正后 sqrt(SdW) + ϵ )。
    • b = b - ( α vdbcorrected / sqrt(Sdbcorrected) + ϵ )

所以Adam算法结合了MomentumRMSprop梯度下降法,并且是一种极其常用的学习算法,被证明能有效适用于不同神经网络,适用于广泛的结构。

本算法中有很多超参数,超参数学习率 α 很重要,也经常需要调试,你可以尝试一系列值,然后看哪个有效。

  • β1 常用的缺省值为0.9,这是dW的移动平均数,也就是 dW 的加权平均数,是Momentum涉及的项。
  • β2Adam论文作者推荐使用0.999,这是在计算 (dW)2 以及 (db)2 的移动加权平均值,
  • ϵ 的选择其实没那么重要,Adam论文的作者建议 ϵ10 - 8 ,但你并不需要设置它,因为它并不会影响算法表现。

在使用Adam的时候,人们往往使用缺省值即可, β1β2ϵ 都是如此,我觉得没人会去调整 ϵ ,然后尝试不同的 a 值,看看哪个效果最好。你也可以调整 β1β2 ,但我认识的业内人士很少这么干。

为什么这个算法叫做Adam?Adam代表的是Adaptive Moment Estimationβ1 用于计算这个微分( dW ),叫做第一矩, β2 用来计算平方数的指数加权平均数( (dW)2 ),叫做第二矩,所以Adam的名字由此而来,但是大家都简称Adam权威算法。

学习率衰减

加快学习算法的一个办法就是随时间慢慢减少学习率,我们将之称为学习率衰减(Learning Rate Decay)。首先通过一个例子看看,为什么要计算学习率衰减。

假设你要使用mini-batch梯度下降法,mini-batch数量不大,大概64或者128个样本,在迭代过程中会有噪音(蓝色线),下降朝向这里的最小值,但是不会精确地收敛,所以你的算法最后在附近摆动,并不会真正收敛,因为你用的 α 是固定值,不同的mini-batch中有噪音。

但要慢慢减少学习率 α 的话,在初期的时候, α 学习率还较大,你的学习还是相对较快,但随着 a 变小,你的步伐也会变慢变小,所以最后你的曲线(绿色线)会在最小值附近的一小块区域里摆动,而不是在训练过程中,大幅度在最小值附近摆动。

所以慢慢减少 α 的本质在于,在学习初期,你能承受较大的步伐,但当开始收敛的时候,小一些的学习率能让你步伐小一些。

你可以这样做到学习率衰减,记得一代要遍历一次数据,如果你有以下这样的训练集,

你应该拆分成不同的mini-batch,第一次遍历训练集叫做第一代。第二次就是第二代,依此类推,你可以将 _α_ 学习率设为 _α = ( 1 / 1 + decayrate * epoch_num 0_ (**decay-rate**称为衰减率,**epoch-num**为代数, _α0_ 为初始学习率),注意这个衰减率是另一个你需要调整的超参数。

这里有一个具体例子,如果你计算了几代,也就是遍历了几次,如果 α0 为0.2,衰减率decay - rate为1,那么在第一代中, α = ( 1 / 1 + 1 0 = 0.1 ,这是在代入这个公式计算( α = ( 1 / 1 + decayrate * epoch_num 0 ),此时衰减率是1而代数是1。在第二代学习率为0.67,第三代变成0.5,第四代为0.4等等(如下表)。

Epoch α
1 0.1
2 0.067
3 0.05
4 0.04
... ...

要理解,作为代数函数,根据上述公式,你的学习率呈递减趋势。如果你想用学习率衰减,要做的是要去尝试不同的值,包括超参数 α0 ,以及超参数衰退率,找到合适的值,除了这个学习率衰减的公式,人们还会用其它的公式:

  • 指数衰减:其中 α 相当于一个小于1的值,如 α = 0.95epoch_num a0 ,所以学习率呈指数下降。

  • 人们用到的其它公式有 α = ( k / sqrt(epoch_num) 0 或者 α = ( k / sqrt(t) 0t 为mini-batch的数字)。

  • 有时也会用一个离散下降的学习率,也就是某个步骤有某个学习率,一会之后,学习率减少了一半,一会儿减少一半,一会儿又一半,这就是离散下降(discrete stair cease)的意思。

到现在,我们讲了一些公式,看学习率 α 究竟如何随时间变化。有时候还会手动衰减。如果你一次只训练一个模型,如果你要花上数小时或数天来训练,有些人的确会这么做,看看自己的模型训练,耗上数日,然后他们觉得,学习速率变慢了,我把 α 调小一点。手动控制 a 当然有用,日复一日地手动调整 α ,只有模型数量小的时候有用,但有时候人们也会这么做。

所以现在有了多个选择来控制学习率 α 。你可能会想,好多超参数,究竟我应该做哪一个选择,我觉得,现在担心为时过早。下一章,我们会讲到,如何系统选择超参数。对我而言,学习率衰减并不是我尝试的要点,设定一个固定的 α ,然后好好调整,会有很大的影响,学习率衰减的确大有裨益,有时候可以加快训练,但它并不是我会率先尝试的内容,但下周我们将涉及超参数调整,你能学到更多系统的办法来管理所有的超参数,以及如何高效搜索超参数。

局部最优的问题

在深度学习研究早期,人们总是担心优化算法会困在极差的局部最优,不过随着深度学习理论不断发展,我们对局部最优的理解也发生了改变。这里向你展示一下现在我们怎么看待局部最优以及深度学习中的优化问题。

下图是曾经人们想到局部最优时脑海里会出现的图,也许你想优化一些参数,我们把它们称之为 W1W2 ,平面的高度就是损失函数。在图中似乎各处都分布着局部最优。梯度下降法或者某个算法可能困在一个局部最优中,而不会抵达全局最优。如果你要作图计算一个数字,比如说这两个维度,就容易出现有多个不同局部最优的图,而这些低维的图曾经影响了我们的理解,但是这些理解并不正确。事实上,如果你要创建一个神经网络,通常梯度为零的点并不是这个图中的局部最优点,实际上成本函数的零梯度点,通常是鞍点。

也就是上图中标记的那些点。

上面讨论的例子是这里是 W1W2 ,高度即成本函数 J 的值。但是对一个高维度空间的函数,如果梯度为0,那么在每个方向,它可能是凸函数,也可能是凹函数。如果你在2万维空间中,那么想要得到局部最优,所有的2万个方向都需要是局部最小,但发生的机率也许很小,也许是 2 - 20000 ,你更有可能遇到有些方向的曲线会这样向上弯曲,另一些方向曲线向下弯,而不是所有的都向上弯曲,因此在高维度空间,你更可能碰到鞍点。

就像下面的这种:

而不会碰到局部最优。至于为什么会把一个曲面叫做鞍点,你想象一下,就像是放在马背上的马鞍一样,如果这是马,这是马的头,这就是马的眼睛。然后你就是骑马的人,要坐在马鞍上,因此这里的这个点,导数为0的点,这个点叫做鞍点。我想那确实是你坐在马鞍上的那个点,而这里导数为0。

所以我们从深度学习历史中学到的一课就是,我们对低维度空间的大部分直觉,比如你可以画出上面的图,并不能应用到高维度空间中适用于其它算法,因为如果你有2万个参数,那么 J 函数有2万个维度向量,你更可能遇到鞍点,而不是局部最优点。

如果局部最优不是问题,那么问题是什么?结果是平稳段会减缓学习,平稳段是一块区域,其中导数长时间接近于0,如果你在此处,梯度会从曲面从上向下下降,因为梯度等于或接近0,曲面很平坦,你得花上很长时间慢慢抵达平稳段的这个点,因为左边或右边的随机扰动,然后你的算法能够走出平稳段(红色笔)。

我们可以沿着这段长坡走,直到这里,然后走出平稳段。

所以的要点是:
  • 首先,你不太可能困在极差的局部最优中,条件是你在训练较大的神经网络,存在大量参数,并且成本函数 J 被定义在较高的维度空间。
  • 第二点,平稳段是一个问题,这样使得学习十分缓慢,这也是像Momentum或是RMSpropAdam这样的算法,能够加速学习算法的地方。在这些情况下,更成熟的优化算法,如Adam算法,能够加快速度,让你尽早往下走出平稳段。

因为你的网络要解决优化问题,说实话,要面临如此之高的维度空间,我觉得没有人有那么好的直觉,知道这些空间长什么样,而且我们对它们的理解还在不断发展,不过希望这一点能够让你更好地理解优化算法所面临的问题。

项目练习

请见GitHub:

回到首页