核心内容摘要
5步掌握KH Coder:让文本分析效率提升80%的开源工具
参数的更新神经网络的学习的目的是找到使损失函数的值尽可能小的参数。
这是寻找最优参数的问题解决这个问题的过程称为最优化optimization。
遗憾的是神经网络的最优化问题非常难。
这是因为参数空间非常复杂无法轻易找到最优解无法使用那种通过解数学式一下子就求得最小值的方法。
而且在深度神经网络中参数的数量非常庞大导致最优化问题更加复杂。
在前几章中为了找到最优参数我们将参数的梯度导数作为了线索。
使用参数的梯度沿梯度方向更新参数并重复这个步骤多次从而逐渐靠近最优参数这个过程称为随机梯度下降法stochastic gradient descent简称SGD。
SGD是一个简单的方法不过比起胡乱地搜索参数空间也算是“聪明”的方法。
但是根据不同的问题也存在比SGD更加聪明的方法。
本节我们将指出SGD的缺点并介绍SGD以外的其他最优化方法。
探险家的故事进入正题前我们先打一个比方来说明关于最优化我们所处的状况。
寻找最优参数时我们所处的状况和这位探险家一样是一个漆黑的世界。
我们必须在没有地图、不能睁眼的情况下在广袤、复杂的地形中寻找“至深之地”。
大家可以想象这是一个多么难的问题。
在这么困难的状况下地面的坡度显得尤为重要。
探险家虽然看不到周围的情况但是能够知道当前所在位置的坡度通过脚底感受地面的倾斜状况。
于是朝着当前所在位置的坡度最大的方向前进就是SGD的策略。
勇敢的探险家心里可能想着只要重复这一策略总有一天可以到达“至深之地”。
SGD让大家感受了最优化问题的难度之后我们再来复习一下SGD。
用数学式可以将SGD写成如下的式
1。
W←W−η∂L∂W W \leftarrow W - \eta \frac{\partial L}{\partial W}W←W−η∂W∂L这里把需要更新的权重参数记为WWW把损失函数关于WWW的梯度记为∂L∂W\frac{\partial L}{\partial W}∂W∂L。
$η $表示学习率实际上会取
01 或
001 这些事先决定好的值。
式子中的←表示用右边的值更新左边的值。
如式
1所示SGD是朝着梯度方向只前进一定距离的简单方法。
现在我们将SGD实现为一个Python 类为方便后面使用我们将其实现为一个名为SGD的类。
class SGD: def __init__(self, lr
0.
: self.lr lr def update(self, params, grads): for key in params.keys(): params[key] - self.lr * grads[key]这里进行初始化时的参数lr表示learning rate学习率。
这个学习率会保存为实例变量。
此外代码段中还定义了update(params, grads)方法这个方法在SGD中会被反复调用。
参数params和grads与之前的神经网络的实现一样是字典型变量按params[‘W1’]、grads[‘W1’]的形式分别保存了权重参数和它们的梯度。
使用这个SGD类可以按如下方式进行神经网络的参数的更新下面的代码是不能实际运行的伪代码。
networkTwoLayerNet(...)optimizerSGD()foriinrange(
:...x_batch,t_batchget_mini_batch(...)# mini-batchgradsnetwork.gradient(x_batch,t_batch)paramsnetwork.params optimizer.update(params,grads)这里首次出现的变量名optimizer表示“进行最优化的人”的意思这里由SGD承担这个角色。
参数的更新由optimizer负责完成。
我们在这里需要做的只是将参数和梯度的信息传给optimizer。
像这样通过单独实现进行最优化的类功能的模块化变得更简单。
比如后面我们马上会实现另一个最优化方法Momentum它同样会实现成拥有update(params, grads)这个共同方法的形式。
这样一来只需要将optimizer SGD()这一语句换成optimizer Momentum()就可以从SGD切换为Momentum。
SGD 的缺点虽然SGD简单并且容易实现但是在解决某些问题时可能没有效率。
这里在指出SGD的缺点之际我们来思考一下求下面这个函数的最小值的问题。
f(x,y)120x2y2 f(x, y) \frac{1}{20}x^2 y^2f(x,y)201x2y2如图
所示式
2表示的函数是向x 轴方向延伸的“碗”状函数。
实际上式
2的等高线呈向x轴方向延伸的椭圆状。
现在看一下式
2表示的函数的梯度。
如果用图表示梯度的话则如图
所示。
这个梯度的特征是yyy轴方向上大xxx轴方向上小。
换句话说就是yyy轴方向的坡度大而x轴方向的坡度小。
这里需要注意的是虽然式
2的最小值在(x,y)(0,
(x, y) (0,
(x,y)(0,
处但是图
中的梯度在很多地方并没有指向(0,
(0,
(0,
。
我们来尝试对图
这种形状的函数应用SGD。
从(x,y)(−
0,
2.
(x, y) (−
0,
2.
(x,y)(−
0,
2.
处初始值开始搜索结果如图
所示。
在图
中SGD呈“之”字形移动。
这是一个相当低效的路径。
也就是说SGD的缺点是如果函数的形状非均向anisotropic比如呈延伸状搜索的路径就会非常低效。
因此我们需要比单纯朝梯度方向前进的SGD更聪明的方法。
SGD低效的根本原因是梯度的方向并没有指向最小值的方向。
为了改正SGD的缺点下面我们将介绍Momentum、AdaGrad、Adam这3种方法来取代SGD。
我们会简单介绍各个方法并用数学式和Python进行实现。
MomentumMomentum是“动量”的意思和物理有关。
用数学式表示Momentum方法如下所示。
本次提取到的公式是动量法Momentum中的参数更新步骤包含两个公式v←αv−η∂L∂W v \leftarrow \alpha v - \eta \frac{\partial L}{\partial W}v←αv−η∂W∂LW←Wv W \leftarrow W vW←Wv和前面的SGD一样WWW表示要更新的权重参数∂L∂W\frac{\partial L}{\partial W}∂W∂L表示损失函数关于WWW的梯度ηηη表示学习率。
这里新出现了一个变量vvv对应物理上的速度。
式
3表示了物体在梯度方向上受力在这个力的作用下物体的速度增加这一物理法则。
如图
所示Momentum方法给人的感觉就像是小球在地面上滚动。
式
3中有αv\alpha vαv这一项。
在物体不受任何力时该项承担使物体逐渐减速的任务ααα设定为
9 之类的值对应物理上的地面摩擦或空气阻力。
下面是Momentum的代码实现源代码在common/optimizer.py中。
classMomentum:def__init__(self,lr
01,momentum
0.
:self.lrlr self.momentummomentum self.vNonedefupdate(self,params,grads):ifself.visNone:self.v{}forkey,valinparams.items():self.v[key]np.zeros_like(val)forkeyinparams.keys():self.v[key]self.momentum*self.v[key]-self.lr*grads[key]params[key]self.v[key]实例变量v会保存物体的速度。
初始化时v中什么都不保存但当第一次调用update()时v会以字典型变量的形式保存与参数结构相同的数据。
剩余的代码部分就是将式
6.
式
4写出来很简单。
现在尝试使用Momentum解决式
2的最优化问题如图
所示。
图
中更新路径就像小球在碗中滚动一样。
和SGD相比我们发现“之”字形的“程度”减轻了。
这是因为虽然xxx轴方向上受到的力非常小但是一直在同一方向上受力所以朝同一个方向会有一定的加速。
反过来虽然yyy轴方向上受到的力很大但是因为交互地受到正方向和反方向的力它们会互相抵消所以$y轴方向上的速度不稳定。
因此和SGD时的情形相比可以更快地朝轴方向上的速度不稳定。
因此和SGD时的情形相比 可以更快地朝轴方向上的速度不稳定。
因此和SGD时的情形相比可以更快地朝x$轴方向靠近减弱“之”字形的变动程度。
AdaGrad在神经网络的学习中学习率数学式中记为ηηη的值很重要。
学习率过小会导致学习花费过多时间反过来学习率过大则会导致学习发散而不能正确进行。
在关于学习率的有效技巧中有一种被称为学习率衰减learning ratedecay的方法即随着学习的进行使学习率逐渐减小。
实际上一开始“多”学然后逐渐“少”学的方法在神经网络的学习中经常被使用。
逐渐减小学习率的想法相当于将“全体”参数的学习率值一起降低。
而AdaGrad [6] 进一步发展了这个想法针对“一个一个”的参数赋予其“定制”的值。
本次提取到的公式是动量法Momentum中的参数更新步骤包含两个公式h←h∂L∂W⊙∂L∂W h \leftarrow h \frac{\partial L}{\partial W} \odot \frac{\partial L}{\partial W}h←h∂W∂L⊙∂W∂LW←W−η1h∂L∂W W \leftarrow W - \eta \frac{1}{\sqrt{h}} \frac{\partial L}{\partial W}W←W−ηh1∂W∂L和前面的SGD一样WWW表示要更新的权重参数∂L∂W\frac{\partial L}{\partial W}∂W∂L表示损失函数关于W的梯度ηηη表示学习率。
这里新出现了变量hhh如式(
6.
所示它保存了以前的所有梯度值的平方和式
5中的表示对应矩阵元素的乘法。
然后在更新参数时通过乘以1h\frac{1}{\sqrt{h}}h1就可以调整学习的尺度。
这意味着参数的元素中变动较大被大幅更新的元素的学习率将变小。
也就是说可以按参数的元素进行学习率衰减使变动大的参数的学习率逐渐减小。
AdaGrad 会记录过去所有梯度的平方和。
因此学习越深入更新的幅度就越小。
实际上如果无止境地学习更新量就会变为0完全不再更新。
为了改善这个问题可以使用RMSProp [7] 方法。
RMSProp 方法并不是将过去所有的梯度一视同仁地相加而是逐渐地遗忘过去的梯度在做加法运算时将新梯度的信息更多地反映出来。
这种操作从专业上讲称为“指数移动平均”呈指数函数式地减小过去的梯度的尺度。
现在来实现AdaGrad。
AdaGrad 的实现过程如下所示源代码在common/optimizer.py中。
classAdaGrad:def__init__(self,lr
0.
:self.lrlr self.hNonedefupdate(self,params,grads):ifself.hisNone:self.h{}forkey,valinparams.items():self.h[key]np.zeros_like(val)forkeyinparams.keys():self.h[key]grads[key]*grads[key]params[key]-self.lr*grads[key]/(np.sqrt(self.h[key])1e-
这里需要注意的是最后一行加上了微小值1e-7。
这是为了防止当self.h[key]中有0 时将0 用作除数的情况。
在很多深度学习的框架中这个微小值也可以设定为参数但这里我们用的是1e-7这个固定值。
现在让我们试着使用AdaGrad解决式
2的最优化问题结果如图
所示。
由图
的结果可知函数的取值高效地向着最小值移动。
由于$y轴方向上的梯度较大因此刚开始变动较大但是后面会根据这个较大的变动按比例进行调整减小更新的步伐。
因此轴方 向上的梯度较大因此刚开始变动较大但是后面会根据这个较大的变动按 比例进行调整减小更新的步伐。
因此轴方向上的梯度较大因此刚开始变动较大但是后面会根据这个较大的变动按比例进行调整减小更新的步伐。
因此y$ 轴方向上的更新程度被减弱“之”字形的变动程度有所衰减。
AdamMomentum参照小球在碗中滚动的物理规则进行移动AdaGrad为参数的每个元素适当地调整更新步伐。
如果将这两个方法融合在一起会怎么样呢这就是Adam[8]方法的基本思路A。
Adam是2015 年提出的新方法。
它的理论有些复杂直观地讲就是融合了Momentum和AdaGrad的方法。
通过组合前面两个方法的优点有望实现参数空间的高效搜索。
此外进行超参数的“偏置校正”也是Adam的特征。
这里不再进行过多的说明详细内容请参考原作者的论文[8]。
关于Python的实现common/optimizer.py中将其实现为了Adam类有兴趣的读者可以参考。
现在我们试着使用Adam解决式
2的最优化问题结果如图
所示。
Adam是2015 年提出的新方法。
它的理论有些复杂直观地讲就是融合了Momentum和AdaGrad的方法。
通过组合前面两个方法的优点有望实现参数空间的高效搜索。
此外进行超参数的“偏置校正”也是Adam的特征。
这里不再进行过多的说明详细内容请参考原作者的论文[8]。
关于Python的实现common/optimizer.py中将其实现为了Adam类有兴趣的读者可以参考。
现在我们试着使用Adam解决式
2的最优化问题结果如图
所示。
在图
中基于Adam的更新过程就像小球在碗中滚动一样。
虽然Momentun也有类似的移动但是相比之下Adam的小球左右摇晃的程度有所减轻。
这得益于学习的更新程度被适当地调整了。
使用哪种更新方法呢到目前为止我们已经学习了4 种更新参数的方法。
这里我们来比较一下这4 种方法源代码在ch06/optimizer_compare_naive.py中如图
所示根据使用的方法不同参数更新的路径也不同。
只看这个图的话AdaGrad似乎是最好的不过也要注意结果会根据要解决的问题而变。
并且很显然超参数学习率等的设定值不同结果也会发生变化。
上面我们介绍了SGD、Momentum、AdaGrad、Adam这4 种方法那么用哪种方法好呢非常遗憾目前并不存在能在所有问题中都表现良好的方法。
这4 种方法各有各的特点都有各自擅长解决的问题和不擅长解决的问题。
很多研究中至今仍在使用SGD。
Momentum和AdaGrad也是值得一试的方法。
最近很多研究人员和技术人员都喜欢用Adam。
本书将主要使用SGD或者Adam读者可以根据自己的喜好多多尝试。
基于MNIST 数据集的更新方法的比较我们以手写数字识别为例比较前面介绍的SGD、Momentum、AdaGrad、Adam这4 种方法并确认不同的方法在学习进展上有多大程度的差异。
先来看一下结果如图
所示源代码在ch06/optimizer_compare_mnist.py中。
这个实验以一个5 层神经网络为对象其中每层有100 个神经元。
激活函数使用的是ReLU。
从图
的结果中可知与SGD相比其他3 种方法学习得更快而且速度基本相同仔细看的话AdaGrad的学习进行得稍微快一点。
这个实验需要注意的地方是实验结果会随学习率等超参数、神经网络的结构几层深等的不同而发生变化。
不过一般而言与SGD相比其他3 种方法可以学习得更快有时最终的识别精度也更高。