# 十三、卷积神经网络

​尽管 IBM 的深蓝超级计算机在1996年击败了国际象棋世界冠军 Garry Kasparvo，直到近几年计算机都不能可靠地完成一些看起来较为复杂的任务，比如判别照片中是否有狗以及识别语音。为什么这些任务对于人类而言如此简单？答案在于感知主要发生在我们意识领域之外，在我们大脑中的专门视觉，听觉和其他感官模块内。当感官信息达到我们的意识时，它已经被装饰了高级特征;例如，当你看着一只可爱的小狗的照片时，你不能选择不看这只小狗，或不注意它的可爱。你也不能解释你如何认出这是一只可爱的小狗，这对你来说很明显。因此，我们不能相信我们的主观经验：感知并不是微不足道的，理解它我们必须看看感官模块是如何工作的。

​卷积神经网络（CNN）是从大脑视觉皮层的研究中出现的，自 20 世纪 80 年代以来它们一直用于图像识别。在过去的几年里，由于计算能力的增加，可用训练数据的数量以及第 11 章介绍的训练深度网络的技巧，CNN 致力于在某些复杂的视觉任务中做出超出人类的表现。他们使图像搜索服务，自动驾驶汽车，视频自动分类系统等变得强大。此外，CNN 并不局限于视觉感知：它们在其他任务中也很成功，如语音识别或自然语言处理（NLP）; 然而，我们现在将专注于视觉应用。

​在本章中，我们将介绍 CNN 的来源，构建它们模块的外观以及如何使用 TensorFlow 实现它们。然后我们将介绍一些最好的 CNN 架构。

​

## 视觉皮层的结构

​David H.Hubel 和 Torsten Wiesel 在 1958 年和 1959 年对猫进行了一系列实验（以及几年后在猴子上的实验），对视觉皮层的结构提供了重要的见解（1981 年作者因此获得了诺贝尔生理和医学奖）。具体来说，他们发现视皮层中的许多神经元有一个小的局部感受野，这意味着它们只对位于视野中有限的一部分区域的视觉刺激起作用（见图 13-1，五个神经元的局部感受野由虚线圆圈表示）。不同神经元的感受野可能重叠，并且它们一起平铺了整个视野。此外，作者表明，一些神经元只对水平线方向的图像作出反应，而另一些神经元只对不同方向的线作出反应（两个神经元可能具有相同的感受野，但对不同方向的线作出反应）。他们还注意到一些神经元具有较大的感受野，并且它们对较复杂的模式作出反应，这些模式是较低层模式的组合。这些观察结果让我们想到：更高级别的神经元是基于相邻低级神经元的输出（在图 13-1 中，请注意，每个神经元只与来自前一层的少数神经元相连）。这个强大的结构能够检测视野中任何区域的各种复杂图案。



​这些对视觉皮层的研究启发了 1980 年推出的新认知机（neocognitron），后者逐渐演变为我们现在称之为卷积神经网络。一个重要的里程碑是 Yann LeCun，LéonBottou，Yoshua Bengio 和 Patrick Haffner 于 1998 年发表的一篇论文，该论文引入了着名的 LeNet-5 架构，广泛用于识别手写支票号码。这个架构有一些你已经知道的构建块，比如完全连接层和 Sigmoid 激活函数，但是它还引入了两个新的构建块：卷积层和池化层。现在我们来看看他们。



## 卷积层

​CNN 最重要的组成部分是卷积层：第一卷积层中的神经元不是连接到输入图像中的每一个像素（就像它们在前面的章节中那样），而是仅仅连接到它们的局部感受野中的像素（参见图 13-2）。 进而，第二卷积层中的每个神经元只与位于第一层中的小矩形内的神经元连接。 这种架构允许网络专注于第一隐藏层中的低级特征，然后将其组装成下一隐藏层中的高级特征，等等。 这种层次结构在现实世界的图像中是很常见的，这也是 CNN 在图像识别方面效果很好的原因之一。

![](../images/chapter_13/13-2.png)

​**到目前为止，我们所看到的所有多层神经网络都有由一长串神经元组成的层，在输入到神经网络之前我们必须将输入图像压缩成 1D。 现在，每个图层都以 2D 表示，这使得神经元与其相应的输入进行匹配变得更加容易。**

​位于给定层的第`i`行第`j`列的神经元连接到位于前一层中的神经元的输出的第i行到第 ![](../images/chapter_13/o-13-1.png) 行，第`j`列到第 ![](../images/chapter_13/o-13-2.png) 列。`fh`和`fw`是局部感受野的高度和宽度（见图 13-3）。 为了使图层具有与前一图层相同的高度和宽度，通常在输入周围添加零，如图所示。 这被称为零填充.

![](../images/chapter_13/13-3.png)

​如图 13-4 所示，通过将局部感受野隔开，还可以将较大的输入层连接到更小的层。 两个连续的感受野之间的距离被称为步幅。 在图中，一个`5×7`的输入层（加零填充）连接到一个`3×4`层，使用`3×3`的卷积核和一个步幅为 2（在这个例子中，步幅在两个方向是相同的，但是它并不一定总是如此）。 位于上层第`i`行第`j`列的神经元与位于前一层中的神经元的输出连接的第 ![](../images/chapter_13/o-13-4.png) 至 ![](../images/chapter_13/o-13-5.png) 行，第 ![](../images/chapter_13/o-13-6.png) 列， `Sh`和`Sw`是垂直和水平的步幅。

![](../images/chapter_13/13-4.png)



## 卷积核/过滤器

![](../images/chapter_13/13-5.png)

​神经元的权重可以表示为局部感受野大小的小图像。 例如，图 13-5 显示了两个可能的权重集，称为过滤器（或卷积核）。第一个表示为中间有一条垂直的白线的黑色正方形（除了中间一列外，这是一个充满 0 的`7×7`矩阵，除了中央垂直线是 1）。 使用这些权重的神经元会忽略除了中央垂直线以外感受野的一切（因为除位于中央垂直线以外，所有的输入都将乘 0）。第二个卷积核是一个黑色的正方形，中间有一条水平的白线。 再一次，使用这些权重的神经元将忽略除了中心水平线之外的局部感受野中的一切。

​现在，如果一个图层中的所有神经元都使用相同的垂直线卷积核（以及相同的偏置项），并且将网络输入到图 13-5（底部图像）中所示的输入图像，则该图层将输出左上图像。 请注意，垂直的白线得到增强，其余的变得模糊。 类似地，如果所有的神经元都使用水平线卷积核，右上角的图像就是你所得到的。 注意到水平的白线得到增强，其余的则被模糊了。因此，使用相同卷积和的一个充满神经元的图层将为您提供一个特征映射，该特征映射突出显示图像中与卷积和最相似的区域。 在训练过程中，CNN 为其任务找到最有用的卷积和，并学习将它们组合成更复杂的模式（例如，交叉是图像中垂直卷积和和水平卷积和都激活的区域）。



## 叠加的多个特征映射

![](../images/chapter_13/13-6.png)

​到目前为止，为了简单起见，我们已经将每个卷积层表示为一个薄的二维层，但是实际上它是由几个相同大小的特征映射组成的，所以使用3D图表示其会更加准确（见图 13-6）。 在一个特征映射中，所有神经元共享相同的参数（权重和偏置，权值共享），但是不同的特征映射可能具有不同的参数。 神经元的感受野与前面描述的相同，但是它延伸到所有先前的层的特征映射。 简而言之，卷积层同时对其输入应用多个卷积核，使其能够检测输入中的任何位置的多个特征。

​**事实上，特征地图中的所有神经元共享相同的参数会显着减少模型中的参数数量，但最重要的是，一旦 CNN 学会识别一个位置的模式，就可以在任何其他位置识别它。 相比之下，一旦一个常规 DNN 学会识别一个位置的模式，它只能在该特定位置识别它。**

​而且，输入图像也由多个子图层组成：每个颜色通道一个。 通常有三种：红色，绿色和蓝色（RGB）。 灰度图像只有一个通道，但是一些图像可能更多 - 例如捕捉额外光频（如红外线）的卫星图像。

​具体地，位于给定卷积层L中的特征映射k的i行，j列中的神经元连接到前一层（L-1）位于 ![](../images/chapter_13/o-13-7.png) 行，![](../images/chapter_13/o-13-8.png) 列的神经元的输出。请注意，位于同一行第`i`列和第`j`列但位于不同特征映射中的所有神经元都连接到上一层中完全相同神经元的输出。

​公式 13-1 在一个总结前面解释的大的数学公式：它展示了如何计算卷积层中给定神经元的输出。它是计算所有投入的加权总并且加上偏置。

![](../images/chapter_13/e-13-1.png)

* ![](../images/chapter_13/o-13-9.png) 是卷积层（L层）特征映射k中位于第`i`行第`j`列的神经元的输出.

* 如前所述，![](../images/chapter_13/o-13-10.png) 和 ![](../images/chapter_13/o-13-11.png) 是垂直和水平的步幅，![](../images/chapter_13/o-13-12.png) 和 ![](../images/chapter_13/o-13-13.png) 是感受野的高度和宽度，![](../images/chapter_13/o-13-14.png) 是前一层（第`l - 1`层）的特征映射的数量。

* ![](../images/chapter_13/o-13-15.png) 是位于层`L-1`，`i'`行，`j'`列，特征映射`k'`（或者如果前一层是输入层的通道`k'`）的神经元的输出。

* ![](../images/chapter_13/o-13-16.png) 是特征映射`k`的偏置项（在`L`层中）。您可以将其视为调整特征映射`k`的整体亮度的旋钮。

* ![](../images/chapter_13/o-13-17.png) 是层`L`的特征映射`k`中的任何神经元与位于行`u`，列`v`（相对于神经元的感受野）的输入之间的连接权重，以及特征映射k'。



## TensorFlow 实现

​在 Tensorflow 中，每个输入图像的通常被表示为三维张量 ![](../images/chapter_13/o-13-18.png)。 一个小批次被表示为四维张量 ![](../images/chapter_13/o-13-19.png)。 卷积层的权重被表示为四维张量 ![](../images/chapter_13/o-13-20.png)。 卷积层的偏差项简单地表示为一维形状的张量 ![](../images/chapter_13/o-13-21.png)。我们来看一个简单的例子。 下面的代码使用 **Scikit-Learn** 的`load_sample_images()`（加载两个彩色图像，一个中国庙宇，另一个是一朵花）加载两个样本图像。 然后创建两个`7×7`的卷积核（一个中间是垂直的白线，另一个是水平的白线），并将他们应用到两张图形中,使用 TensorFlow 的`conv2d()`函数构建的卷积图层（使用零填充且步幅为 2）。 最后，绘制其中一个结果特征映射（类似于图 13-5 中的右上图）。

```python
from sklearn.datasets import load_sample_image
import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf

if __name__ == '__main__':

    # Load sample images
    china = load_sample_image("china.jpg")
    flower = load_sample_image("flower.jpg")
    dataset = np.array([china, flower], dtype=np.float32)
    batch_size, height, width, channels = dataset.shape

    # Create 2 filters
    filters = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
    filters[:, 3, :, 0] = 1  # vertical line
    filters[3, :, :, 1] = 1  # horizontal line

    # Create a graph with input X plus a convolutional layer applying the 2 filters
    X = tf.placeholder(tf.float32, shape=(None, height, width, channels))
    convolution = tf.nn.conv2d(X, filters, strides=[1,2,2,1], padding="SAME")

    with tf.Session() as sess:
        output = sess.run(convolution, feed_dict={X: dataset})

    plt.imshow(output[0, :, :, 1], cmap="gray") # plot 1st image's 2nd feature map
    plt.show()

```

大部分代码是不言而喻的，但`conv2d()`这一行值得解释一下：

*   `X`是输入小批次（4D 张量，如前所述）
*   卷积核是应用的一组卷积核（也是一个 4D 张量，如前所述）。
*   步幅是一个四元素的一维数组，其中两个中间的值是垂直和水平的步幅（`sh`和`sw`）。 第一个和最后一个元素现在必须等于 1。他们可能有一天会被用来指定批量步长（跳过一些实例）和频道步幅（跳过上一层的特征映射或通道）。
*   `padding`必须是`"VALID"`或`"SAME"`：

    - 如果设置为`"VALID"`，卷积层不使用零填充，并且可能会忽略输入图像底部和右侧的某些行和列，具体取决于步幅，如图 13-7 所示（为简单起见， 这里只显示水平尺寸，当然，垂直尺寸也适用相同的逻辑）
    - 如果设置为`"SAME"`，则卷积层在必要时使用零填充。 在这种情况下，输出神经元的数量等于输入神经元的数量除以该步幅，向上舍入（在这个例子中，`ceil(13/5)= 3`）。 然后在输入周围尽可能均匀地添加零。

![](../images/chapter_13/13-7.png)

不幸的是，卷积图层有很多超参数：你必须选择卷积核的数量，高度和宽度，步幅和填充类型。 与往常一样，您可以使用交叉验证来查找正确的超参数值，但这非常耗时。 稍后我们将讨论常见的 CNN 体系结构，以便让您了解超参数值在实践中的最佳工作方式。



## 内存需求

​CNN 的另一个问题是卷积层需要大量的 RAM，特别是在训练期间，因为反向传播需要在正向传递期间计算的所有中间值。

​例如，考虑具有`5×5`卷积核的卷积层，输出 200 个尺寸为`150×100`的特征映射，步长为 1，使用`SAME`填充。 如果输入是`150×100`RGB 图像（三个通道），则参数的数量是`(5×5×3 + 1)×200 = 15,200`（`+1`对应于偏置项），这跟全连接层比较是相当小的。（具有`150×100`神经元的全连接层，每个连接到所有`150×100×3`输入，将具有`150 ^ 2×100 ^ 2×3 = 675,000,000`个参数！）然而，200 个特征映射中的每一个包含`150×100`个神经元，并且这些神经元中的每一个都需要计算其`5×5×3 = 75`个输入的权重和：总共 2.25 亿次浮点乘法。不像全连接层那么糟糕，但仍然是计算密集型的。 而且，如果使用 32 位浮点数来表示特征映射，则卷积层的输出将占用 RAM 的`200×150×100×32 = 9600万`位（大约 11.4MB）。 这只是一个例子！ 如果训练批次包含 100 个实例，则该层将占用超过 1 GB 的 RAM！

​在推理过程中（即对新实例进行预测时），一旦下一层计算完毕，一层所占用的 RAM 就可以被释放，因此只需要两个连续层所需的 RAM 数量。 但是在训练期间，在正向传递期间计算的所有内容都需要被保留用于反向传递，所以所需的 RAM 量（至少）是所有层所需的 RAM 总量。

​**如果由于内存不足错误导致训练崩溃，则可以尝试减少小批量大小。 或者，您可以尝试使用步幅降低维度，或者删除几个图层。 或者你可以尝试使用 16 位浮点数而不是 32 位浮点数。 或者你可以在多个设备上分发 CNN。**



## 池化层

​一旦你理解了卷积层是如何工作的，池化层很容易掌握。 他们的目标是对输入图像进行二次抽样（即收缩）以减少计算负担，内存使用量和参数数量（从而限制过度拟合的风险）。 减少输入图像的大小也使得神经网络容忍一点点的图像变换（位置不变）。

​就像在卷积图层中一样，池化层中的每个神经元都连接到前一层中有限数量的神经元的输出，位于一个小的矩形感受野内。 您必须像以前一样定义其大小，跨度和填充类型。 但是，汇集的神经元没有权重; 它所做的只是使用聚合函数（如最大值或平均值）来聚合输入。 图 13-8 显示了最大池层，这是最常见的池化类型。 在这个例子中，我们使用一个2×2的核，步幅为 2，没有填充。 请注意，只有每个核中的最大输入值才会进入下一层。 其他输入被丢弃。

![](../images/chapter_13/13-8.png)       

​这显然是一个非常具有破坏性的层：即使只有一个`2×2`的核和 2 的步幅，输出在两个方向上都会减小两倍（所以它的面积将减少四倍），一下减少了 75% 的输入值

​池化层通常独立于每个输入通道工作，因此输出深度与输入深度相同。 接下来可以看到，在这种情况下，图像的空间维度（高度和宽度）保持不变，但是通道数目可以减少。

​在 TensorFlow 中实现一个最大池层是非常容易的。 以下代码使用`2×2`核创建最大池化层，步幅为2，没有填充，然后将其应用于数据集中的所有图像：

```python
import numpy as np
from sklearn.datasets import load_sample_image
import tensorflow as tf
import matplotlib.pyplot as plt

china = load_sample_image("china.jpg")
flower = load_sample_image("flower.jpg")

dataset = np.array([china, flower], dtype=np.float32)
batch_size, height, width, channels = dataset.shape

# Create 2 filters
filters = np.zeros(shape=(7, 7, channels, 2), dtype=np.float32)
filters[:, 3, :, 0] = 1  # vertical line
filters[3, :, :, 1] = 1  # horizontal line

X = tf.placeholder(tf.float32, shape=(None, height, width, channels))
max_pool = tf.nn.max_pool(X, ksize=[1,2,2,1], strides=[1,2,2,1],padding="VALID")

with tf.Session() as sess:
    output = sess.run(max_pool, feed_dict={X: dataset})

plt.imshow(output[0].astype(np.uint8))  # plot the output for the 1st image
plt.show()
```

​`ksize`参数包含沿输入张量的所有四维的核形状：`[min-batch, height, width, channels]`。 TensorFlow 目前不支持在多个实例上合并，因此`ksize`的第一个元素必须等于 1。此外，它不支持在空间维度（高度和宽度）和深度维度上合并，因此`ksize[1]`和`ksize[2]`都必须等于 1，否则`ksize[3]`必须等于 1。

​要创建一个平均池化层，只需使用`avg_pool()`函数而不是`max_pool()`。

​现在你知道所有的构建模块来创建一个卷积神经网络。 我们来看看如何组装它们。



## CNN 架构

​典型的 CNN 体系结构有一些卷积层（每一个通常跟着一个 ReLU 层），然后是一个池化层，然后是另外几个卷积层（+ ReLU），然后是另一个池化层，等等。 随着网络的进展，图像变得越来越小，但是由于卷积层的缘故，图像通常也会越来越深（即更多的特征映射）（见图 13-9）。 在堆栈的顶部，添加由几个全连接层（+ ReLU）组成的常规前馈神经网络，并且最终层输出预测（例如，输出估计类别概率的 softmax 层）。

![](../images/chapter_13/13-9.png)

​**一个常见的错误是使用太大的卷积核。 通常可以通过将两个`3×3`内核堆叠在一起来获得与`9×9`内核相同的效果，计算量更少。**

​多年来，这种基础架构的变体已经被开发出来，导致了该领域的惊人进步。 这种进步的一个很好的衡量标准是比赛中的错误率，比如 ILSVRC ImageNet 的挑战。 在这个比赛中，图像分类的五大误差率在五年内从 26% 下降到仅仅 3% 左右。 前五位错误率是系统前5位预测未包含正确答案的测试图像的数量。 图像很大（256 像素），有 1000 个类，其中一些非常微妙（尝试区分 120 个狗的品种）。 查看获奖作品的演变是了解 CNN 如何工作的好方法。

​我们先来看看经典的 LeNet-5 架构（1998 年），然后是 ILSVRC 挑战赛的三名获胜者 AlexNet（2012），GoogLeNet（2014）和 ResNet（2015）。

​其他视觉任务在其他视觉任务中，如物体检测和定位以及图像分割，也取得了惊人的进展。 在物体检测和定位中，神经网络通常输出图像中各种物体周围的一系列边界框。 例如，参 见Maxine Oquab 等人的 2015 年论文，该论文为每个客体类别输出热图，或者 Russell Stewart 等人的 2015 年论文，该论文结合使用 CNN 来检测人脸，并使用递归神经网络来输出 围绕它们的一系列边界框。 在图像分割中，网络输出图像（通常与输入大小相同），其中每个像素指示相应输入像素所属的对象的类别。 例如，查看 Evan Shelhamer 等人的 2016 年论文。



## LeNet-5

​LeNet-5 架构也许是最广为人知的 CNN 架构。 如前所述，它是由 Yann LeCun 于 1998 年创建的，广泛用于手写数字识别（MNIST）。 它由表 13-1 所示的层组成。

![](../images/chapter_13/t-13-1.png)

有一些额外的细节要注意：

*   MNIST 图像是`28×28`像素，但是它们被零填充到`32×32`像素，并且在被输入到网络之前被归一化。 网络的其余部分不使用任何填充，这就是为什么随着图像在网络中的进展，大小不断缩小。
*   平均池化层比平常稍微复杂一些：每个神经元计算输入的平均值，然后将结果乘以一个可学习的系数（每个特征映射一个），并添加一个可学习的偏差项（每个特征映射一个），然后最后应用激活函数。
*   C3 图中的大多数神经元仅在三个或四个 S2 图（而不是全部六个 S2 图）中连接到神经元。有关详细信息，请参阅原始论文中的表 1。
*   输出层有点特殊：每个神经元不是计算输入和权向量的点积，而是输出其输入向量和其权向量之间的欧几里德距离的平方。 每个输出测量图像属于特定数字类别的多少。 交叉熵损失函数现在是首选，因为它更多地惩罚不好的预测，产生更大的梯度，从而更快地收敛。

Yann LeCun 的网站（“LENET”部分）展示了 LeNet-5 分类数字的很好的演示。



## AlexNet

​AlexNet CNN 架构赢得了 2012 年的 ImageNet ILSVRC 挑战赛：它达到了 17% 的 top-5 的错误率，而第二名错误率只有 26%！ 它由 Alex Krizhevsky（因此而得名），Ilya Sutskever 和 Geoffrey Hinton 开发。 它与 LeNet-5 非常相似，只是更大更深，它是第一个将卷积层直接堆叠在一起，而不是在每个卷积层顶部堆叠一个池化层。 表 13-2 介绍了这种架构。

![](../images/chapter_13/t-13-2.png)

​为了减少过拟合，作者使用了前面章节中讨论的两种正则化技术：首先他们在训练期间将丢失率（dropout 率为 50%）应用于层 F8 和 F9 的输出。其次，他们通过随机对训练图像进行各种偏移，水平翻转和改变照明条件来进行数据增强。

​AlexNet 还在层 C1 和 C3 的 ReLU 步骤之后立即使用竞争标准化步骤，称为局部响应标准化（local response normalization）。 这种标准化形式使得在相同的位置的神经元被最强烈的激活但是在相邻的特征映射中抑制神经元（在生物神经元中观察到了这种竞争激活）。 这鼓励不同的特征映射特殊化，迫使它们分开，并让他们探索更广泛的特征，最终提升泛化能力。 公式 13-2 显示了如何应用 LRN。

![](../images/chapter_13/e-13-2.png)

*   ![](../images/chapter_13/o-13-22.png) 是位于特征映射`i`的神经元的标准化输出，在某行`u`和列`v`（注意，在这个等式中我们只考虑位于这个行和列的神经元，所以`u`和`v`没有显示）。
*   ![](../images/chapter_13/o-13-23.png) 是在 ReLU 步骤之后，但在归一化之前的那个神经元的激活。
*   `k`，`α`，`β`和`r`是超参数。 `k`称为偏置，`r`称为深度半径。
*   ![](../images/chapter_13/o-13-24.png) 是特征映射的数量。

​例如，如果`r = 2`且神经元具有强激活，则将抑制位于其上下的特征映射中的神经元的激活。

​在 AlexNet 中，超参数设置如下：`r = 2`，`α= 0.00002`，`β= 0.75`，`k = 1`。这个步骤可以使用 TensorFlow 的`local_response_normalization()`操作来实现。

​AlexNet 的一个名为 ZF Net 的变体由 Matthew Zeiler 和 Rob Fergus 开发，赢得了 2013 年 ILSVRC 的挑战。 它基本上是 AlexNet 的一些调整的超参数（特征映射的数量，内核大小，步幅等）。



## GoogLeNet

​GoogLeNet 架构是由 Christian Szegedy 等人开发的。 来自 Google Research，通过低于 7% 的 top-5 错误率，赢得了 ILSVRC 2014 的挑战赛。 这个伟大的表现很大程度上因为它比以前的 CNN 网络更深（见图 13-11）。 这是通过称为初始模块（inception modules）的子网络实现的，这使得 GoogLeNet 比以前的架构更有效地使用参数：实际上，GoogLeNet 的参数比 AlexNet 少了 10 倍（约 600 万而不是 6000 万）。

![](../images/chapter_13/13-10.png)

​初始模块的架构如图 13-10 所示。 符号`3×3 + 2(S)`表示该层使用`3×3`内核，步幅 2 和`SAME`填充。 输入信号首先被复制并馈送到四个不同的层。 所有卷积层都使用 ReLU 激活功能。 请注意，第二组卷积层使用不同的内核大小（`1×1`，`3×3`和`5×5`），允许它们以不同的比例捕获图案。 还要注意，每一层都使用了跨度为1和SAME填充的（即使是最大的池化层），所以它们的输出全都具有与其输入相同的高度和宽度。这使得将所有输出在最后的深度连接层（depth concat layer）上沿着深度方向堆叠成为可能（即，堆叠来自所有四个顶部卷积层的特征映射）。 这个连接层可以在 TensorFlow 中使用`concat()`操作实现，其中`axis = 3`（轴 3 是深度）。

​您可能想知道为什么初始模块具有`1×1`内核的卷积层。 当然这些图层不能捕获任何功能，因为他们一次只能看一个像素？ 实际上，这些层次有两个目的：

​首先，它们被配置为输出比输入少得多的特征映射，所以它们作为瓶颈层，意味着它们降低了维度。 在`3×3`和`5×5`卷积之前，这是特别有用的，因为这些在计算上是非常耗费内存的层。

​其次，每一个卷积层对（`[1 × 1, 3 × 3]`和`[1 × 1, 5 × 5]`表现地像一个强大的卷积层，可以捕捉到更多的复杂的模式。事实上，这一对卷积层不是在图像上扫过一个简单的线性分类器（就像单个卷积层一样），而是在图像上扫描一个双层神经网络。

​简而言之，您可以将整个初始模块视为类固醇卷积层，能够输出捕捉各种尺度复杂模式的特征映射。

​**每个卷积层的卷积核的数量是一个超参数。 不幸的是，这意味着你有六个超参数来调整你添加的每个初始层。**

![](../images/chapter_13/13-11.png)

​现在让我们来看看 GoogLeNet CNN 的架构（见图 13-11）。 它非常深，我们不得不将它分成三列，但是 GoogLeNet 实际上是一列，包括九个初始模块（带有旋转顶端的框），每个模块实际上包含三层。每个卷积层和池化层输出的特征映射的数量显示在内核大小前。 初始模块中的六个数字表示模块中每个卷积层输出的特征映射的数量（与图 13-10 中的顺序相同）。 请注意，所有的卷积层都使用 ReLU 激活函数。

让我们来过一遍这个网络：

*   前两层将图像的高度和宽度除以 4（使其面积除以 16），以减少计算负担。
*   然后，局部响应标准化层确保前面的层学习各种各样的功能（如前所述）
*   接下来是两个卷积层，其中第一个像瓶颈层一样。 正如前面所解释的，你可以把这一对看作是一个单一的更智能的卷积层。
*   再次，局部响应标准化层确保了先前的层捕捉各种各样的模式。
*   接下来，最大池化层将图像高度和宽度减少 2，再次加快计算速度。
*   然后是九个初始模块的堆叠，与几个最大池层交织，以降低维度并加速网络。
*   接下来，平均池化层使用具有`VALID`填充的特征映射的大小的内核，输出`1×1`特征映射：这种令人惊讶的策略被称为全局平均池化。 它有效地强制以前的图层产生特征映射，这些特征映射实际上是每个目标类的置信图（因为其他类型的功能将被平均步骤破坏）。 这样在 CNN 的顶部就不必有有几个全连接层（如 AlexNet），大大减少了网络中的参数数量，并减少了了过度拟合的风险。
*   最后一层是不言自明的：正则化 drop out，然后是具有 softmax 激活函数的完全连接层来输出估计类的概率。

​这个图略有简化：原来的 GoogLeNet 架构还包括两个插在第三和第六个初始模块之上的辅助分类器。 它们都由一个平均池层，一个卷积层，两个全连接层和一个 softmax 激活层组成。 在训练期间，他们的损失（缩小了 70%）加在了整体损失上。 目标是解决消失梯度问题，正则化网络。 但是，结果显示其效果相对小。



## **ResNet**

​最后是，2015 年 ILSVRC 挑战赛的赢家 Kaiming He 等人开发的 Residual Network（或 ResNet），该网络的 top-5 误率低到惊人的 3.6%，它使用了一个非常深的 CNN，由 152 层组成。 能够训练如此深的网络的关键是使用跳过连接（skip connection，也称为快捷连接）：一个层的输入信号也被添加到位于下一层的输出。 让我们看看为什么这是有用的。

​当训练一个神经网络时，目标是使其模拟一个目标函数`h(x)`。 如果将输入x添加到网络的输出中（即添加跳过连接），那么网络将被迫模拟`f(x)= h(x) - x`而不是`h(x)`。 这被称为残留学习（见图 13-12）。

![](../images/chapter_13/13-12.png)

​当你初始化一个普通的神经网络时，它的权重接近于零，所以网络只输出接近零的值。 如果添加跳过连接，则生成的网络只输出其输入的副本; 换句话说，它最初对身份函数进行建模。 如果目标函数与身份函数非常接近（常常是这种情况），这将大大加快训练速度。

​而且，如果添加了许多跳转连接，即使几个层还没有开始学习，网络也可以开始进行（见图 13-13）。 由于跳过连接，信号可以很容易地通过整个网络。 深度剩余网络可以看作是一堆剩余单位，其中每个剩余单位是一个有跳过连接的小型神经网络。

![](../images/chapter_13/13-13.png)

​现在让我们看看 ResNet 的架构（见图 13-14）。 这实际上是令人惊讶的简单。 它的开始和结束与GoogLeNet完全一样（除了没有 dropout 层），而在两者之间只是一堆很简单的残余单位。 每个残差单元由两个卷积层组成，使用`3×3`的内核和保存空间维度（步幅 1，`SAME`填充），批量归一化（BN）和 ReLU 激活。

![](../images/chapter_13/13-14.png)

​需要注意的是特征映射的数量每隔几个残差单位会加倍，同时它们的高度和宽度减半（使用步幅 2 卷积层）。 发生这种情况时，输入不能直接添加到剩余单元的输出中，因为它们不具有相同的形状（例如，此问题影响图 13-14 中的虚线箭头表示的跳过连接）。 为了解决这个问题，输入通过一个`1×1`卷积层，步长2和正确数量的输出特征映射（见图 13-15）。

![](../images/chapter_13/13-15.png)

​ResNet-34 是具有 34 个层（仅计算卷积层和完全连接层）的 ResNet，包含 3 个剩余单元输出 64 个特征映射，4 个剩余单元输出 128 个特征映射，6 个剩余单元输出 256 个特征映射，3 个剩余单元输出 512 个特征映射。

​ResNet-152 更深，使用稍微不同的剩余单位。 他们使用三个卷积层，而不是两个 256 个特征映射的`3×3`的卷积层，它们使用三个卷积层：第一个卷积层只有 64 个特征映射（少 4 倍），这是一个瓶颈层（已经讨论过） ，然后是具有 64 个特征映射的`3×3`层，最后是具有 256 个特征映射（`4×64`）的另一个`1×1`卷积层，以恢复原始深度。ResNet-152 包含三个这样的剩余单位，输出 256 个特征映射，然后是 8 个剩余单位，输出 512 个特征映射，高达 36 个剩余单位，输出 1024 个特征映射，最后是 3 个剩余单位，输出 2048 个特征映射。

​正如你所看到的，这个领域正在迅速发展，每年都会有各种各样的架构出现。 一个明显的趋势是 CNN 越来越深入。 他们也越来越轻量，需要越来越少的参数。 目前，ResNet 架构既是最强大的，也是最简单的，所以它现在应该是你应该使用的，但是每年都要继续关注 ILSVRC 的挑战。 2016 年获奖者是来自中国的 Trimps-Soushen 团队，他们的出错率惊人的缩减到 2.99%。 为了达到这个目标，他们训练了以前模型的组合，并将它们合并为一个整体。 根据任务的不同，降低的错误率可能会或可能不值得额外的复杂性。

​还有其他一些架构可供您参考，特别是 VGGNet（2014 年 ILSVRC 挑战赛的亚军）和 Inception-v4（将 GooLeNet 和 ResNet 的思想融合在一起，实现了接近 3% 的 top-5 误差 ImageNet 分类率）。

​实施我们刚刚讨论的各种CNN架构真的没什么特别的。 我们之前看到如何构建所有的独立构建模块，所以现在您只需要组装它们来创建所需的构架。 我们将在即将开始的练习中构建 ResNet-34，您将在 Jupyter 笔记本中找到完整的工作代码。

## TensorFlow 卷积操作

TensorFlow 还提供了一些其他类型的卷积层：

*   `conv1d()`为 1D 输入创建一个卷积层。 例如，在自然语言处理中这是有用的，其中句子可以表示为一维单词阵列，并且接受场覆盖一些邻近单词。
*   `conv3d()`创建一个 3D 输入的卷积层，如 3D PET 扫描。
*   `atrous_conv2d()`创建了一个 atrous 卷积层（“àtrous”是法语“with holes”）。 这相当于使用具有通过插入行和列（即，孔）而扩大的卷积核的普通卷积层。 例如，等于`[[1,2,3]]`的`1×3`卷积核可以以4的扩张率扩张，导致扩张的卷积核`[[1,0,0,0,2,0,0,0,3]]`。 这使得卷积层在没有计算价格的情况下具有更大的局部感受野，并且不使用额外的参数。
*   `conv2d_transpose()`创建了一个转置卷积层，有时称为去卷积层，它对图像进行上采样（这个名称是非常具有误导性的，因为这个层并不执行去卷积，这是一个定义良好的数学运算（卷积的逆）） 。这是通过在输入之间插入零来实现的，所以你可以把它看作是一个使用分数步长的普通卷积层。例如，在图像分割中，上采样是有用的：在典型的CNN中，特征映射越来越小当通过网络时，所以如果你想输出一个与输入大小相同的图像，你需要一个上采样层。
*   `depthwise_conv2d()`创建一个深度卷积层，将每个卷积核独立应用于每个单独的输入通道。 因此，如果有`fn`卷积核和`fn'`输入通道，那么这将输出`fn×fn'`特征映射。
*   `separable_conv2d()`创建一个可分离的卷积层，首先像深度卷积层一样工作，然后将`1×1`卷积层应用于结果特征映射。 这使得可以将卷积核应用于任意的输入通道组。
