前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >斯坦福CS231n - CNN for Visual Recognition(7)-lecture6梯度检查、参数更新

斯坦福CS231n - CNN for Visual Recognition(7)-lecture6梯度检查、参数更新

作者头像
李智
发布2018-08-03 17:24:23
6840
发布2018-08-03 17:24:23
举报
文章被收录于专栏:李智的专栏李智的专栏

本节主要介绍了神经网络梯度检查和参数更新过程

梯度检查

  梯度检查是非常重要的一个环节,就是将解析梯度和数值计算梯度进行比较。数值计算梯度时,使用中心化公式

df(x)dx=f(x+h)−f(x−h)2h(推荐使用)

\frac{df(x)}{dx} = \frac{f(x + h) - f(x - h)}{2h} \hspace{0.1in} \text{(推荐使用)}   其中hh在实践中近似为1e−51e^{-5}。不要使用以下公式

df(x)dx=f(x+h)−f(x)h(不要使用)

\frac{df(x)}{dx} = \frac{f(x + h) - f(x)}{h} \hspace{0.1in} \text{(不要使用)}   中心化公式在检查梯度的每个维度的时候,会计算两次损失函数(计算量为两倍),但梯度的近似值会准确很多。要详细理解,可对f(x+h)和f(x−h)f(x+h)和f(x-h)使用泰勒展开,可以看到第一个公式的误差近似O(h)O(h),第二个公式的误差近似O(h2)O(h^2)。 使用相对误差做比较。在得到数值梯度f′nf^′_n和解析梯度f′af^′_a之后,如何去比较呢?取绝对值|f'a−f'n||f′a−f′n|或差的平方(f'a−f'n)2(f′a−f′n)^2。两种方式都不好,因为如果本身梯度值就很小,即使相差比较小,两者相差也很大。因此要使用相对误差:

∣f′a−f′n∣max(∣f′a∣,∣f′n∣)

\frac{∣f^′_a−f^′_n∣}{max(∣f^′_a∣,∣f^′_n∣)}

  加maxmax项的原因很简单:整体形式变得简单和对称。还必须注意两个式子都为零且通过梯度检查的情况。   对于相对误差而言:

  • 相对误差>1e−2相对误差>1e^{−2} :意味着解析梯度计算出错
  • 1e−2>相对误差>1e−41e^{−2>}相对误差>1e^{−4}:解析梯度可能出错
  • 1e−4>相对误差1^{e−4}>相对误差:基本正确,但是对一些不可导的目标函数tanh或softmax)还是太大
  • 1e−71e^{-7}或者更小:解析梯度计算正确
  • 当然,神经网络的层数越多,相对误差就越高。所以如果你是对10层神经网络做梯度检查,那么1e−21e^{-2}就没啥问题,因为误差一直在累积。相反,如果一个可微函数的相对误差值是1e−21e^{-2},那么通常说明解析题都出错了。

注意: 使用双精度。不要使用单精度浮点数来进行梯度检查。这样做即使梯度实现正确,相对误差值也会很高(比如1e−21e^{-2})。 留意浮点数的有效范围。建议看下这篇文章《What Every Computer Scientist Should Know About Floating-Point Arithmetic》。如果梯度值很小,就会出现问题。通常会将解析梯度和数值梯度显示出来,以保证计算时,所有的数都在浮点数的可计算范围内,如果太小(<1e−10<1e^{-10})可考虑乘个常数。 目标函数的不可导点(kinks)。不可导点是指目标函数不可导的部分,比如ReLU(max(0,x)max(0,x)),SVM损失或Maxout神经元。考虑当x=1e−6x=1e^{-6}时,对ReLU函数进行梯度检查。因为x=1e−6<0x=1e^{-6}<0,所以解析梯度在该点梯度为0(见图)。然而,该点的数值梯度会突然计算出一个非零的梯度值,因为f(x+h)f(x+h)可能越过了不可导点(当h>1e−6h>1e^{-6}时,(f(x+h)−f(x−h))/2h>0(f(x+h)-f(x-h))/2h>0),结果非0。实际上这种情况很常见。一个用SVM进行分类的神经网络如果采用ReLU,会有更多的不可导点。

这里写图片描述
这里写图片描述

ReLU函数

  注意,在计算损失的过程中可以知道不可导点有没有被越过。在比如max(x,y)max(x,y)函数,可以前向传播时持续跟踪x,yx,y中最大值,当计算f(x+h)和f(x−h)f(x+h)\text{和}f(x-h)时,如果有一个值正负变了,那就说明不可导点被越过,数值梯度会不准确。 使用少量数据点。含有不可导点的损失函数(如ReLU或边缘损失等)的数据点越少,不可导点就越少,在计算有限差值近似时越过不可导点的几率就越小。如果你的梯度检查对2-3个数据点都有效,那么基本在整个批量数据上也没问题。因此使用很少量的数据点,能让梯度检查更迅速高效。 谨慎设置步长hh。hh不是越小越好,因为当hh特别小时,可能会遇到数值精度问题。有时如果梯度检查无法进行,可以试试将hh调到1e−4或1e−61e^{-4}或1e^{-6},梯度检查可能就恢复正常。下图是选取hh不同值的影响:

  为了安全起见,最好让网络学习(“预热”)一小段时间,等到损失函数开始下降的之后再进行梯度检查。在第一次迭代就进行梯度检查的危险就在于,此时可能正处在不正常的边界情况,从而掩盖了梯度没有正确实现的事实。 不要让正则化项吞没数据。通常损失函数为数据损失部分与正则化部分之和。因此如果正则化部分盖过了数据部分,那么主要的梯度来源于正则化项,这样就会掩盖掉数据损失梯度的错误。因此,推荐先关掉正则化(λ=0\lambda=0)对数据损失做单独检查,然后对正则化做单独检查。正则化的单独检查可以去掉其中数据损失的部分,也可以提高正则化强度,确认其效果在梯度检查中是无法忽略的,这样不正确的实现就会被观察到了。 关闭随机失活(dropout)和数据扩张(augmentation)。它们会在计算数值梯度的时候导致巨大误差。关闭它们的负面影响是无法对它们进行梯度检查(如随机失活的反向传播实现可能出错)。因此,一个更好的解决方案就是在计算f(x+h)和f(x−h)f(x+h)和f(x-h)前强制增加一个特定的随机种子,在计算解析梯度时也同样如此。 检查少量的维度。实际中,梯度可以有上百万的参数,在这种情况下只能检查其中一些维度然后假设其他维度是正确的。注意要在所有不同的参数中都抽取一部分来梯度检查。


学习前检查的提示与技巧

  在参数更新,优化之前,最好进行合理性检查。 寻找特定情况的正确损失值。在使用小参数进行初始化时,确保得到的损失值与期望一致。最好先单独检查数据损失(正则化为0)。例如,对CIFAR-10数据集,Softmax分类器的期望初始损失值是2.302,因为初始时预计每个类别的概率是0.1(10类),然后Softmax损失值正确分类的负对数概率:−ln(0.1)=2.302-ln(0.1)=2.302。对于Weston Watkins SVM分类器Li=∑j≠yimax(0,sj−syi+1)\displaystyle L_i=\sum\nolimits_{j\not=y_i}max(0,s_j-s_{y_i}+1),假设所有的边界都被越过(所有分值近似为零),所以损失值是9(对于每个错误分类,边界值是1)。如果没看到这些损失值,那么初始化中就可能有问题。   加入正则化时,损失函数值会增加。 对小数据子集过拟合。在整个数据集训练之前,尝试在一个很小的数据集上进行训练(比如20个数据),然后确保能到达0的损失值(关闭正则化)。如果不能的话,说明有错误,进行整个数据集训练是没有意义的。但是,能对小数据集进行过拟合依然有可能不正确。比如,因为某些错误,数据点的特征是随机的,这样算法也可能对小数据进行过拟合,但是在整个数据集上跑算法的时候,就没有任何泛化能力。


学习中对参数进行跟踪

跟踪损失函数

  左图显示了不同学习率下损失函数优化效果。过低的学习率使损失函数近似线性下降。合适的学习率损失函数会呈几何指数下降,更高的学习率会让损失值很快下降,但是损失函数值最终很差。这是因为学习率太大,参数在随机震荡,导致越过低点而不能取到很好的值。   右图显示了一个典型的随时间变化的损失函数值,这使在CIFAR-10数据集上面训练的一个小的网络,虽然损失函数值曲线看起来比较合理(可能学习率有点小),但是可以看出批数据的数量可能太小(损失值噪音很大)。   损失值的震荡程度和批尺寸(batch size)有关,当批尺寸为1,震荡会相对较大。当批尺寸就是整个数据集时震荡就会最小,因为每个梯度更新都是单调地优化损失函数(除非学习率很大)。   也可用对数域对损失函数值作图。因为学习过程一般是指数型的形状,图表就会看起来更像是直线。

跟踪训练集和验证集正确率

  训练集准确率和验证集准确率中间的空隙指明了模型过拟合的程度。在图中,蓝色的验证集曲线相较于训练集,准确率低了很多,这说明模型有很强的过拟合。此时,应增大正则化强度(更强的L2权重惩罚,更多的随机失活等)或收集更多的数据。   另一种可能就是验证集曲线和训练集曲线差不多,但是准确率不高,这种情况说明你的模型容量还不够大:应该通过增加参数数量让模型容量更大些。  

跟踪权重更新比例

  跟踪权重中更新值的数量和全部值的数量之间的比例。注意:是更新的那一块权重,而不是原始梯度(比如,在普通sgd中就是梯度乘以学习率)。需要对每个参数集的更新比例进行单独的计算和跟踪。实际终这个比例应该在1e−31e^{-3}左右。如果更低,说明学习率可能太小;如果更高,说明学习率可能太高。下面是具体例子:

代码语言:javascript
复制
# 假设参数向量为W,其梯度向量为dW
param_scale = np.linalg.norm(W.ravel())
update = -learning_rate*dW # 简单SGD更新
update_scale = np.linalg.norm(update.ravel())
W += update # 实际更新
print update_scale / param_scale # 要得到1e-3左右

  有研究者更喜欢计算和跟踪梯度的范式及其更新。这些矩阵通常是相关的,也能得到近似的结果。

跟踪每层激活数据及梯度分布

  不正确的初始化可能让学习过程变慢,甚至停止。这个问题可以比较简单地诊断出来。其中一个方法是输出网络中所有层的激活数据和梯度分布的柱状图。如果看到任何奇怪的分布情况,情况就不妙了。比如,对于Tanh的神经元,我们应该看到激活数据的值在整个[-1,1]区间中都有分布。如果看到神经元的输出全部是0,或接近-1和1,那肯定有问题。

首层可视化

  如果数据是图像像素数据,那么把第一层特征可视化会有帮助:

这里写图片描述
这里写图片描述

  左图中的特征充满了噪音,这暗示了网络可能出现了问题:网络没有收敛,学习率设置不恰当,正则化惩罚的权重过低。右图的特征不错,平滑,干净而且种类繁多,说明训练过程进行良好。


参数更新

梯度下降

  最简单的更新形式是沿着负梯度方向改变参数,有BGD、SGD、Mini-batch SGD三种,第三种使用最多。

Batch gradient descent

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

\theta=\theta-\eta\cdot\nabla_\theta J(\theta)   每次基于整个数据计算梯度进行更新。

代码语言:javascript
复制
for i in range(nb_epochs):  
  params_grad = evaluate_gradient(loss_function, data, params)  
  params = params - learning_rate * params_grad  
SGD(Stochastic Gradient Descent )随机梯度下降

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

\theta=\theta-\eta\cdot\nabla_\theta J(\theta;x^{(i)};y^{(i)})   每次基于一个数据样本计算梯度。

代码语言:javascript
复制
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  
Mini-batch Gradient Descent

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

\theta=\theta-\eta\cdot\nabla_\theta J(\theta;x^{(i:i+n)};y^{(i:i+n)})   每次基于n个数据样本计算梯度。

代码语言:javascript
复制
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)使用先进的Deep Learning库,可以高效地计算mini-batch的梯度

   注:n一般取[50,256]范围内的数,视具体应用而定。

梯度下降算法面临的挑战

   1)选择合适的Learning Rate很困难,太小导致收敛慢,太大阻碍收敛且导致损失函数在最小值附近波动或发散;    2)预先定义的Learning Rate变动规则不能适应数据集的特性;    3)同样的Learning Rate运用到所有的参数更新(后面的AdaGrad, AdaDelta, RMSProp, Adam为解决此问题而生);    4)最小化高度非凸损失函数要解决问题:避免陷入众多的局部最优值。

动量(Momentum)更新

vt=μvt−1+η∇θJ(θ)

v_t=\mu v_{t-1}+\eta\nabla_\theta J(\theta)

θ=θ−vt

\theta=\theta-v_t   动量的物理解释是:当我们把球推下山时,球不断地累积其动量,速度越来越快(直到其最大速度,如果有空气阻力,如μ<1\mu<1),同样的事情发生在参数更新中:梯度保持相同方向的维度的动量不停地增加,梯度方向不停变化的维度的动量不停地减少,因此有更快的收敛速度并减少振荡。   损失值可以理解为是山的高度(高度势能U=mghU=mgh,所以U∝hU\propto h)。随机初始化参数相当于在某个位置给质点设定初始速度为0。这样最优化过程可看做模拟参数向量(即质点)在地形上滚动的过程。

  因为作用于质点的力与梯度的潜在能量(F=−∇UF=-\nabla U)有关,质点所受的力就是损失函数的(负)梯度。因为F=maF=ma,所以(负)梯度与质点的加速度是成比例的。

  注意这里和随机梯度下降(SGD)不同,梯度下降中,梯度直接影响位置。而动量更新中,梯度只是影响速度,然后速度再影响位置:(公式中加减是上升或下降的问题) 

代码语言:javascript
复制
# 动量更新
v = mu * v - learning_rate * dx # 与速度融合
x += v # 与位置融合

  在这里引入了一个初始化为0的变量v和一个超参数mu。这个变量(mu)在最优化的过程中被看做动量(一般设为0.9),但其物理意义与摩擦系数更一致。这个变量有效地抑制了速度,降低了系统的动能,不然质点在山底永远不会停下来。

  通过交叉验证,这个参数通常设为[0.5,0.9,0.95,0.99][0.5,0.9,0.95,0.99]中的一个。和学习率随着时间退火类似,动量随时间变化的设置有时能略微改善最优化的效果,其中动量在学习过程的后阶段会上升。一个典型的设置是刚开始将动量设为0.5而在后面的多个周期(epoch)中慢慢提升到0.99。

通过动量更新,参数向量会在任何有持续梯度的方向上增加速度。 优点: 利用物体运动时的惯性,加快到达全局最优点的速度,且减少振荡。 缺点:球盲目地沿着斜坡向山下滚。

   当损失函数表面曲线的一维比其它维有更多的沟壑时,SGD要跨越此沟壑是困难的,如上图左边所示,SGD沿着沟壑的斜坡振荡,然后犹犹豫豫地向局部最优点前进。

   动量它模拟的是物体运动时的惯性,更新时在一定程度上保留之前更新的方向,同时利用当前batch的梯度微调最终的更新方向。这样一来,可以在一定程度上增加稳定性,从而学习地更快,并且还有一定摆脱局部最优的能力

NAG更新(Nesterov Accelerated Gradient)

vt=μvt−1+η∇θJ(θ−μvt−1)

v_t=\mu v_{t-1}+\eta\nabla_\theta J(\theta-\mu v_{t-1})

θ=θ−vt

\theta=\theta-v_t Nesterov动量比较流行。在理论上对于凸函数它能得到更好的收敛,在实践中也比标准动量表现更好一些。

优点:一个聪明的球,知道它将到哪儿去,且知道在斜坡向上之前减速。沿着当前方向,先走一步,然后再看向哪个方向走最快,这样对前方的情况就有了更多地了解,可以做出明智的决策。

Momentum:

  • 计算当前的梯度(上图中:比较小的蓝色向量)
  • 沿着更新的累积的梯度方向进行一大跳(上图中:比较大的蓝色向量)

NAG:

  • 沿着以前累积的梯度方向进行一大跳 (上图中:棕色向量)
  • 在新的位置测量梯度,然后进行校正(上图中:绿色向量)
  • 这个有预料的更新可以防止走的太快并导致增加的响应

关键区别:计算梯度的位置不一样

  Nesterov动量核心思路是,当参数向量位于某个位置x时,观察上面的动量更新公式可以发现,动量部分(忽视带梯度的第二个部分)会通过mu * v稍微改变参数向量。因此,如果要计算梯度,那么可以将未来的近似位置x + mu * v看做是“向前看”,这个点在我们一会儿要停止的位置附近。因此,计算x + mu * v的梯度而不是“旧”位置x的梯度就有意义了。   也就是说,添加一些注释后,实现代码如下:

代码语言:javascript
复制
x_ahead = x + mu * v
# 计算dx_ahead(在x_ahead处的梯度,而不是在x处的梯度)
v = mu * v - learning_rate * dx_ahead
x += v

  实践中,人们更喜欢和普通SGD或上面的动量方法一样简单的表达式。通过对x_ahead = x + mu * v使用变量变换进行改写是可以做到的,然后用x_ahead而不是x来表示上面的更新。也就是说,实际存储的参数向量总是向前一步的那个版本。x_ahead的公式(将其重新命名为x)就变成了:

代码语言:javascript
复制
v_prev = v # 存储备份
v = mu * v - learning_rate * dx # 速度更新保持不变
x += -mu * v_prev + (1 + mu) * v # 位置更新变了形式

  对于NAG(Nesterov’s Accelerated Momentum)的来源和数学公式推导,可以看以下材料:


参考资料

链接:http://cs231n.github.io/neural-networks-3/ 链接:https://zhuanlan.zhihu.com/p/21741716?refer=intelligentunit https://zhuanlan.zhihu.com/p/21798784?refer=intelligentunit 链接:http://blog.csdn.net/han_xiaoyang/article/details/50521064 链接:http://blog.csdn.net/myarrow/article/details/51848285

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年12月01日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 梯度检查
  • 学习前检查的提示与技巧
  • 学习中对参数进行跟踪
    • 跟踪损失函数
      • 跟踪训练集和验证集正确率
        • 跟踪权重更新比例
          • 跟踪每层激活数据及梯度分布
            • 首层可视化
            • 参数更新
              • 梯度下降
                • Batch gradient descent
                • SGD(Stochastic Gradient Descent )随机梯度下降
                • Mini-batch Gradient Descent
                • 梯度下降算法面临的挑战
              • 动量(Momentum)更新
                • NAG更新(Nesterov Accelerated Gradient)
                • 参考资料
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档