本文缘起于一次 CNN 作业中的一道题, 这道题涉及到了基本的 CNN 网络搭建, 在 MNIST 数据集上的分类结果, Batch Normalization 的影响, Dropout 的影响, 卷积核大小的影响, 数据集大小的影响, 不同部分数据集的影响, 随机数种子的影响, 以及不同激活单元的影响等, 能够让人比较全面地对 CNN 有一个了解, 所以想做一下, 于是有了本文.
工具
开源深度学习库: PyTorch http://pytorch.org
数据集: MNIST http://yann.lecun.com/exdb/mnist/
实现
初始要求
首先建立基本的 BASE 网络, 在 Pytorch 中有如下 code:
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1), padding=0)
self.conv2 = nn.Conv2d(20, 50, kernel_size=(5, 5), stride=(1, 1), padding=0)
- self.fc1 = nn.Linear(4*4*50, 500)
- self.fc2 = nn.Linear(500, 10)
- def forward(self, x):
- x = F.max_pool2d(self.conv1(x), 2)
- x = F.max_pool2d(self.conv2(x), 2)
- x = x.view(-1, 4*4*50)
- x = F.relu(self.fc1(x))
- x = self.fc2(x)
- return F.log_softmax(x)
这部分代码见 base.py.
问题 A: 预处理
即要求将 MNIST 数据集按照规则读取并且 tranform 到适合处理的格式. 这里读取的代码沿用了 BigDL Python Support 的读取方式, 无需细说, 根据 MNIST 主页上的数据格式可以很快读出, 关键 block 有读取 32 位比特的函数:
- def _read32(bytestream):
- dt = numpy.dtype(numpy.uint32).newbyteorder('>') # 大端模式读取, 最高字节在前(MSB first)
- return numpy.frombuffer(bytestream.read(4), dtype=dt)[0]
读出后是 (N, 1, 28, 28) 的 tensor, 每个像素是 0-255 的值, 首先做一下归一化, 将所有值除以 255, 得到一个 0-1 的值, 然后再 Normalize, 训练集和测试集的均值方差都已知, 直接做即可. 由于训练集和测试集的均值方差都是针对归一化后的数据来说的, 所以刚开始没做归一化, 所以 forward 输出和 grad 很离谱, 后来才发现是这里出了问题.
这部分代码见 preprocessing.py.
问题 B:BASE 模型
将 random seed 设置为 0, 在前 10000 个训练样本上学习参数, 最后看 20 个 epochs 之后的测试集错误率. 最后结果为:
Test set: Average loss: 0.0014, Accuracy: 9732/10000 (97.3%)
可以看到, BASE 模型准确率并不是那么的高.
问题 C:Batch Normalization v.s BASE
在前三个 block 的卷积层之后加上 Batch Normalization 层, 简单修改网络结构如下即可:
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
self.conv1 = nn.Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1), padding=0)
self.conv2 = nn.Conv2d(20, 50, kernel_size=(5, 5), stride=(1, 1), padding=0)
- self.fc1 = nn.Linear(4*4*50, 500)
- self.fc2 = nn.Linear(500, 10)
- self.bn1 = nn.BatchNorm2d(20)
- self.bn2 = nn.BatchNorm2d(50)
- self.bn3 = nn.BatchNorm1d(500)
- def forward(self, x):
- x = self.conv1(x)
- x = F.max_pool2d(self.bn1(x), 2)
- x = self.conv2(x)
- x = F.max_pool2d(self.bn2(x), 2)
- x = x.view(-1, 4*4*50)
- x = self.fc1(x)
- x = F.relu(self.bn3(x))
- x = self.fc2(x)
- return F.log_softmax(x)
同样的参数 run 一下, 得出加了 BN 的结果为:
Test set: Average loss: 0.0009, Accuracy: 9817/10000 (98.2%)
由此可见, 有明显的效果提升.
关于 Batch Normalization 的更多资料参见[2],[5].
问题 D: Dropout Layer
在最后一层即 fc2 层后加一个 Dropout(p=0.5)后, 在 BASE 和 BN 上的结果分别为:
- BASE:Test set: Average loss: 0.0011, Accuracy: 9769/10000 (97.7%)
- BN: Test set: Average loss: 0.0014, Accuracy: 9789/10000 (97.9%)
观察得知, dropout 能够对 BASE 模型起到一定提升作用, 但是对 BN 模型却效果不明显反而降低了.
原因可能在于, BN 模型中本身即包含了正则化的效果, 再加一层 Dropout 显得没有必要反而可能影响结果.
问题 E:SK model
SK model: Stacking two 3x3 conv. layers to replace 5x5 conv. layer
如此一番改动后, 搭建的 SK 模型如下:
- class Net(nn.Module):
- def __init__(self):
- super(Net, self).__init__()
self.conv1_1 = nn.Conv2d(1, 20, kernel_size=(3, 3), stride=(1, 1), padding=0)
self.conv1_2 = nn.Conv2d(20, 20, kernel_size=(3, 3), stride=(1, 1), padding=0)
self.conv2 = nn.Conv2d(20, 50, kernel_size=(3, 3), stride=(1, 1), padding=0)
- self.fc1 = nn.Linear(5*5*50, 500)
- self.fc2 = nn.Linear(500, 10)
- self.bn1_1 = nn.BatchNorm2d(20)
- self.bn1_2 = nn.BatchNorm2d(20)
- self.bn2 = nn.BatchNorm2d(50)
- self.bn3 = nn.BatchNorm1d(500)
- self.drop = nn.Dropout(p=0.5)
- def forward(self, x):
- x = F.relu(self.bn1_1(self.conv1_1(x)))
- x = F.relu(self.bn1_2(self.conv1_2(x)))
- x = F.max_pool2d(x, 2)
- x = self.conv2(x)
- x = F.max_pool2d(self.bn2(x), 2)
- x = x.view(-1, 5*5*50)
- x = self.fc1(x)
- x = F.relu(self.bn3(x))
- x = self.fc2(x)
- return F.log_softmax(x)
在 20 个 epoch 后, 结果如下,
SK: Test set: Average loss: 0.0008, Accuracy: 9848/10000 (98.5%)
测试集准确率得到了少许的提高.
这里利用 2 个 3x3 的卷积核来代替大的 5x5 卷积核, 参数个数由 5x5=25 变为了 2x3x3=18. 实践表明, 这样使得计算更快了, 并且小的卷积层之间的 ReLU 也很有帮助.
VGG 中就使用了这种方法.
问题 F:Change Number of channels
通过将特征图大小乘上一个倍数, 再通过 shell 程序执行, 得到如下结果:
- SK0.2: 97.7%
- SK0.5: 98.2%
- SK1: 98.5%
- SK1.5: 98.6%
- SK2: 98.5% (max 98.7%)
在特征图分别为 4,10, 30, 40 时, 最终的准确度基本是往上提升的. 这在一定程度上说明, 在没有达到过拟合前, 增大特征图的个数, 即相当于提取了更多的特征, 提取特征数的增加有助于精度的提高.
这部分代码见 SK_s.py 和 runSK.sh.
问题 G:Use different training set sizes
同样通过脚本运行, 增加参数
- parser.add_argument('--usedatasize', type=int, default=60000, metavar='SZ',
- help='use how many training data to train network')
表示使用的数据大小, 从前往后取 usebatchsize 个数据.
这部分程序见 SK_s.py 和 runTrainingSize.sh.
运行的结果如下:
- 500: 84.2%
- 1000: 92.0%
- 2000: 94.3%
- 5000: 95.5%
- 10000: 96.6%
- 20000: 98.4%
- 60000: 99.1%
由此可以明显地看出, 数据越多, 结果的精度越大.
太少的数据无法准确反映数据的整体分布情况, 而且容易过拟合, 数据多到一定程度效果也会不明显, 不过, 大多数时候我们总还是嫌数据太少, 而且更多的数据获取起来也有一定难度.
问题 H:Use different training sets
采用脚本完成, 这部分程序见 SK_0.2.py 和
- diffTrainingSets.sh
- .
运行结果如下:
- 0-10000: 98.0%
- 10000-20000: 97.8%
- 20000-30000: 97.8%
- 30000-40000: 97.4%
- 40000-50000: 97.5%
- 50000-60000: 97.7%
由此可见, 采用不同的训练样本集合训练出来的网络有一定的差异, 虽不是很大, 但是毕竟显示出了不稳定的结果.
问题 I:Random Seed's effects
采用 runSeed.sh 脚本完成, 用到了全部 60000 个训练集.
运行的结果如下:
- Seed 0: 98.9%
- Seed 1: 99.0%
- Seed 12: 99.1%
- Seed 123: 99.0%
- Seed 1234: 99.1%
- Seed 12345: 99.0%
- Seed 123456: 98.9%
事实上在用上整个训练集的时候, 随机数生成器的种子设置对于最后结果的影响不大.
问题 J:ReLU or Sigmoid?
将 ReLU 全部换成 Sigmoid 后, 用全部 60000 个训练集训练, 有对比结果如下:
ReLU SK_0.2: 99.0%
Sigmoid SK_0.2: 98.6%
由此可以看出, 在训练 CNN 时, 使用 ReLU 激活单元比 Sigmoid 激活单元要更好一些. 原因可能在于二者机制的差别, sigmoid 在神经元输入值较大或者较小时, 输出值会近乎 0 或者 1, 这使得许多地方的梯度几乎为 0, 权重几乎得不到更新. 而 ReLU 虽然增加了计算的负担, 但是它能够显著加速收敛过程, 并且也不会有梯度饱和问题.
总结
本文利用 PyTorch 对几个 CNN 模型在 MNIST 数据集上的比较, 以及一些参数的设置对模型效果的影响, 从而对 CNN 的许多方面进行了一些详细的评估.
用过这么一次觉得 PyTorch 还是挺好用的, 比较简单, 其他模型不知道, 反正卷积神经网络模型是如此.
项目具体代码见[7].
由于笔者对 CNN(卷积神经网络)研究不太深入, 所以每个结果后的解释或有失偏颇, 读者批判阅读即可.
References
[1] Introduction to Convolutional Neural Networks https://cs.nju.edu.cn/wujx/paper/CNN.pdf
- [2] Batch Normalization http://proceedings.mlr.press/v37/ioffe15.pdf
- [3] PyTorch Doc http://pytorch.org/docs/index.html
- [4] MatConvNet http://www.vlfeat.org/matconvnet/
[5] 知乎 - 深度学习中 Batch Normalization 为什么效果好? https://www.zhihu.com/question/38102762
[6] Dropout http://jmlr.org/papers/volume15/srivastava14a/srivastava14a.pdf
[7] Github 上的代码 https://github.com/whatbeg/Data-Analysis-and-Mining/tree/master/CNN_for_Mnist
来源: https://juejin.im/entry/5ae139806fb9a07aca79d993