CNN 自写代码调参经验

对非线性函数的一个最成功的改进就是纠正线性单元(Rectified Linear Units,ReLU),其作用是如果卷积计算的值小于0,则让其等于0,否则即保持原来的值不变。这种做法所谓是简单粗暴,但ReLU使网络的激活具有了稀疏的特性,实验说明一切。


起因

从去年开始接触神经网络,觉得CNN结构很简单没什么难的,直到有一天被一位拿过信息竞赛金牌的学弟鄙视了……中期答辩之后不忙,于是我花了几天用MATLAB写了一个CNN,算是给自己一个交代。

为什么用MATLAB?

  1. 相比c/c++,MATLAB实现更方便
  2. 我没打算写又快又漂亮的产品级的代码,只是希望抠一遍算法的各种细节,所以theano等各种成熟的工具包就不考虑了

实现

这篇文章不打算讲各种基础的细节,只是总结一下我踩的各种坑……

CNN训练无非是反向传播,现成的公式很容易找到,自己花一些时间也能推导出来。我参照的是这篇文章:

@article{bouvrie2006notes,
title={Notes on convolutional neural networks},
author={Bouvrie, Jake},
year={2006}
}

这篇文章的好处在于作者在推导公式的同时顺便给出了MATLAB上的实现方法。

遇到的问题

梯度消失

我在实现过程中犯的第一个错误是没有循序渐进。仗着自己写过一些神经网络的代码以为手到擒来,直接按照 LeNet-5 的结构写,过于复杂的结构给测试和调试都带来了很大的麻烦,可谓不作死就不会死。

简单分析一下LeNet-5的结构:第一层8个5*5的卷积核,第二层分别作2*2pooling,第三层16个5*5的卷积核,第四层2*2pooling,随后是三个节点数分别为120、84、10的全连接层(做手写数字识别)。这时的参数个数为卷积核及其偏置+pooling层放缩系数及偏置+全连接层权重及偏置,全链接层的参数个数比前四层的参数个数 多了两个数量级 。

过多的参数个数会极大地提升运算时间,降低测试的效率;同时过多的层数伴随着严重的梯度消失问题(vanishing gradient),严重影响训练效果。在上面提到的网络结构下,我发现传到第一层的梯度已经小于1e-10,只有后面的全连接层在变化,卷积层几乎训不动了。

我对付这个问题的手段包括:

  1. 减少层数
  2. 增大学习率(learning rate)
  3. 用ReLU代替sigmoid

其中前两种虽然也有效果,只能算是权宜之计,第三种才算是正经的解决了这个问题。在采用ReLU作为激活函数之后,传到输入层的梯度已经达到1e-5~1e-4这个量级。我用MNIST数据集里的5000个样本训练,1000个样本测试,发现测试结果差不多,收敛速度快了2倍左右。据@kevin好好学习的说法,ReLU还使网络的激活具有了稀疏的特性,我对这方面就没研究了。

非线性映射的位置

在前面提到的Bouvrie的文章中,非线性映射放在卷积层还是pooling层后面都可以,微博上几位大牛也是这个观点。奇怪的是,我在一开始实现的时候把sigmoid放在了pooling层后面,虽然很渣但至少能跑出结果。后来我把sigmoid放在卷积层后面就训不动了,感觉跟梯度消失的时候很像。我猜测也许是卷积层和pooling层激活程度不同导致传回去的梯度大小不一样。

权重衰减(weight decay)

因为我用的数据很少(数据多了跑起来太慢),我担心会出现过拟合,所以在cost function里加了一项正则项。但结果是不加正则项训练结果很好,一加就出现严重的under fitting,不管怎么调参数都没用。原来一直听说CNN的权重共享就相当于自带某种正则化,现在看起来确实是这样。

随机梯度下降(SGD)的参数选择

最主要的就一个参数,minibatch的大小。在 Deep Learning Toolbox 的demo里,这个值取的是50。但是我的实验中,似乎minibatch取1的时候收敛最快。

我的感觉:训练样本顺序随机的话,每次产生的梯度也是随机的,那么50个样本平均一下梯度就很小了,也许这样比较稳健,但收敛会比较慢;相反,每个样本更新一次梯度就大得多,不过也许会比较盲目。

另外还有一个参数是learning rate。在实验中,增大minibatch会出现训不动的情况,这时适当增大learning rate能够让training cost继续下降。

另外Yann LeCun似乎说过每一层应该采取不同的learning rate,不过我没试。

增加全连接层数后的性能

关于这点我不是很确定,但似乎除了增加训练时间外没什么实际作用……

网络的改进

自动学习组合系数

为了打破对称性,从第一个卷积层到第二个卷积层并不是全链接,例如第二层第一个卷积核只卷积第一层的第123个feature map,第二层第二个卷积核只卷积第一层的第345个feature map。在LeNet里这个组合是人为规定的,Bouvrie的文章中提出这个组合系数也是可以学出来的。但是我的实验里人为规定和自动学习的效果好像差别不大,不知道更复杂的数据集上会怎么样。

ReLU的位置

如果网络的最后一层是softmax分类器的话似乎其前一层就不能用ReLU,因为ReLU输出可能相差很大(比如0和几十),这时再经过softmax就会出现一个节点为1其它全0的情况。softmax的cost function里包含一项log(y),如果y正好是0就没法算了。所以我在倒数第二层还是采用sigmoid。

其他高级玩法

本来还打算玩一些其他更有趣的东西,比如dropout、maxout、max pooling等等。但是时间有限,老板已经嫌我不务正业了。

总结

这次的收获:

  1. 写复杂的算法不能急于求成,要循序渐进
  2. 测试很重要。上Andrew Ng公开课时候觉得gradient check很烦,现在看来简直是神器
  3. 机器越快越好……


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章