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

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

梯度检查

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

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}左右。如果更低,说明学习率可能太小;如果更高,说明学习率可能太高。下面是具体例子:

# 假设参数向量为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)   每次基于整个数据计算梯度进行更新。

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)})   每次基于一个数据样本计算梯度。

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个数据样本计算梯度。

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)不同,梯度下降中,梯度直接影响位置。而动量更新中,梯度只是影响速度,然后速度再影响位置:(公式中加减是上升或下降的问题) 

# 动量更新
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的梯度就有意义了。   也就是说,添加一些注释后,实现代码如下:

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)就变成了:

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏人工智能

机器学习实战之朴素贝叶斯

机器学习实战之朴素贝叶斯 1.1、简介 贝叶斯分类是一类分类算法的总称,这类算法均以贝叶斯定理为基础,故统称为贝叶斯分类。已知某条件概率,如何得到两个事件交换后...

1937
来自专栏人工智能LeadAI

最终章 | TensorFlow战Kaggle“手写识别达成99%准确率

这是一个TensorFlow的系列文章,本文是第三篇,在这个系列中,你讲了解到机器学习的一些基本概念、TensorFlow的使用,并能实际完成手写数字识别、图像...

4039
来自专栏算法channel

机器学习之线性回归:算法兑现为python代码

? 前面三天推送机器学习线性回归算法之最小二乘法,从假设到原理,详细分析了直接求解和梯度下降两种算法,接下来手动编写python代码实现线性回归的算法吧。 0...

3909
来自专栏人工智能LeadAI

深度学习优化器总结

每次更新我们需要计算整个数据集的梯度,因此使用批量梯度下降进行优化时,计算速度很慢,而且对于不适合内存计算的数据将会非常棘手。批量梯度下降算法不允许我们实时更新...

1403
来自专栏梦里茶室

TensorFlow 深度学习笔记 Stochastic Optimization

Stochastic Optimization 转载请注明作者:梦里风林 Github工程地址:https://github.com/ahangchen/G...

20810
来自专栏决胜机器学习

深层神经网络参数调优(四) ——adam算法、α衰减与局部最优

深层神经网络参数调优(四)——adam算法、α衰减与局部最优 (原创内容,转载请注明来源,谢谢) 一、概述 本文主要讲解另外一种思想的梯度下降——adam,并...

5216
来自专栏GAN&CV

全面解读Group Normbalization-(吴育昕-何凯明)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_25737169/article/d...

1083
来自专栏计算机视觉战队

深度学习超参数简单理解

说到这些参数就会想到Stochastic Gradient Descent (SGD)!其实这些参数在caffe.proto中 对caffe网络中出现的各项参数...

3679
来自专栏PPV课数据科学社区

数据咖小课堂:R语言十八讲--(补充)处理缺失值

? 缺失值处理在数据分析中是关键的一步,而且是开始的关键一步,我们对于数据的缺失处理直接影响模型的准确性. 1.产生的原因: 调查者忘记回答了,拒绝回答,不完...

3098
来自专栏本立2道生

论文学习-系统评估卷积神经网络各项超参数设计的影响-Systematic evaluation of CNN advances on the ImageNet

论文状态:Published in CVIU Volume 161 Issue C, August 2017 论文地址:https://arxiv.org/a...

712

扫码关注云+社区

领取腾讯云代金券