Neural Networks and Deep Learning读书笔记--卷积神经网络应用:手写数字识别

已经明白了卷积神经网络后面的核心思想,想要通过实现一些卷积网络,并将它们应用于 MNIST 数字分类问题,来看看它们如何在实践中工作。
使用的程序是 network3.py,它是前面章节开发的 network.py 和 network2.py 的强化版本。程序 network.py 和 network2.py 是用 Python 和矩阵库 Numpy 实现的。这些程序从最初的原理工作,并致力于反向传播、随即梯度下降等细节。 network3.py 使用一个称为 Theano 的机器学习库[1]。使用 Theano 使得实现针对卷积神经网络的反向传播很容易,因为它自动计算涉及到的映射。特别地,Theano 的一个非常好的特性是它能够运行于 CPU 或者,如果可以,GPU 上。运行于 GPU 上可以提供显著的增速,而且,有助于实际用于更复杂的网络。

([1]参见 Theano: A CPU and GPU Math Expression Compiler in Python (http://www.iro.umontreal.ca/~lisa/pointeurs/theano_scipy2010.pdf),作者为 James Bergstra, Olivier Breuleux, Frederic Bastien, Pascal Lamblin, fRavzan Pascanu, Guillaume Desjardins, Joseph Turian, David Warde-Farley, 和 Yoshua Bengio (2010)。 Theano 也是流行的 Pylearn2(http://deeplearning.net/software/pylearn2/) 和 Keras (http://keras.io/) 神经网络库的基础。其它在本文写作时流行的神经网路库包括 Caffe (http://caffe.berkeleyvision.org/) 和 Torch (http://torch.ch/) 。)

因此需要一个可运行在你的系统上的 Theano,首先按照项目主页上的说明来安装 Theano。下面的例子使用 Theano 0.6运行过,有些在没有 GPU 支持的 Mac OS X Yosemite 运行过,有些在有 NVIDIA GPU 支持的 Ubuntu 14.04 中运行过,有些实验在两个系统中都运行过。为了让 networks3.py 运行,需要把 networks3.py 源码中的 GPU 标志设置为 True 或者 False。此外,为了让 Theano 运行于 GPU 上,这份指导说明有帮助,联网上也有教程,很容易用 Google 搜索到。如果你手上的系统没有可用的 GPU,那么你可能想要看下 Amazon Web Services EC2 G2 实例类型。注意即使有 GPU 支持,代码仍然需要一些时间执行。许多实验要花费从几分钟到几个小时的时间来运行。在 CPU 上可能需要花费数天时间来运行最复杂的实验。正如前面章节里说的,我建议让程序运行着,同时继续阅读,偶尔回来检查下代码的输出。如果你用的是 CPU,你可能需要对更复杂的实验减少训练迭代期的数量,或者整个忽略它们。

浅层网络

为了取得一个基线,从一个浅层架构开始,它仅仅使用一个隐藏层,包含100个隐藏神经元。我们会训练60个epoch,使用学习速率为0.1,小批量数据 大小为10,没有规范化。这样运行:

>>> import network3
>>> from network3 import Network
>>> from network3 import ConvPoolLayer, FullyConnectedLayer, SoftmaxLayer
>>> training_data, validation_data, test_data = network3.load_data_shared()
>>> mini_batch_size = 10
>>> net = Network([
        FullyConnectedLayer(n_in=784, n_out=100),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(training_data, 60, mini_batch_size, 0.1,
            validation_data, test_data)

获得的一个最好的分类准确率是97.8%。这是 在 validation_data 上得到了最好的分类准确率时的epoch处测得的test_data 上的分类准确率。使用验证数据来决定在何时对测试准确率估值有助于避免测试数据上的过度拟合。可能结果稍有不同,因为网络的权重和偏置是随机初始化的。

使用一个相似的网络架构和学习超参数,这个97.8% 的准确率接近于第三章中获得的 98.04% 的准确率。特别地,两个例子都使用一个浅层网络,具有单个包含有100个隐藏神经元的隐藏层。两者都训练 60个迭代期,小批量数据大小为10,学习速率 为0.1。
然而,与之前的网络中有两个不同的地方。首先,我们规范化了之前的网络,来帮助降低过度拟合带来的影响。规范化当前的网络确实可以提高准确率,但是得到的只是很小,所以我们将推迟到后面再来考虑规范化。第二,虽然之前的网络中的最终层使用了sigmoid激活函数和交叉熵代价函数,当前网络使用一个softmax和对数似然代价函数。正如第三章中解释的,这不是一个大的改变。(主要是因为softmax和对数似然代价函数在图像分类网络中很常见)

卷积神经网络

1.一层卷积-池化层

尝试使用一个更深的网络:
在网络开始位置的右边插入一个卷积层,使用55局部感受野,跨距为 1,20个特征映射。插入一个最大池化层,用一个 22的池化窗口来合并特征。所以总体的网络架构看起来很像上一节讨论的架构,但是有一个额外的全连接层:
在这里插入图片描述
在这个架构中,可以把卷积和池化层看作是在学习输入训练图像中的局部感受野,而后面的全连接层则在一个更抽象的层次学习,从整个图像整合全局信息。这是一种常见的卷积
神经网络模式。
训练这个网络:

>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28),
                      filter_shape=(20, 1, 5, 5),
                      poolsize=(2, 2)),
        FullyConnectedLayer(n_in=20*12*12, n_out=100),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(training_data, 60, mini_batch_size, 0.1,
            validation_data, test_data)

得到了98.78% 的准确率,这是相当大的改善,超过了我们以前结构的任何一个。事实上,我们已经减少了超过三分之一的错误率,这是一个很大的进步。

在指定网络结构时,我把卷积和池化层作为一个单一层对待。不管他们是被视为分开的层还是作为一个单一的层在一定程度上是一个个人喜好的问题。network3.py 视他们为单个层,因为它使得 network3.py 的代码更紧凑。然而,如果需要的话,很容易修改 network3.py 使得这些层可以单独指定。

2.两层卷积-池化层

尝试进一步提高准确率,试着插入第二个卷积–池化层。把它插在已有的卷积–池化层和全连接隐藏层之间。再次使用一个 55 局部感受野,池化22的区域。看看用前面相似的超参数训练会发生什么:

>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28),
                      filter_shape=(20, 1, 5, 5),
                      poolsize=(2, 2)),
        ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12),
                      filter_shape=(40, 20, 5, 5),
                      poolsize=(2, 2)),
        FullyConnectedLayer(n_in=40*4*4, n_out=100),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(training_data, 60, mini_batch_size, 0.1,
            validation_data, test_data)

得到了改善:达到了99.06% 的分类准确率。

应用第二个卷积–池化层的意义:可以认为第二个卷积–池化层输入12*12 幅“图像”,其“像素”代表原始输入图像中特定的局部特征的存在(或不存在),也就是可以认为这一层输入原始输入图像的一个版本。这个版本是经过抽象和凝缩过的,但是仍然有大量的空间结构,所以使用第二个卷积–池化层是有意义的。

从前面层的输出涉及 20 个独立的特征映射,所以对第二个卷积–池化层有201212个输入,这就好像有20 幅单独的图像输入给第二个卷积–池化层,而不是第一个卷积–池化层输入的单幅图像。允许第二个卷积–池化层中的每个神经元从它的局部感受野中的所有2055输入神经元学习,也就是第二个卷积–混合层中的特征检测器可访问所有前面层的特征,但仅在其特定的局部感受野中[2]。
([2]如果输入图像是有颜色的,这个问题会在第一层中出现。在这种情况下,对于每一个像素会有 3个输入特征,对应于输入图像中的红色、绿色和蓝色通道。因此允许特征检测器可访问所有颜色信息,但仅仅在一个给定的局部感受野中。)

到现在为止,我们开发的网络实际上是 LeNet-5网络的变形,并引入了 MNIST 问题。试着改变一些内容:
使用 tanh 激活函数: 已知 tanh 函数可以是一个比 figmoid函数更好的激活函数,但是还没有实际采用过这些建议,因为已经用 sigmoid取得了大量进展。现在尝试用 tanh 作为激活函数,训练卷积和全连接层中具有 tanh 激活值的网络(可以将 activation_fn=tanh 作为一个参数传递给 ConvPoolLayer 和 FullyConnectedLayer 类。)开始时使用 sigmoid网络中使用的相同的超参数,但是训练 20个迭代期,然后继续训练到 60个迭代期。试着将tanh和 S型网络的每个迭代期的验证准确率都绘制出来,都绘制到60个迭代期。会发现 tanh 网络训练得稍微快些,但是最终的准确率非常相似。
(你能否解释为什么 tanh 网络可以训练得更快?你能否用 S型取得一个相似的训练速度,也许通过改变学习速率,或者做些调整[9]?
试着用五六个迭代学习超参数和网络架构,寻找 tanh 优于 S 型的方面。)

使用修正线性单元: 改变神经元,使用修正线性单元而不是 sigmoid激活函数。使用激活函数 f(z)=max(0,z)。训练 R60个迭代期,学习速率为0.03,L2 规范化参数0.1:

>>> from network3 import ReLU
>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28),
                      filter_shape=(20, 1, 5, 5),
                      poolsize=(2, 2),
                      activation_fn=ReLU),
        ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12),
                      filter_shape=(40, 20, 5, 5),
                      poolsize=(2, 2),
                      activation_fn=ReLU),
        FullyConnectedLayer(n_in=40*4*4, n_out=100, activation_fn=ReLU),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(training_data, 60, mini_batch_size, 0.03,
            validation_data, test_data, lmbda=0.1)

得到一个 99.23% 的分类准确率,它稍微超过了 S 型的结果(99.06)。在所有实验中发现基于修正线性单元的网络,其性能始终优于基于 S 型激活函数的网络。
修正线性激活函数好于 S 型或者 tanh 函数的原因是以经验为依据的:一些人经常基于直觉或者启发式的理由试着用修正线性单元(一个通常的理由是 max(0,z) 在 z 取最大极限时不会饱和,不像 S 型神经元,而这有助于修正线性单元持续学习),在分类基准数据集时取得了很好的结果,并且其实践传播开了。

**扩展训练数据:**另一种可能希望改进结果的方法是以算法形式扩展训练数据。扩展训练数据的一个简单的方法是将每个训练图像置换一个像素,可以是上一个像素,下一个像素,左一个像素,或右一个像素。我们可以通过在 shell 提示符中运行程序 expand_mnist.py 来这样做[12]:

$ python expand_mnist.py

运行这个程序取得 50,000幅 MNIST 训练图像并扩展为具有250,000幅训练图像的训练集。然后可以使用这些训练图像来训练网络。使用和上面一样的具有修正线性单元的网络。初始的实验中减少了训练迭代期的数量(因为在训练5倍的数据)。但是实际上,扩展数据结果是相当多地减少了过度拟合的影响。所以在做了一些实验后最终回到训练 60个迭代期。不管怎样,让我们训练:

>>> expanded_training_data, _, _ = network3.load_data_shared(
        "../data/mnist_expanded.pkl.gz")
>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28),
                      filter_shape=(20, 1, 5, 5),
poolsize=(2, 2),
                      activation_fn=ReLU),
        ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12),
                      filter_shape=(40, 20, 5, 5),
                      poolsize=(2, 2),
                      activation_fn=ReLU),
        FullyConnectedLayer(n_in=40*4*4, n_out=100, activation_fn=ReLU),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(expanded_training_data, 60, mini_batch_size, 0.03,
            validation_data, test_data, lmbda=0.1)

使用扩展后的训练数据取得了一个 99.37% 的训练准确率。这个几乎是微不足道的改变在分类准确率上给出了一个显著的改进。这种以算法形式扩展数据的想法还可以更进一步,如通过旋转,位移和扭曲 MNIST 训练图像来扩展,通过组合所有这些流程,相当大地增加了训练数据的有效规模。

**插入一个额外的全连接层:**使用和上面完全相同的程序,但是扩展全连接层的规模。试过300和 1000个神经元,分别取得了 99.46% 和 99.43%。但对于前面的结果(99.37%)并不是一个令人信服的超越。
因此试着插入一个额外的全连接层,这样就有两个100 个隐藏神经元的全连接层:

>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28),
                      filter_shape=(20, 1, 5, 5),
                      poolsize=(2, 2),
                      activation_fn=ReLU),
        ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12),
                      filter_shape=(40, 20, 5, 5),
                      poolsize=(2, 2),
                      activation_fn=ReLU),
        FullyConnectedLayer(n_in=40*4*4, n_out=100, activation_fn=ReLU),
        FullyConnectedLayer(n_in=100, n_out=100, activation_fn=ReLU),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(expanded_training_data, 60, mini_batch_size, 0.03,
            validation_data, test_data, lmbda=0.1)

取得一个 99.43% 的测试准确率,扩展后的网络并没有帮助太多。运行类似的试验,用包含300和1000个隐藏神经元的全连接层产生 99.48% 和 99.47% 的结果。这是令人鼓舞的,但仍然缺乏一个真正决定性的胜利。

扩展的或者额外的全连接层真的对 MNIST 没帮助吗?或者说网络有能力做得更好,但我们在用错误的方式学习?例如,也许我们可以用更强有力的规范化技术来减小过度拟合的趋势。一种可能性是第三章介绍的弃权技术。回想弃权的基本思想是在训练网络时随机地移除单独的激活值。这使得模型对单独依据的丢失更为强劲,因此不太可能依赖于训练数据的特质。让我们试着应用弃权到最终的全连接层:

>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28),
filter_shape=(20, 1, 5, 5),
                      poolsize=(2, 2),
                      activation_fn=ReLU),
        ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12),
                      filter_shape=(40, 20, 5, 5),
                      poolsize=(2, 2),
                      activation_fn=ReLU),
        FullyConnectedLayer(
            n_in=40*4*4, n_out=1000, activation_fn=ReLU, p_dropout=0.5),
        FullyConnectedLayer(
            n_in=1000, n_out=1000, activation_fn=ReLU, p_dropout=0.5),
        SoftmaxLayer(n_in=1000, n_out=10, p_dropout=0.5)],
        mini_batch_size)
>>> net.SGD(expanded_training_data, 40, mini_batch_size, 0.03,
            validation_data, test_data

取得了99.60% 的准确率,这是一个显著的超越我们前面结果的进步,尤其是我们主要的基准,具有100个隐藏神经元的网络,其中我们达到了99.37%。

有两个值得注意的变化。
首先,减少了训练迭代期的数量到40:弃权减少了过度拟合,所以我们学习得更快。
其次,全连接隐藏层有1000个神经元,不是之前使用的100个。当然,在训练时弃权有效地忽略了很多神经元。所以一些扩充是可以预期的。(实际上试过用 300 和1000个隐藏神经元的实验,用1000个隐藏神经元(非常略微地)取得了更好的验证性能)

**使用一个组合的网络:**一个简单的进一步提高性能的方法是创建几个神经网络,然后让它们投票来决定最好的分类。例如,假设我们使用上述的方式训练了 5个不同的神经网络,每个达到了接近于99.6% 的准确率。尽管网络都会有相似的准确率,他们很可能因为不同的随机初始化产生不同的错误。在这5个网络中进行一次投票来取得一个优于单个网络的分类,似乎是合理的。
它确实产生了更进一步的改善:最终得到了 99.67% 的准确率。网络组合正确分类了除了 33个之外所有的10000个测试图像。剩余的测试集中的错误显示在下面。右上角的标签是按照 NMIST 数据的正确的分类,而右下角的标签是我们组合网络的输出。
在这里插入图片描述
开头两个数字,一个 6 和一个 5,是我们的组合犯的真正的错误。然而,它们也是可以理解的错误,人类也会犯。那个 6 确实看上去更像一个 0,而那个 5看上去更像一个 3。第三幅图像,据称是一个 8,在我看来实际上更像一个 9。所以这里我站在网络组合这边:我认为它比最初画出这些数字的人做得更好。另一方面,第四幅图像,那个 6,确实看上去是我们网络分类错了。如此等等。在大多数情况下我们网络的选择看上去至少是合理的,而在一些情况下我们比最初写这些数字的人做得更好。总体而言,我们的网络提供卓越的性能,特别是当你认为它们正确分类的 9,967 张图片,这是没有显示。在这种背景下,这里的不清晰的错误似乎是可以理解的。甚至一个细心的人也会偶尔犯错误。因此我认为只有一个非常细心和有条理的人才会做得更好。我们的网络正在接近人类的性能。

**为什么我们只对全连接层应用弃权:**只在网络的全连接部分应用了弃权,而不是卷积层。原则上可以在卷积层上应用一个类似的程序。但是,实际上那没必要:卷积层有相当大的先天的对于过度拟合的抵抗。原因是共享权重意味着卷积滤波器被强制从整个图像中学习。这使他们不太可能去选择在训练数据中的局部特质。于是就很少有必要来应用其它规范化,例如弃权。

为什么我们能够训练? 深的、多层的神经网络中的会有一些基本障碍。特别是梯度往往是相当不稳定的:当我们从输出层移动到前面层,梯度趋于消失(消失的梯度问题)或爆炸(爆炸的梯度问题)。由于梯度是我们用来训练的动机,这会导致问题。

因此:(1)使用卷积层极大地减少了这些层中的参数的数目,使学习的问题更容易;(2)使用更多强有力的规范化技术(尤其是弃权和卷积层)来减少过度拟合,否则它在更复杂的网络中是更多的问题;(3)使用修正线性单元而不是 S 型神经元,来加速训练 —— 依据经验通常是 3—5倍;(4)使用 GPU 并愿意长时间的训练。特别是,在我们最后的实验中,我们训练了40个迭代期,使用一个 5倍于未经处理的 MNIST 训练数据的数据集。在本书前面,我们主要用原始训练数据训练了30个迭代期。结合因素(3)和(4),仿佛我们训练了比以前 30倍长的时间。

也已经使用了其它主意:利用充分大的数据集(为了避免过度拟合);使用正确的代价函数(为了避免学习减速);使用好的权重初始化(也是为了避免因为神经元饱和引起的学习减速);以算法形式扩展训练数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值