CS231N-Lecture5 Training Neural Network

一、概述

这一集讲了选择合适的activation function

预处理数据。

Weight的初始化。

Batch Normalization的运用。

如何监督整个学习过程以便提早发现问题。

以及如何优化hyper parameter

二、Neural Networks

讲激活函数之前讲了很多关于神经网络的历史。体会最深的就是,一个事物的兴起,不是自身力量就足够的。如果没有摩尔定律,没有大数据,神经网络的发展还不会被提上时代的议程。

激活函数,用来在线性的输入之后产生非线性的输出。非线性代表着解决更加复杂问题的能力。虽然说线性也能解决高复杂度问题,比如素描中画足够多的直线是可以在中间截出一个圆。但是远不如用非线性函数来得快和准确,并且其复杂性也让实践变得很难。

下图是一些激活函数。

  1. Sigmoid sadf 历史上,使用最多的是Sigmoid。它接收一个实数,然后产生一个[0, 1]的输出。 历史上使用最多,因为Sigmoid function可以用来表示一个neuron是否被激活这样的的概念。 但是Sigmoid有三个问题: 这里写图片描述 第一个问题,饱和的neuron会杀死backpropagation过程中的gradient。这个问题被称作vanishing gradient problem这里写图片描述 上图中,解释了vanishing gradient problem。 当x = 10x = -10的时候,就是一个sigmoid就产生了一个saturated neuron(饱和神经元)。这个neuron的值接近0,或者接近1。如上图,在x = 10x = -10的地方,y基本上是01Sigmoid function的导数是sigmoid * (1 - sigmoid)。 想象一下,sigmoid无限接近于1的时候,dsigmoid = ->1 * (1 - ->1) = -> 0(用->表示极限了…)。 另外,sigmoid无限接近于0的时候,dsigmoid = ->0 * (1 - ->0) = -> 0。 两种情况,在链式法则下,所有的数乘以0都等于0,因此gradient就被杀死了。之后的gradient都等于0。 第二个问题,Sigmoid function的输出不是趋向于0的(zero-centered)。 这个翻译可能不是很正确,原文写出来了zero-centered,结合解释体会一下。 这里写图片描述 上图中,如果使用Sigmoid,当input x总是正的时候,wgradients会是怎样的? 答案是,wgradients要么都是正的,要么都是负的。 这里写图片描述 上图中,如果x都是正数,那么sigmoid的输出y[0.5,1]。 那么dsigmoid = [0.5, 1] * (1 - [0.5, 1]) ~= [0, 0.5]总是> 0的。 假设最后整个神经网络的输出是正数,最后wgradient就是正数;如果输出是负数,最后wgradient就是负数。如下图。 这里写图片描述 这样的gradients造成的问题就是,如上图中,你的w要么全是正的(x是正的),要么全是负的(x是负的)。 假设最优的一个w向量应该是在上图中第四象限,那么要将你的w优化到最优状态,就必须走这样的之字形(zig-zag),因为你的w要么只能往下走(负数),要么只能往右走(正的)。达成优化的效率十分低下,模型拟合的过程就会十分缓慢。 如果要深究的话,查找有关费雪矩阵(Fisher Matrices )以及自然梯度(Natural Gradient)的相关论文,证明过程是挺复杂的。 因此,在inputactivation function的处理中,最好都追求0趋向的数据。好的数据预处理和选用属性更佳的激活函数,就可以达到这样的效果。(貌似Sigmoid不好的地方就在于它的求导函数让gradient的值很蛋疼)。 第三个问题,exp()函数是消耗计算资源的。 虽然说Convnet中大部分的计算资源都将被大量的convolution占用,但相比其他activation functionexp()都是不小的一笔计算开支。
  2. 选择其他Ativation Function 1) tanh 这里写图片描述 `tanh`解决了`sigmoid`的第二个`数据非0趋向`的问题。 到这里,还是理解不了这个zero-centered这个问题。 我猜是不是因为sigmoid的区间只在[0,1],那么输出的分布是不均匀的,输出都是大于等于0的数,所以称为non zero-centered。 而像tanh,输出区间分布在[-1, 1],分布能比较均匀,因此是zero-centerred。 但是,reLU呢?区间是[0,x]啊… 然后google了一下,大家都在说zero-centered的作用,但是没有解释zero-center的意思…看了一些文章之后大概有了一点感觉。 点这里看Quora这位大哥对于zero-center的举例 看了这个之后,我觉得zero-center应该是activation function应该有[0,0]这个值。 sigmoid,是没有[0,0]点的,因此就算你的dataset预处理得离0normalized)有多好,但是sigmoid还是不能给你一个良好的输出(这个输出永远大于0)。 然后结合Quora中那位大哥对于perceptron的解释,觉得对于神经网络做出决定来说,确切的0和确切的非0是很重要的。中间的其他输出值都是杂项,是噪音,会干扰最终的决定,也会阻碍拟合的进程(噪音越严重,w的优化过程就越缓慢)。 这样想来,tanh也不行。下面还有负数的输出,同时也没有解决vanishing gradient的问题。 ![](https://imgconvert.csdnimg.cn/aHR0cDovL2ltZy5ibG9nLmNzZG4ubmV0LzIwMTcwNTE5MTQ0NjM5NTA3?x-oss-process=image/format,png) 如上图,`tanh`的输出区间是`[-1, 1]`。虽然比`sigmoid`好一些,但是在`饱和神经元`的情况下,还是没有解决`vanishing gradient`的问题。 2) ReLu 12年的时候Krizhevsky提出了使用max(0,x)这样的函数让神经网络的拟合更加迅速。 ReLu解决了vanishing gradient的问题,至少在正区间内,神经元不会饱和了。 这里有疑问,x无限接近于0的时候,不就饱和了么… 那backprop的时候,gradient还是接近0的…再体会一下。 同时,解决了浪费计算资源的问题,ReLu只是做一个门槛比较(threshholding),计算效率高。 最后,使用ReLu让神经网络拟合更快。 但是看到这里懵了,说ReLu不是zero-centered 刚才有google了一些文章,讲relu的,看到了relu容易die的缺点,差不多就是一旦learning_rate没有设置好,导致第一次update w的时候让input到了negative regime(负数那边),那么这个relu节点就死亡了,再也不会被激活(因为relu的导数drelu = 1 if x > 0 else 0,如果流过去的input0,流回来的gradient就是0w不会被更新),导致w不会被更新,也导致这个neuron不再学习了。如下图。 但是关于这个zero-centered,课后再查资料吧…估计zero-centered就是中心点要在[0,0]上… 3) Leaky Relu 为了解决relu neuron will die的问题,leaky relu把负数区域的斜率调整了一些。如图。 这里提到了这个0.01可以是任意数值,因此有了Prelu,将这个x之前的系数设置成变量alpha,这个变量同样可以通过backprop来学习,不断进行调整。 4) ELU 这个activation functionrelu所有的有点,并且输出的均值是接近0的。 但是可以看到,计算的时候是需要exp()的,产生了效率问题。 5) Maxout "Neuron" 有优点但是并不常见,如果使用这个激活函数,意味着每个neuron将有两组w,感觉增加了神经网络的负担。
  3. 小结 使用relu,但是小心选择learning_rate。 尝试其他的函数,但是都不常用。 不要使用sigmoid

三、Data Preprocessing

在预处理dataset时,可以看到下图中的zero-centerednormalized data

另外,在实践中,也有看到下面PCA主成分析和Whitening的。

不过,在针对图片的神经网络中,不太会用到PCA或者Whitening,或者是Normalization,常用的就是centered

根据视频所说,per-channel mean的方式更加常用。因为只需要考虑颜色通道的3个数值,而不用考虑图片整体的像素矩阵

四、Weight Initialization

问题:为什么不能将w初始化成0?

所有的neuron的输出都将是一样的,backprop时的gradient也将是一样的,不利于学习。

关于W的初始化,有下面几种方式的演进。

  1. 小随机数 这种方式适用在规模较小的神经网络中,但同时也很容易造成激活之后输出结果的不均匀分布。 下面会用图示来说明这个问题。 为了验证输出结果分布不均的问题,先创建了一个10层的每层有500neuron的神经网络;使用tanh作为非线性激活函数;W的初始化就用上图中的公式。代码如图。 下面要做的就是,要统计一下上面这个网络每一层在tanh activation之后的输出,并且看一下每层的输出的平均数和标准方差。画图如下。 通过上图可以看到,刚开始的时候,每一层的输出的平均值是一直是0;再来看std,从1开始,一直下降,然后突然就栽到一直保持0的状态。 然后看下直方图,第一层还挺正常的,数据还能均匀分布在[-1,1]的区间内。之后就迅速收窄,所有的tanh输出都是接近0了。 只要tanh的输出接近0,那么在backprop的时候,gradient几乎也是0,因此不仅W得不到良好的更新调整,整个网络就像是在一个死循环一样。 这也是vanishing gradient problem的表现。 那么,如果将0.01调整成1.0会如何呢?调整如下图。 可以看到,这是另一个极端。看直方图,几乎所有的neuron都饱和了,意味着tanh的输出要么是1,要么是-1。 在backprop的时候,所有的gradient都是0,那么什么都得不到更新,网络也不会进行任何学习。 Weight Initialization的选择要十分小心。在上述情况下,那么W的值应该介于10.01之间。可以不断尝试在这中间找一个合适的值。 看一下学者对初始化的见解。 下图是10年提出的Xavier Initialization 用图中的公式初始化,可看到直方图的分布是很均匀的,没有过于饱和的地方,网络也能在合理的backprop中良好学习。 虽然看上图的std还是会降到0。Andrej解释是因为这个学术论文是没有考虑tanh作为非线性函数的。即便如此,这样的下降看起来不像之前那样迅速,神经网络也能正常进行学习。 再继续,如果将tanh换成relu,应该好似用下面的初始化方式。如图。
  2. Batch Normalization 之前所强调的,就是非线性之后的一个正态分布是很重要的。 下面的方法,就是怎么得到一个正态分布的非线性输出。 学者提出,既然那么想要正态分布,那就做一个正态分布即可。 上图中,batch norm的公式将对输入的每一个维度作normalization。 这样的操作之后,所有的输出的数值就能成一个正态分布了。 通常,会将batch normalization layers插入到网络中,确保每一个input x的每一个特征维度,都能产生一个unit gaussian activation。如图。 问题:需要给tanh函数传递unit gaussian的输入吗?本来,在没有高斯分布的时候,tanh层的输入可以是任意可以调整的(backprop回来的gradient调整了w,下次进入tanh的输入和上次会有不同,并且分布是相对随机的,因此神经网络可以在这种随机分布情况下,通过更新w来控制tanh的输出的区间);但是一旦在tanh前加了一层BN(Batch Normalization)层,那么进入tanh的输入总是高斯分布的,网络本身就没有任何办法调整tanh的输出区间了。 为了解决这个问题,就用到了下图中的方法。 在使用BN之后,网络可以选择对x增加一个系数γ,并可以选择增加一个量β。保证输出的值在横向和竖向都可以动态调整。 在使用了这个方式之后,网络就能在BN之后,对tanh的输入进行控制,如果需要的话。它可以选择缩放这个输入,或者上移,下移。 值得注意的是,公式中这个γβ是可以通过bakprop来学习的。因此,有了这两个变量之后,BN就可以变成一个恒等函数(identity function),意思就是可有可无了,但是是由网络自身来控制。 一旦神经网络学会了γβ,它可以选择将BN的输出还原回BN的输入,就像BN根本不存在一样。 网络可以通过学习,自己决定是否使用BN。如果使用BN是有好处的,那就保留,反之,则通过γβ重置BN的变换。 整个BN的过程如下。 Batch Normalization的好处有如下几点: 它能优化流经网络的gradient,让其不会vanish消失掉,也不会直接爆炸。它能让我们设置更高的learning_rate,网络能在更短的时间内完成训练。它让我们不再那么依赖Weight Initialization。这是最重要的一点。它的作用像是之前提到过的regularization。可以减少之后要用到的dropout的量。可以这样想,Andrej提到了BN的作用就是将原本单独的一个input x,在BN之后,就和这个batch中其他的input捆绑到了一起。在weight regularization中,原理选择出分布更加均衡的weight来让网络的训练结果更加generalize。类似的,这里的input在被捆绑到一起之后,能够让神经网络更好地意识到每个特征之间的关系,比起单独单干的一个input,这样的训练方式可能可以取得更好的效果。(纯属胡说八道也说不定,以后再体会)

五、检查网络的学习过程

这里讲了一下从处理数据到完成训练一个神经网络的的流程。一旦发现哪一部有问题,可以提前停止,进行调整。

  1. 预处理数据 第一步就是预处理数据,如下图。
  2. 选择神经网络结构 选择适当的网络结构。
  3. 计算一次,确保loss在正常的区间 先将regularization设置为0.0 前面的视频说过,如果W初始化没有问题,那么第一次的loss应该在-log(num_classes - 1),大约2.3。 然后提高一些regularization的值,比如到1e3,正常情况下,loss也应该有所提高。 loss也有所提高,说明一切正常。
  4. 小批量数据 之后,可以先选取很小一部分数据来训练,这里只选取了20sample 这一步是确保创建的网络能够overfit小批量的数据。如下图。 这里,loss接近0training acc等于1,成功overfitting了这20sample
  5. 完整训练 这一步,首先从很小的regularization开始,先找到适当的learning_rate,可以使loss下降。 如果loss没有下降,可能是learning_rate设置太小。

六、Hyperparameter Optimization

  1. 从粗糙到精细 可以先用一小部分的数据,像上面使用的20个,找到一个大致的能让网络学习起来的参数。 然后慢慢微调。 如果在调试过程中看到loss大于了原有loss的三倍,直接停止训练,重新调整参数。 寻找参数的方式如下图。
  2. 微调 找到最初的参数之后,要进行微调。微调的时候,可以调整随机的区间,然后看哪一组数据的效果最好。 上图中,最下面红框中的准确率有%53,但是要特别当心。因为产生这个准确率的reglr都逼近边界值(10和1)。 说明了在机器学习中要也别当心边界值,边界值上的好效果可能意味着有内在的问题。当然也可能没有。
  3. 可视化训练过程以及观察数值比例 将loss的下降可视化,如果平稳下降,没有出现奇葩情况,说明一切正常。如图。 最后,也可以检查W的更新值和W的比值。正常应该再1e-3左右。

七、总结

这一集介绍了激活函数;介绍了预处理数据的几种方式;学习了如何初始化Weight;学习了Batch Normalization的作用;如何检查一个神经网络的参数是否正常,以及如何优化hyper parameter