如何优化深度神经网络
假设训练两个层数不一样的神经网络,一个层数较少(例如20层),一个层数较多(50层),在测试集中我们发现:
测试集上的效果显示20层要56层的错误率要更低,但是我们不能就先入为主的断定56层是由于参数太多导致过拟合,所以在测试集上表现要比20层神经网络要差。
其实我们应该首先看看它们在训练集上的表现:
上图我们看到在训练集上的错误率其实已经是56层的神经网络要比20层的神经网络要高了,这也有可能我们在训练的时候就没有将56层神经网络的模型训练好,以至于测试集上的表现差,因为在训练的时候你可能卡在例如局部最小值的地方,你的训练并不能总是保证得到好的结果。
这样,即使模型是一个很深的网络,参数很多但是并不能总是保证能得到好的结果,所以有时候你的网络在测试集上表现不好,可能并不是过拟合,而很有可能是你的神经网络模型一开始就没有训练好,因为网络越深就越有可能陷入局部最小值,当然还有可能是其他原因导致模型没有训练好。
文献上也有一些方法有提到如何改善网络的性能,有些方法是让网络模型在训练集上性能变好,有些则是让模型在最终的测试集上的性能变好。
有一个很知名的方法叫dropout,它的目的是要让模型在测试集上的性能变好,但是有可能其实用这个方法的时候你的模型在训练集上的性能反而变差。所以如果一开始如果模型在训练集上的表现已经很差了,如果用dropout方法就会让模型训练得越来越差。
现在假设模型在训练集上的表现就不好,当前比较流行的改善模型在训练集上的表的方法有,如下图,
看上图的手写数字分类的精度我们可以发现,随着神经网络层数的加深,网络的性能表现并没有更好,反而是更差了,也就是说神经网络层数越多并不意味的性能就更好,但是并不是说它过拟合了,因为这只是在训练集上表现的结果。
当网络很深的时候有可能存在一个问题,Vansihing Gradient:在接近输入层的那几层的梯度是非常小的,但是在接近输出的那几层梯度则比较大。
如果整个模型的参数的学习率都是一样的,那么靠近输入层的那几层的学习速度是非常慢的,而靠近输出的那几层的学习速度则相对更快。
所以在靠近输入层那几层的参数几乎没有改变的时候,也就是说几乎还是刚开始的随机值得时候,后面那几层的参数就已经收敛了,因为很有可能训练就陷入局部最小值,导致训练中断。所以模型的性能就会很差。但是为什么会出现这个问题呢?这个现象来自于sigmoid函数。看下图:
首先我们需要检查一下为什么前面几层的梯度会特别小。要算某一个参数对LOSS的偏微分,可以对参数稍微做一下变换,如下
通过对W的变动,并查看它对LOSS有多大的影响,可以估计出LOSS对参数W的偏微分有多大。例如将从输入到第一个隐藏层的参数W变化一下(加上一个^w),这个变化就会对第一层的输出有影响。
另外如果激活函数是sigmoid函数还会对这变化造成衰减,如上图,因为sigmoid函数的输入时一个正负无穷大的值,而输出则限定在0和+1之间。这个也就是说输入很大,但是经过sigmoid函数之后会衰减。假设神经网络有很多层,则每一层都会衰减。所以第一层的参数有一个变化,但是实际上传到最后一层以后就会变得很小。所以说在靠近输入的地方的参数计算出来的梯度就很小。
那么如何改善这个问题呢?
有一种办法是可以给不同的参数不同的学习率(即dynamic learn learning),这种方法比较容易想到,因为既然我们知道用在给参数固定的学习率的时候,前面的梯度比较小,而后面的参数梯度则很大,并且训练出来的模型的效果很差,但是用动态学习率就有可能改善这个问题。
另外一种也是比较直接的方法就是直接替换激活函数,因为我们已经知道问题产生的原因就是因为用的是sigmoid函数。例如将激活函数换成ReLU函数。
Relu即Rectified Linear Unit,上图提到为什么选ReLU的几个原因,第一个首先是它要比sigmoid要快,第二个是说他有生物学上的原因,第三个,hinton曾说reLU其实就是无限多个有着不同偏执的sigmoid函数,最重要的也就是第四个它可以解决梯度消失的问题。那么问题来了ReLU是如何解决Vansihing gradient problem的呢?
上面这个两层的神经网络,假设他的激活函数都是ReLU, RelU有两个运算域,第一个运算域是输入小于0,第二个是运算域是a>0; 输入小于0的时候输出a=0;在给定输入的时候,网络中的8个神经元有一些作用在输入小于0的域,有一些神经元作用在输入大于0的域。
那些输入大于0神经元来说它的输入等于输出,也就是等同于是一个线性的函数。那些输入小于0的神经元,它的输出也等于0,网络中这些输入等于0的神经元以及他所连接的那些网络等同于不存在(因为他对输出没有影响),于是神经网络可以简化为下图:
所以用Relu,这样看起来好像模型中都是线性的函数,输入和输出的关系是线性的,这样模型就看起来变成一个很瘦的线性网络,如果输入只是很小的变化,那么它就不可能改变每个神经元的运算域,这时线性的,但是如果输入变化比较大,那么就会改变神经元的运算域,这时就不是线性的,所以整体来说整个函数是非线性的,但是有可能它在小范围内是线性的。
上面是两个ReLU的变形,前面的是一个固定的参数,后面的这个是个变量,它同样有训练中的训练得到。现在还有一个比较流行的ReLU的变形叫做Exponential Linear Unit(ELU),其实它就是让左边非线性而右边线性。
让网络自动学习激活函数的方法叫做Maxout网络,在Maxout网络中ReLU只是它的一种特殊情况。
如上图有两个输入x1和x2乘上四组不同的权值,在一般的网络里面后面是接上四个激活函数然后得到输出,但是在Maxout网络里面就不是这样,它首先把输出分组,分组的规则是事先就决定好的,分好组后再取最大值,然后得到输出,7和1这是整个模型的第一个隐藏层,紧接着的又是接四个权值,然后分组再取最大值…,如此重复。
上图左上角的神经元输入经过ReLU后得到a,其中x和z的关系可以写成正下图的表达式,它是一个线性的函数(蓝色的直线)。a和z之间的关系是,如果z在横轴上面,则a大于0,若a在横轴下面则a=0。
上图右边是Maxout的网络,Maxout的网络可以做出和ReLU(左半边)一样的效果。假设网络训练出来得到的参数w和b以及0与0,Maxout即可变成ReLU。其中z1得到的曲线是和左边的网络(ReLU)是一样的。z2则等于0,即红色的直线,它跟输入没有任何关系;而Maxout会选择最大的值,这里分为两个区域,也叫是转折角的地方,左边Maxout选择的是z2,因为z1是负数,而右边选择的是z1,因为它大于0。这就是Maxout的作用。
所以Maxout可以产生为它想要转换的激活函数,例如下图
红色的直线是新的一条直线,两条直线交叉后,分段的像上面一样同Maxout取最大值。
Maxout可以可以学习出任何分段的线性的凸函数的激活函数,另外激活函数的分段数取决于分组的元素的个数。
Maxout有一个取最大值的运算,没有办法做微分,它能训练吗?其实只要是你可以算出参数的变化LOSS的变化,就可以用gradient descent即梯度下降来优化神经网络模型。所以Maxout是可以训练的。
实际的操作中,我们假设只有两个输入x1和x2并且,红色方框的输出是较大的值,我们可以将max操作想象成是线性的,然后把较小的组员当做不存在,所以在给定的输入时,Maxout也是一个线性的函数。于是在训练的时候就可以用反向传播backprogation来算每一个参数的梯度,然后用梯度下降来训练这个网络。
当然没有被maxout选中到的参数就不会用来算梯度。
它是如何调整学习率的一种方法。
就算是问题是一个凸优化问题,只要是梯度和学习率设置得够小,adagrad方法也是有效的。上图公式显示我们用一次微分估算二次微分的值,这是假设二次微分是很固定的情况。实际上我们现实中面对的问题更加复杂和更加困难。处理这种更加复杂的情况推荐用RMSProp方法,如后面的介绍。
RMSProp是adagrad的一个变形,RMSProp的相对于adagrad来说可以设置不同的学习率。
面对这些更加困难的问题,在做深度神经网络训练的通常的都是先定义一个LOSS函数。然后用梯度下降去优化这个LOSS, 但是这个LOSS函数对这组参数来说是非常复杂的,而且参数本身又是一个高维空间里的参数。其实我们不知道参数对error surface影响有多大,我们只知道它可能是很复杂的。
如上图,如果我们只考虑参数w1,它的error surface假设如图所示的碗状结构,蓝色代表LOSS较低的地方,红色代表LOSS比较高的地方。
左边蓝线可能需要比较小的学习率,因为曲线比较陡峭。靠近月牙的中间的地方,这个地方的坡度比较平缓,斜率比较小。这个地方我们希望学习率要大一些。也就是说,同一个方向上,我们有时候希望学习率大一些有时候学习率小一些。
所以相对Adagrad的固定设置比较小的学习率来说,我们有时候需要设置不同的学习率。
RMSProp的方法具体如上图所示,和adagrad一样,有分母这一项,adagrad中它过去算出来的所有的梯度的平方和。第一层更新参数的时候从w0更新为w1,初始化的分母项等于g0,第二次更新参数从w1更新到w2,此时分母项则做了一些小的变化,不再是所有梯度的平方和,而是改成新的梯度乘上(1-a)然后加上所有就得梯度的平方和。
在训练深度神经网络的时候往往会遇到各种各样的问题导致训练的中断,例如陷入了局部最小值。
但是在真是的物理世界中,训练的过程有和一个球从山坡上滚下来的动作很类似,和深度神经网络的训练不同的是,滚下来的这个球会因为惯性而翻越过小坡然后滚向下一个更深的谷地,每次到一个谷底都有再次向前的惯性。
参考真实物理世界中的这个惯性,我们可以在深度神经网络训练中引入这个惯性的概念尝试避免陷入局部的最小值,导致训练中断。
当然,这个引入惯性的尝试并不保证训练能避免陷入局部最小值。
Vanilla gradient descent 即简单的梯度下降,红色箭头是梯度,蓝色箭头是实际的运动方向。注意还有学习率。
momentum每次移动需要考虑两个方面,第一个是梯度的方向,第二个就是前面介绍的所谓的惯性。
因为每一次的movement都包含了前面的movement的信息,所以考虑前一次的movement的方向就是考虑前面所有movement的方向。
Adam就是RMSProp与Momentum的组合。
为了让测试集上的正确率和训练集上的正确率不要相差太多,Early stoping是其中的一个方法。
如果学习率设置得比较靠谱,考虑训练的时间点和LOSS的关系,那么在随着训练时间点的往后推移(更新参数),理论上LOSS就会越来越小,直到LOSS没办法下降。
但是由于训练集上会或多或少的有一些自己的特性,所以随着参数的变化,在训练集上的错误率越来越小,但是并不能保证它能在测试集上错误率越来越小。所以通常随着训练的时间点不断推进,训练集的上的LOSS会越来越小,但是反而在测试机上的LOSS就会越来越大,于是我们希望训练能停在测试集LOSS最小的地方。
所以在训练集上训练的每个训练的epoch结束后都去算一下验证集的错误率,如果错误率不在下降就应该停止训练。
正则化就是要让新的LOSS函数最小,找到一个权值集合不仅能最小化原始的cost,同时也越接近0;也就是说在做正则化的时候就修改我的cost函数,即在原始LOSS的基础上加上一个正在化项,这个正则化项可以是L1或是L2; 正则化项也是越小越好。
对新的LOSS函数求梯度,就可以得到更新了的权值,新的权值参数与前面的一个权值和学习率和拉莫达有关,wt每一次都会乘以一个小于1的值,它会随着学习的进行慢慢的变小,最终趋向于0,但是最终不是所有的参数都变成0,因为还有后面的一项和前面的一项取得平衡,如果某个参数对LOSS的微分都等于0了,那就说明这个参数权值对LOSS没有影响,也就是说那些没有用的参数最后都会变成0,而有用的参数最终不会是0,又由于这些参数会越来越小,所以L2的正则化也叫做weight decay。
L1正则化求其梯度后得到的sgn(w)也就是根据w的正负号取+1或-1;这个也就是说我们队L1正则化项对w求微分得到的是它的方向。
与L2正则化一样,L1也让参数接近0,但是具体的做法则不一样,L2是让参数乘上一个固定的小于1的则,而L1则是让参数减掉一个固定的值。
L2对比较大的参数惩罚比较强,因为是让固定参数乘以某一个小与1的值,参数会是聚集在靠近0的值,而不会是等于0;
L1则是有些参数之间的差距会较大,有些参数会比较大,而有些参数则接近0.
Dropout是一个比较有深度学习特色的方法。
每一次更新参数的之前,都会对每一个神经元做一次采样,而每一个神经元都有%P的几率会被dropout。
如上图,ropout的神经元就会在网络里面消失,所以没做一次dropout神经网络的结构都会改变,它会变得更瘦,然后每次训练都是用dropout之后的"瘦小"的网络。另外每一次更新参数之前都要对网络做一次dropout。
测试的时候有两个重点:
第一个是测试的时候没有dropout;
第二个重点是如果训练的dropout速率是p%,在测试的时候所有的权值(训练得到)都要乘以(1-%p),举个例子就是假设dropout的速率是50%,如果训练得到的权值是1,在测试的时候w就是0.5.
因为在训练时候dropout %p的神经元出局,所以参数就会减少%p,但是在测试的时候参数是全部参数,所以在测试集测试的时候参数乘上一个1-p%反而让测试集和训练集的情况更加匹配。
因为退出是整体的一种形式,如上图,把训练集分成四个子集,即用四个不同的网络来训练。
回顾一下,如果有一个模型训练的方差很大,说明它每一次训练出来的结果会相差很多,所以模型的性能就会很不好。但是如果把这些方差很大的模型平均起来就会得到bias很小的模型。
Ensamble用的就是这个概念,因为如果单一网络的方差很大,但是你把网络分成很多个子集(也就是dropout后的结果),每个子集的结构可能相差很大,用不同的训练数据来训练,然后把这些多个网络的输出平均起来就能得到好的结果。
上图表示dropout的训练是用不同的形状的minibatch训练网络,如果M个神经元就会有2的M次方个可能的网络,这些不同的minibatch网络共享一些参数,也就是说这些共享的参数是由不同的minibatch网络训练出来的。
Ensemble在理论上,给定的测试数据输入给每一个网络,然后每个网络都会有一个输出,最后把每个网络的输出平均起来才是最终的结果,如果有M个网络就会大概有2M次方个可能的网络输出,然后做平均 – 这是ensemble的做法。但是dropout不是这样,它只是将所有的参数都乘上1-%p。
用一个简单的例子解释为什么dropout的做法和ensamble的做法不一样。上图表示有一个最简单的网络(右上角),dropout后可以得到四个不同的网络,它们分别训练。理论上把它们四个的输出平均起来,如上图右下角图。
那么如何让原始的网络得到和dropout一样的输出,很简单,可以直接让原始的输出乘以1/2,从这里可以看出来如果是ensamble测试的时候要穷举所有的网络可能是跟乘以一个参数是很近似的。
如果将激活函数由原来的线性函数改成sigmoid函数会是怎么样的呢?左边的ensamble方法和右边的直接乘以1/2的方法就不近似了。所以如果激活函数换成非线性的时候就不成立。
但是如果激活函数是非线性的但是却很接近线性,dropout的结果也有可能很好,例如maxout它在小范围内可以看成是线性的网络