[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记

Lecture 5 Training Neural Networks,Part I

神经网络的历史

这次的历史就直接讲神经网络的历史(和第一节不同),从第一台能够实现感知器算法实现的Mark I 感知机开始,简单图像处理初次问世;到了1960年,Widrow与Hoff发明的霍夫机问世,这是人类初次使用神经网络架构;再到1986年,Rumelhart等人首次发明了反向传播算法,并使之流行起来;接着一大段的空白期,到了2006年,重新使神经网络乃至深度学习复兴的算法出现;接下来的首次重大的成果在2010年与2012年问世,分别是使用深度信念网络的声学建模与用于大词汇量语音识别的上下文相关预训练深度神经网络,还有AlexNet网络的问世,让深度学习展现了极强的生命力。

训练神经网络

**函数

其实**函数的作用就是让你的网络有更好的分类效果,而取消**函数,那么网络就是一个普通的线性分类器了。

  1. Sigmoid**函数σ(x)=1(1+ex)\displaystyle\sigma(x)=\frac{1}{(1+e^{-x})}
    [深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
    在历史上,sigmoid函数非常常用,这是因为它对于神经元的**频率有良好的解释:从完全不**(0)到在求和后的最大频率处的完全饱和(saturated)的**(1)。然而现在Sigmoid函数已经不太受欢迎,实际很少使用了,这是因为它有两个主要缺点:
    1. Sigmoid函数饱和使梯度消失。sigmoid神经元有一个不好的特性,就是当神经元的**在接近0或1处时会饱和:在这些区域,梯度几乎为0。回忆一下,在反向传播的时候,这个(局部)梯度将会与整个损失函数关于该门单元输出的梯度相乘。因此,如果局部梯度非常小,那么相乘的结果也会接近零,这会有效地“杀死”梯度,几乎就有没有信号通过神经元传到权重再到数据了。还有,为了防止饱和,必须对于权重矩阵初始化特别留意。比如,如果初始化权重过大,那么大多数神经元将会饱和,导致网络就几乎不学习了。
    2. Sigmoid函数的输出不是零中心的。这个性质并不是我们想要的,因为在神经网络后面层中的神经元得到的数据将不是零中心的。这一情况将影响梯度下降的运作,因为如果输入神经元的数据总是正数(比如在f=wTx+bf=w^Tx+b中每个元素都x>0x>0),那么关于w的梯度在反向传播的过程中,将会要么全部是正数,要么全部是负数(具体依整个表达式f而定)。这将会导致梯度下降权重更新时出现z字型的下降。如果数据量是关于原点对称的话,可以看到整个批量的数据的梯度被加起来后,对于权重的最终更新将会有不同的正负,这样就从一定程度上减轻了这个问题。因此,该问题相对于上面的神经元饱和问题来说只是个小麻烦,没有那么严重。
      [深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
    3. 最后一点是expexp在计算时是非常耗时的,影响计算时间。
  2. tanh**函数tanh(x)tanh(x)
    [深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
    可以看到tanh也存在梯度消失问题,但是它是以0为中心的,因此,在实际操作中,tanh非线性函数比Sigmoid非线性函数更受欢迎。注意tanh神经元是一个简单放大的sigmoid神经元,具体说来就是:tanh(x)=2σ(2x)1tanh(x)=2\sigma(2x)-1
  3. ReLU**函数max(0,x)max(0,x)
    [深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
    这个函数的意义就是将小于等于0的数输出0,而大于0时输出原数,使用ReLU有以下一些优缺点:
    1. 优点:相较于sigmoid和tanh函数,ReLU对于随机梯度下降的收敛有巨大的加速作用( Krizhevsky 等的论文指出有6倍之多)。据称这是由它的线性,非饱和的公式导致的。
    2. 优点:sigmoid和tanh神经元含有指数运算等耗费计算资源的操作,而ReLU可以简单地通过对一个矩阵进行阈值计算得到。
    3. 缺点:在训练的时候,ReLU单元比较脆弱并且可能“死掉”。举例来说,当一个很大的梯度流过ReLU的神经元的时候,很有可能这个梯度将原本的正值变化到负值,导致**函数导数为0,在这种状态下神经元将无法被其他任何数据点再次**,导致相应参数永远不会被更新。产生这种现象还有两个原因:参数的初始化问题,学习率太高也会导致在训练过程中参数更新太大(与梯度太大相似)。 解决方法:采用Xavier初始化方法,以及避免将学习率设置太大或使用adagrad等自动调节学习率的算法。
      [深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
  4. Leaky ReLU**函数max(0.01x,x)max(0.01x,x)
    [深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
    Leaky ReLU是为解决“ReLU死亡”问题的尝试。ReLU中当x<0时,函数值为0。而Leaky ReLU则是给出一个很小的负数梯度值,比如0.01。有些研究者的论文指出这个**函数表现很不错,但是其效果并不是很稳定。Kaiming He等人在2015年发布的论文Delving Deep into Rectifiers中介绍了一种新方法PReLU,把负区间上的斜率当做每个神经元中的一个参数f(x)=max(αx,x)f(x) = max(\alpha{x}, x)。然而该**函数在在不同任务中均有益处的一致性并没有特别清晰。
  5. ELU**函数f(x)={xx&gt;0α(ex1)x0 f(x)=\left\{ \begin{aligned} &amp;x &amp;&amp;x &gt; 0 \\ &amp;\alpha(e^x - 1) &amp;&amp; x \leq 0 \end{aligned} \right.
    [深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
    这个**函数的好处是它在负值的位置处的输出均值为0,并且与Leaky ReLU相比,它在负饱和状态增加了一些噪声的稳健性,缺点是,在负值处需要计算exe^x,计算会很复杂。
  6. Maxout**函数max(w1Tx+b1,w2Tx+b2)max(w^T_1x+b_1,w^T_2x+b_2)
    ReLU和Leaky ReLU都是这个公式的特殊情况(比如ReLU就是当w1,b1=0w_1,b_1=0的时候)。这样Maxout神经元就拥有ReLU单元的所有优点(线性操作和不饱和),而没有它的缺点(死亡的ReLU单元)。然而和ReLU对比,它每个神经元的参数数量增加了一倍,这就导致整体参数的数量激增。

可以看一下课程的**函数使用总结:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记

数据预处理

关于数据预处理我们有3个常用的符号,数据矩阵X,假设其尺寸是[N x D](N是数据样本的数量,D是数据的维度)。
均值减法(Mean subtraction)是预处理最常用的形式。它对数据中每个独立特征减去平均值,从几何上可以理解为在每个维度上都将数据云的中心都迁移到原点。在numpy中,该操作可以通过代码X -= np.mean(X, axis=0)实现。而对于图像,更常用的是对所有像素都减去一个值,可以用X -= np.mean(X)实现,也可以在3个颜色通道上分别操作。
归一化(Normalization)是指将数据的所有维度都归一化,使其数值范围都近似相等。有两种常用方法可以实现归一化。第一种是先对数据做零中心化(zero-centered)处理,然后每个维度都除以其标准差,实现代码为X /= np.std(X, axis=0)。第二种方法是对每个维度都做归一化,使得每个维度的最大和最小值是1和-1。这个预处理操作只有在确信不同的输入特征有不同的数值范围(或计量单位)时才有意义,但要注意预处理操作的重要性几乎等同于学习算法本身。在图像处理中,由于像素的数值范围几乎是一致的(都在0-255之间),所以进行这个额外的预处理步骤一般不进行。
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记

权重初始化

上面已经讲了如何构建一个神经网络的结构并对数据进行预处理,但是在开始训练网络之前,还需要初始化网络的参数。
全0初始化:错误,让我们从应该避免的错误开始。在训练完毕后,虽然不知道网络中每个权重的最终值应该是多少,但如果数据经过了恰当的归一化的话,就可以假设所有权重数值中大约一半为正数,一半为负数。这样,一个听起来蛮合理的想法就是把这些权重的初始值都设为0吧,因为在期望上来说0是最合理的猜测。这个做法错误的!因为如果网络中的每个神经元都计算出同样的输出,然后它们就会在反向传播中计算出同样的梯度,从而进行同样的参数更新。换句话说,如果权重被初始化为同样的值,神经元之间就失去了不对称性的源头。
小随机数初始化:因此,权重初始值要非常接近0又不能等于0。解决方法就是将权重初始化为很小的数值,以此来打破对称性。其思路是:如果神经元刚开始的时候是随机且不相等的,那么它们将计算出不同的更新,并将自身变成整个网络的不同部分。小随机数权重初始化的实现方法是:W=0.01np.random.randn(D,H)W = 0.01 * np.random.randn(D,H)。其中randn函数是基于零均值和标准差为1的一个高斯分布来生成随机数的。根据这个式子,每个神经元的权重向量都被初始化为一个随机向量,而这些随机向量又服从一个多变量高斯分布,这样在输入空间中,所有的神经元的指向是随机的。也可以使用均匀分布生成的随机数,但是从实践结果来看,对于算法的结果影响极小。
如果说数据量较小还好,但是一旦数据量较多,那么虽然输入层与前几层权重表现很好,但是在后几层由于反向传播需要乘上WW,而这个WW又很小,所以导致后面几层的输出均值与方差都趋于0,且**函数使用tanh函数:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
大随机数初始化:既然不能使用小的数,那么直接使用均值为0,方差为1的高斯分布矩阵可以吗?即W=1np.random.randn(D,H)W = 1 * np.random.randn(D,H),在下图可以看出,当数值较大时,会使**函数趋于1与-1的饱和区域,导致梯度消失,使得神经元不工作,**函数使用tanh函数:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
使用1/sqrt(n)校准方差:既然权重不能太大也不能太小,那么怎样去选择呢?我们可以除以输入数据量的平方根来调整其数值范围,这样神经元输出的方差就归一化到1了。也就是说,建议将神经元的权重向量初始化为:W=np.random.randn(n)/sqrt(n)W = np.random.randn(n) / sqrt(n)。其中n是输入数据的数量。这样就保证了网络中所有神经元起始时有近似同样的输出分布。下面看一下具体的推导过程:
Var(s)=Var(inwixi)Var(s)=Var(\sum^n_iw_ix_i)
=inVar(wixi)=\sum^n_iVar(w_ix_i)
=in[E(wi)]2Var(xi)+E[(xi)]2Var(wi)+Var(xIi)Var(wi)=\sum^n_i[E(w_i)]^2Var(x_i)+E[(x_i)]^2Var(w_i)+Var(xIi)Var(w_i)
=inVar(xi)Var(wi)=\sum^n_iVar(x_i)Var(w_i)
=(nVar(w))Var(x)=(nVar(w))Var(x)
在前两步,使用了方差的性质。在第三步,因为假设输入和权重的平均值都是0,所以E[xi]=E[wi]=0E[x_i]=E[w_i]=0。注意这并不是一般化情况,比如在ReLU单元中均值就为正。在最后一步,我们假设所有的wi,xiw_i,x_i都服从同样的分布。从这个推导过程我们可以看见,如果想要ss有和输入xx一样的方差,那么在初始化的时候必须保证每个权重ww的方差是1/n1/n。又因为对于一个随机变量XX和标量aa,有Var(aX)=a2Var(X)Var(aX)=a^2Var(X),这就说明可以基于一个标准高斯分布,然后乘以a=1/na=\sqrt{1/n},使其方差为1/n1/n,于是得出:w=np.random.randn(n)/sqrt(n)w = np.random.randn(n) / sqrt(n)
实践经验证明,这样做可以提高收敛的速度,**函数使用tanh函数:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
如果**函数使用ReLU呢?如下图所示,层数越深,就有越多的神经元失活,所以这个方法对ReLU**函数效果很差:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
其实目前最多的是使用ReLU**函数,并且使用w=np.random.randn(n)sqrt(2.0/n)w = np.random.randn(n) * sqrt(2.0/n)来进行权重初始化,关于这一点,具体的意义在于使用ReLU**函数的话,会让高斯分布的权重减半,而乘以二倍则是将这种情况考虑进去:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记

批量归一化

批量归一化(Batch Normalization)减轻了如何合理初始化神经网络这个棘手问题带来的头痛,其做法是让**数据在训练开始前通过一个网络,网络处理数据使其服从标准高斯分布。因为归一化是一个简单可求导的操作,所以上述思路是可行的。在实现层面,应用这个技巧通常意味着全连接层或卷积层与**函数之间添加一个BN层,为每个维度独立计算经验均值和方差。在实践中,使用了批量归一化的网络对于不好的初始值有更强的鲁棒性。
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
接下来是BN层的另一部份,可以不仅仅使用归一化操作,还可以让每一列的数据进行平移操作,调整进入**函数的特征,还有更有意义的是当γ(k)=Var[x(k)]\gamma^{(k)} = \sqrt{Var[x^{(k)}]}β(k)=E[x(k)]\beta^{(k)} = E[x^{(k)}]时,BN层的作用就会被抵消掉,这样在之后的反向传播过程中,就能更好地进行学习γ(k)\gamma^{(k)}β(k)\beta^{(k)},来确定是否需要批量归一化。
说一说它的优缺点:

  1. 它可以改善通过网络的梯度流。
  2. 支持更高的学习率。
  3. 减少对初始化的强烈依赖,不用顾虑初始值设置的头痛。
  4. 作为一种有意义的归一化形式,可能会略微减少dropout的需要。
  5. 其实加入BN层会略微较少训练时间,最多达到30%。
    [深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
    最后总结一下:批量归一化可以理解为在网络的每一层之前都做预处理,只是这种操作以另一种方式与网络集成在了一起。

检查学习过程

以上是我们将所有训练之前需要了解的都说过了,下面就是直接进入训练过程,训练过程中为了检查我的模型是否正确,可以先进行先验检查,例如,我要做一个简单的二层神经网络,输出为10类,损失函数选择Softmax,进行过数据预处理与简单的权重初始化后(用小权重初始化方法,因为层数很少),先不加入正则化项;因为输出为10类,那么预计损失则为log(110)=2.30-log(\frac{1}{10}) = 2.30,观察网络输出:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
可以看到输出就是我想要的,成功!
当我逐渐加入正则化项时,应该预估到损失值会上升:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
prefect!这样我的网络模型应该是没有问题的,接下来应该测试我的模型会不会过饱和,以查看反向传播部分是否完好,这个测试应该只使用一小部分数据,那么这里使用20个数据,不使用正则项,优化器选择SGD,查看效果:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
终极香蕉牛奶鱿鱼蛇皮完美,接下来可以尝试小学习率与加入正则化项,来查看我们的最优学习率在什么位置:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
这样我们的模型就训练好了!

超参数优化

超参数优化意义上就是寻找最好的超参数:正则化项与学习速率,那么我们可以进行随即搜索的方式在区间内寻找最优超参数:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
上图中通过小样本,运行5epochs结果展示,可以看出最好的几个准确率的学习速率与正则化项取值可以规范到一定的区间内,第二步就是尽可能再缩小选值区间:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
这样就能选择到尽可能最好的超参数,但是看到上图中最好的准确率结果所选择的学习率与第一次的最好值很相近,所以接下来可以将学习率放到10210310^{-2}—10{-3}区间内寻找。
其实我们在训练时,会出现很奇怪的损失函数曲线,也是比较让我们头痛的,就应该不断地调参调参,以达到我们的最优模型,下面是一类损失函数曲线,这个曲线在最初一直是不变的,到了一定时间才会下降,原因就在于我的权重初始值可能选择的太小了,导致损失函数变化得很慢:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
还有一点就是我们一般观察的是准确率随着Epoch的变化,有时会出现下图的情况,就应该知道可能是出现了过拟合的状态,应该加大正则化项:
[深度学习基础] 斯坦福CS231n李飞飞计算机视觉Lecture 5笔记
所以我们要尽量能够调到参数的最优值,以达到最完美的模型!

小结

  1. 从训练神经网络的第一步开始,了解每个**函数的优缺点,选择最好的**函数。
  2. 对数据进行预处理,让数据最好以原点为中心。
  3. 进行权重初始化,让权重最好是以0为均值,1为标准差的高斯分布,这样让数据的结果也以0为中心,接下来引入BN层,并讨论BN层的优点,最好使用BN层进行训练。
  4. 在真正进行训练时,先通过小数据量查看模型是否完整,再进行超参数优化,以达到最优的模型。

资料来源:

  1. 斯坦福CS231n李飞飞计算机视觉视频课程:https://study.163.com/course/courseMain.htm?courseId=1003223001
  2. CS231n官方笔记授权翻译总集篇:https://zhuanlan.zhihu.com/p/21930884
  3. CS231n官方PPT:http://vision.stanford.edu/teaching/cs231n/syllabus.html