卷积神经网络实践
我们现在已经看到卷积神经网络背后的核心思想。让我们通过实现一些卷积网络,并应用它们到MNIST数字分类问题,来看它们实际上是怎样工作的。我们将用名为network3.py的程序来做这个,它是前面章节开发的network.py和network2.py程序的升级版本【还要注意network3.py包含了来自Theano库文档中卷积神经网络(特别是LeNet-5的实现),来自Misha Denil的dropout实现,还有来自Chris Olah】。如果你想跟着做,代码在GitHub上。注意我们在下一节过一遍network3.py的代码。本节我们将把network3.py作为构建卷积网络的库。
程序network.py和network2.py实现时使用了Python和矩阵库Numpy。这些程序是从最初的原理开始,并逐渐深入到反向传播,随机梯度下降等技术的细节。但现在我们了解了这些细节,对于network3.py我们将用一个叫做Theano【读 Theano: A CPU and GPU Math Expression Compiler in Python, 作者 James Bergstra, Olivier Breuleux, Frederic Bastien, Pascal Lamblin, Ravzan Pascanu, Guillaume Desjardins, Joseph Turian, David Warde-Farley, 和 Yoshua Bengio (2010). Theano也是流行神经网络库Pylearn2和Keras的基础。写作期间其他流行的神经网络库还包括Caffe和Torch。】的机器学习库。使用Theano可以很简单的为卷积神经网络实现反向传播,因为它自动计算了所有包含的映射。Theano也比我们之前代码快很多(我们的代码是为了更好理解,而不是追求速度),这使其训练更复杂的网络变得更加实际。特别是,Theano有一个显著的特性就是即可以跑在CPU上,如果可以还能跑在GPU上。在GPU上跑提供了本质上的加速,再度让训练更加复杂的网络变得实际。
如果你愿意跟随,那么你需要下载Theano在你的系统上跑了。按照项目首页上的说明,安装Theano。例子是跑在Theano 0.6上的【在我发布这一章时,Theano的当前版本已经改为0.7版本。我实际上已经重新在Theano 0.7下跑了下例子,并得到了与文本中报告极其相似的结果。】。一些运行在没有GPU的Mac OS X Yosemite上。一些运行在有NVIDIA GPU的Ubuntu 14.04上。一些实验是在两者上面都跑过。为了让network3.py能运行,你需要在network3.py的源代码中设置GPU标志位为True或False(根据实际情况)。除此之外,为了让Theano跑在GPU上,你可能需要这段说明。网络上也有很多教程,用Google百度一下很容易就能找到,这或许能帮到你。如果你手头上没有可用的GPU,你可以上Amazon Web Services看一下EC2 G2的现货。注意就算是用GPU,代码也需要跑一段时间。许多实验需要运行几分钟到数小时。用CPU跑最复杂的试验可能需要几天的时间。像前面的章节一样,我建议一边跑着代码,一边阅读,偶尔回头看看代码的输出。如果你用的是CPU,你可能希望减少一些太复杂的试验的训练代数,或干脆将它们整个停掉。
为了得到一个基准,我们开始先用一个只有单个隐含层的浅层网络,包含100个隐含神经元。我们将训练60代,使用的学习率为,最小批次大小为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.80%。这是test_data上的分类准确率,在训练代上用validation_data评估,得到的最好的分类准确率。评估测试准确时使用验证数据可以避免对测试数据过拟合(见之前关于验证数据使用的讨论)。后面将遵循这个原则。你的结果可能有些许不同,因为网络的权重和偏移量是随机初始化的【实事上,在这个试验里我实际上用这个架构分别运行了三次网络的训练。然后我报出了三次训练结果中最好的分类准确率。用多次运行减少了结果中的变化,当然在比较多个架构很有用,就像我们现在做的一样。后面我遵循了这样的流程,除非特别说明。实际上,它对所得到的结果几乎没有影响。】。
97.8%的分类准确率与前面第3章得到的98.04%的准确率很接近,用的是相似的网络架构和学习超参数。尤其是两者都是用了一个浅层网络,单上包含有100个隐含神经元的隐含层。都训练了60代,使用了最小批次大小为10,学习率为。
尽管这样,前面的网络还是有两处不同。首先,我们对之前网络做了正则化,来减少过拟合的影响。正则化当前网络提升准确率的效果有限,所以我们放到后面再考虑正则化的事情。其次,前面网络的最后一层用的sigmoid激活函数和交叉熵成本函数,当前网络最后是用了一个softmax层和对数似然成本函数。正如第3章解释的,这并不是一个很大的改变。我做这样的替换并没有深层次的原因——主要是因为softmax加对数似然成本在现代图像分类网络中更普通一些。
用一个更深层的网络架构能取得更好结果吗?
让我们开始在网络开头处的右面插入一个卷积层。我们将用的感受野,步幅长度为1,和20个特征映射。我们再插入了一个最大池化层,用的池化窗口来整合特征。所以整个网络架构看上去很像上一节讨论的架构,但多了一个额外的全连接层:
在这个架构里,我们可以把卷积和池化层当前是在从输入训练图像中学习局部的空间结构,后面的全连接层是通过整合整个图像的信息,学习更抽象的层次。这在卷积神经网络里是一个很普通的模式。
让我们训练一个这样的网络,看一下它是怎么运行的【这里我继续用为10最小批次大小。实事上,像我们之前讨论的,用大一些的最小批次可能会加速训练。继续使用相同的最小批次大小,主要是为了与前面章节中的实验保持一致。】:
>>> 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,将层分成指定的样子。
练习
- 如果省略全连通层,只有卷积池化层和softmax层的话,分类准确率会是什么样?包含全连通层有帮助吗?
我们可以改进98.78%的分类准确率吗?
让我们深度插入第二个卷积池化层。我们将在现有的卷积池化层和全连通隐含层中间挺进去。同样,我们将用的感受野和的池化区域。让我们看下当用和前面相似的超参数时会发生什么:
>>> 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%的分类准确率!
这一占就很特自然的提出两个问题。第一个问题是:应用的第二个卷积池化层意味着什么?实事上,你可以把第二个卷积池化层当成是输入了的“图像”,其“像素”表示原始输入图像里特定局部特征的存在(或缺失)。所以你可以把这一层当成是输入了原始输入图像的一个版本。这个版本是被抽象和压缩过的,但仍有大量空间结构,因此有理由去用第二个卷积池化层。
这是一个另人满意的观点,但引出了第二个问题。前面网络层的输出分别包含20个特征映射,所以就有个输入到第二卷积池化层,这就不是像第一卷积池化层那样的单个图像了。第二卷积池化层里的神经元如何去响应这么多个输入图像呢?实事上,我们允许这一层的每个神经元去学习它感受野里所有个输入神经元。更通俗地讲:第二卷积池化层里特征的检测是针对前面网络层所有特征的,但只在它们特定的感受野范围内来说【如果输入图像是彩色的,这个问题就会出现在第一层。那样的话对每一像素就要有3个输入特征,对应输入图像里的红,绿和蓝色通道。因此,我们允许特征检测访问所有颜色信息,但只允许在给定的感受野内访问】。
问题
- 使用tanh激活函数:我在本书之前多次提到过一个观点,tanh函数跟sigmoid函数相比可能是一个更好的激活函数。我们还从未在这些建议上采取过行动,因为我们已在sigmoid上取得了很多进展。但现在让我们用tanh来当我们的激活函数做一些试验。试着训练训练用tanh来激活卷积和全连通层的网络【注意你可以传递activationfn=tanh作为一个参数到ConvPoolLayer和FullyConnectedLayer类中】。开始时用和像sigmoid网络一样的超参数,但训练20代而不是60代。你的网络表现如何?如果你继续一直到60代呢?尝试为基于tanh和sigmoid网络绘制每一代的验证准确率,一直到60代。如果你的结果和我一直的话,你会发现tanh网络训练的会快一点,但最后的准确率很接近。你能解释下为什么tanh网络会训练的更快吗?你能通过改变像学习率或调节其他什么地方来让sigmoid网络达到这样的训练速度吗【你可能会在看到这个公式后找到一些灵感】?尝试在学习超参数或网络架构上来回看看,找找tanh比sgimoid优越的地方。注:这是一个开方式问题。就我个人而言,我没有发现在转换到tanh方面有什么优势,尽管我没有做过彻底的实验,也许你会找到方法。无论如何,我们马上将发现换成修正线性激活函数的优势,因此就不在tanh的使用上做深入讨论了。_
使用修正线性单元:我们这一点上开发的网络实际上是一篇1998年开创性论文介绍MNIST问题所用网络中的一个变体,这个网络就是LeNet-5。这为后面的试验和建立直观上的理解打下了很好的基础。尤其是我们有很多方式去改变网络,来改善我们的结果。
作为开始,我们将神经元的sigmoid激活函数替换成修正线性单元。就是说用这样一个激活函数。我们用的学习率训练代。我也发现用一些正则化参数为的l2正则也有点帮助:
>>> 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%的分类准确率。相对于sigmoid的结果(99.06)这是一个马马虎虎的提升。尽管这样,在我所有的试验中,我发现基于修正线性单元的网络一直都优于基于sigmoid激活函数的网络。对于这个问题改用修正线性单元似乎有一个真正的收益。
是什么让修正线性单元比sigmoid或tanh函数更好呢?目前,我们只有浅显的理解来回答这个问题。确实,修正线性单元是近几年才被广泛使用的。近来用它都是凭经验的:少数几个尝试用线性修正单元的人,通常都是基于预感或试探性的观点【一个普通的理由是不会在极限大的处饱和,这和sigmoid不一样,而这帮修正线性单元能继续顺利的学习。就目前来讲,这个观点还不错,但它很难成为一个详实的论证,更像是一个马马虎虎的故事。注意我们在第2章讨论过饱和的问题】。他们在分类基准数据集上得到了的良好结果,并且传授了他们的实践。在理想的情况下应该有一个理论指导我们哪个应用该用哪个激活函数。但目前这还离我们很远。如果未来有一个更好的选激活函数的方式而取得巨大的改善,我一点也不会惊讶。并且我还期望能在未来几十年里能开发出一个关于激活函数更强力的理论。今天我们仍然只能依靠基于经验的浅显了解。
扩充训练数据:另一种能改善结果的方式是通过算法来扩充训练数据。扩充训练数据的一种简单方式是对每个训练图像都平移一个像素,向上、向下、向左或向右一像素。我们可以在shell提示符上运行expand_mnist.py程序来做这件事情【expand_mnist.py的代码在这里】:
$ 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%的分类准确率。你看这微不足道的变化让分类准确率有了显著的提升。确实,根据我们之前的讨论,扩充数据算法的思想可以再发展一下。只是提醒你前面那些讨论一些结果的味道:2003年 Simard, Steinkraus 和 Platt【Best Practices for Convolutional Neural Networks Applied to Visual Document Analysis, 作者 Patrice Simard, Dave Steinkraus, 和 John Platt (2003).】提升它们MNIST性能到了99.6%,所用的网络与我们的非常相似,两个卷积池化层,跟一个有100个隐含神经元的全连通隐含层。他们的架构里有一点不同——例如,他们没有用修正线性单元的优势,他们 提升性能的关键就在于对训练数据的扩充。他们对MNIST训练图像做了旋转,变换和平移。他们还开发了一个“弹性畸变”的程序,一种模仿人类写字时手部肌肉随机颤抖的现象。通过整合所有这些程序,他们大幅增加了有效训练数据的大小,他们就是这样达到了99.6%的准确率。
问题
- 卷积层的思想是以一种不变的方式扫描图像。那么,这就很奇怪了,我们所做的只是对输入数据做了些变换,我们的网络就能学习得更多。你能解释为什么这很合理吗?
插入一个额外的全连通层:我们能做的更好吗?一种可能就是用和上面一样的架构,但扩充一下全连通层的大小。我度过300和1,000个神经元,得到的结果分别是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和1,000个神经元的全连通层跑相似的试验产出的结果是99.48%和99.47%。这令人鼓舞,但仍不足以说是真正的胜出。
这是怎么回事呢?是扩充或额外的全连通层真得对MNIST没有帮助?还是我们的网络有提升的空间,但学习的方法有问题呢?例如,可能我们需要更强的正则化技术来减少过拟合的倾向。一种可能是在第3章介绍过的dropout技术。回顾下dropout的基本思想就是在训练网络时随机的去掉个别激活值。这让模型对个别证据的丢失更加鲁棒,因此就不太可能去依赖于训练数据上的特殊性质。
>>> 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:dropout减少了过拟合,因此我们学习得更快了。
第二,全连通隐含层有1,000个神经元,而不是之前用的100个。当然,dropout训练时会有效地忽略掉大部分神经元,所以就需要扩充一些。实事上,我试过用300和1,000个隐含神经元来试验,然后1,000个隐含神经元的验证性能更好。
用一个集成网络:进一步提升性能的容易做法就是创建多个神经网络,然后让他们投票选出最好的分类。举例来说,假设我们用上面的方法训练了5个不同的神经网络,每个都达到了99.6%的准确率。尽管这些网络准确率很相似,但它们可能犯的错误不一样,因为有着不一样的随机初始化。貌似在5个网络中投票得出的分类要比其中任何一个网络都要好。
这听起来好得点令人难以置信,但这种类型的集成是神经网络和其他机器学习中的常见技巧。并且实事上它也产生了进一步的提升:我们最终到了99.67%的准确率。换句话说,我们的集成网络在分类10,000张测试图片时,只有33张是错的。
测试数据集中的部分错误显示在下面。右上的标签是正确的分类,对应MNIST的数据,同时右下的标签是我们集成网络的输出:
值得仔细研究下它们的细节。起先的两个数字,一个6和一个5,是我们集成网络真的分错了。尽管这样,这也是可以理解的错误,这方面人类会给出合理的答案。那个6真得看起来和0很像,那个5也和3很像。第三张图片,大概是8,实际上在我看来更像是一个9。所以这里我站在集成网络一边:我想它比原来画这个数字的无论何人做的更好。另一方面,第四张图片,是6,真得看上去是我们的网络分类分得太糟糕了。
诸如此类。大部分情况下,我们网络的选择看上是至少可信的,而且有时做的比原来写这个数字的人要好。总的来说,我们的网络提供了出色的性能,尤其你考虑下那些被正确分类的9,967张图片是它们没有见过的。在这种情况下,如果此少的错误看上去是完全能理解的。即使是再仔细的人类也会偶尔犯下错。所以我认为只有非常细心和有条理的人会做得更好。我们的网络已经接近了人类的性能。
为什么我们只对全连通层用dropout:如果你仔细看上面代码的话,你将注意到我们只对网络的全连通部分应用了dropout,没有对卷积层。原则上我们对卷积层做相似的操作。但实事上没有这个必要:卷积层对过拟合有相当大的内部阻力。原因在于共享权重意味着卷积filter被迫使去从整个图像上学习。这让它们不太可能去提取训练数据上的局部性质。因此就无需去应用其他诸如dropout的正则化。
进一步探索:进一步提升MNIST上的性能还是有可能的。Rodrigo Benenson已经做了一个信息汇总页,展示了近些年来的进展,还附有论文的连接地址。许多这些论文用的是和我们用过的深度卷积网络很相似的网络。如果仔细阅读这些论文,你会发现很多有意思的技术,可能会让你想去实现它们。如果你这样做了,那就应该从可以快速训练的简单网络开始实现,这样可以让你更快的理解正在做的事情。
多数情况下,我们不会去调查最近的成果。但我忍不住破次例。它是2010年 Cireșan, Meier, Gambardella, 和 Schmidhuber的一篇论文【Deep, Big, Simple Neural Nets Excel on Handwritten Digit Recognition, 作者 Dan Claudiu Cireșan, Ueli Meier, Luca Maria Gambardella, 和 Jürgen Schmidhuber (2010)。】。我喜欢这篇论文是因为它很简单。网络是一个多层网络,只用了全连通层(没有卷积)。他们最好网络的隐含层分别有2500、2000、1500、1000和500个神经元。他们用了和Simard等人相似的思想来扩充训练数据。除此之外,他们很少用其他技巧,包括没有卷积层:这是一个很普通很普通的网络,只要有耐心和足够的计算能力,可以在19世纪80年代(如果MNIST数据已经存在的话)就可以说被训练。他们达到了99.65%的准确率,和我们的差不多。其关键是用了一个非常大而深的网络,并用GPU对训练进行了加速。这让他们训练了很多代。他们还利用长时间的训练来逐渐缩小学习从到。尝试使用像他们这样的架构来匹配这些结果是一项有趣的练习。
我们为什么可以训练?在上一章我们了解到训练深层和多层的网络有根本上困难。尤其是看过梯度趋势根相当不稳定:我们从输出层向前面的层移动,梯度趋势要么消失(梯度消失问题)要么爆炸(梯度爆炸问题)。因为梯度是我们用来训练的信号,这就引起了麻烦。
我们怎样避免出现这样的结果?
当然,答案是我们并没有避免这些结果。相反,我们做了一点事情,让我们可以继续下去。特别是:(1)用卷积层极大的减小了这些层里参数的数量,让学习问题变得简单很多;(2)使用了更强力的正则化技术(尤其是dropout和卷积层)来减少过拟合,否则这在越复杂的网络里就越是个大问题;(3)用修正线性单元来替代sigmoid神经元来加速训练——经验上通常有3-5倍的提升。(4)应用GPU并做了长时间的训练。尤其是在最后一次试验中,我们用比原生MNIST训练数据大5倍的数据训练了40代。结合(3)和(4)点,好像我们比之前多训练了30倍的时长。
你的反应可能是“就这样?这就是训练深度网络要做的全部吗?那还大惊小怪些什么?”
当然,我们也用了些其他的想法:用充分大的数据集(来避免过拟合);用了正确的成本函数(来避免学习缓慢的问题);使用好的权重初始化(避免神经元饱和引起的学习缓慢问题);基于算法来扩充训练数据。这些或其他我们在之前章节讨论过的思想,大部分都在这一章中可以再用,只是没作评说而以。
话虽如此,但这确实是一套简单的思想。简单,却在实际使用时很给力。深度学习开始这得如此容易!
这些网络到底有多深?将卷积池化层当成是一层的话,我们最终的架构是有4个隐含层。这样的网络真得应该被叫称为一个深度网络吗?当然,4个隐含层要比我们之前学得浅层网络要多一些。其中大部分网络只有单个隐含层,或者偶尔有2个隐含层。另一方面,作为2015年顶尖的深度网络有时能达到几十个隐含层。我偶尔听过如果你没有用相同数量的隐含层,人们就用比你深的态度自居,那你就不是真正在做深度学习。我对此不以为然,部分是因为这让深度学习定义取决于最后的结果。深度学习真正的突破是认识到可以实践的超过1或2个隐含层的网络,它主导了直到20世纪中叶为主的工作。这确实是一个重大突破,开启了对表现更突出模型的探索。但除此之外,层的数量并不是主要基础性质。更确切地说,使用更深的网络是一种用来帮助实现其他目标——比如更好的分类准确率这样的工具。
关于过程的一些话:这一节我们顺利的从单个隐含层的浅层网络转移到了多层卷积网络。一切似乎都很容易!大部分我们的改变都取得了一些成绩。如果你开始试验,我敢保证事情并不总是这么顺利的。因为我呈现的是一个干净的叙述,省略了许多试验——包括许多失败的试验。这种干净的叙述将帮你理清基本思想。但它也传达了一种没多风险的印象。得到一个好的有效果的网络会经历许多磨炼、错误甚至偶尔的挫折。为了加速这个过程,你可能发现重温下第3章讨论的怎样挑选神经网络的超参数会很有帮助,也许还可以看看在这一节中所建议的扩展阅读。