# 《第五章》

#### 5.1二维卷积层

1. 二维互相关运算：

![二维互相关运算](https://zh.d2l.ai/_images/correlation.svg)

1. 卷积运算：与互相关运算类似。为了得到卷积运算的输出，我们只需将核数组左右翻转并上下翻转，再与输入数组做互相关运算。可见，卷积运算和互相关运算虽然类似，但如果它们使用相同的核数组，对于同一个输入，输出往往并不相同。
2. 特征图（feature map）：二维卷积层输出的二维数组可以看作输入在空间维度（宽和高）上某一级的表征。
3. x的感受野（receptive field）：影响元素x的前向计算的所有可能输入区域（可能大于输入的实际尺寸）。
4. 卷积神经网络：通过数据来学习卷积核
5. corr2d因为用了\[i,j]=导致自动求导失败，这是由于autograd目前还有局限性。在该类的`forward`函数里，将`corr2d`函数替换成`nd.Convolution`类使得自动求梯度变得可行。

**练习**

* 构造一个输入图像`X`，令它有水平方向的边缘。如何设计卷积核`K`来检测图像中水平边缘？如果是对角方向的边缘呢？

  答：检测水平边缘的核函数应该是\[\[1],\[-1]]；对角方向的核函数\[1, -1]或\[\[1],\[-1]]或\[\[1, 0],\[-1, 0]]；

  对角方向的边缘监测方法有按水平的，按垂直的，或者都要。根据不同的方法，宽高会发生相应变化。
* 试着对我们自己构造的`Conv2D`类进行自动求梯度，会有什么样的错误信息？在该类的`forward`函数里，将`corr2d`函数替换成`nd.Convolution`类使得自动求梯度变得可行。

  答：infershape\[attrs.op]\(attrs, \&in\_shapes, \&out\_shapes)

  corr2d因为用了\[i,j]=导致自动求导失败，这是由于autograd目前还有局限性。改成 nd.Convolution 会没问题，因为它手动实现了backward函数。
* 如何通过变化输入和核数组将互相关运算表示成一个矩阵乘法？

  答：卷积变成矩阵乘法，从乘法和加法的运算次数上看，两者没什么差别，但是转化成矩阵后，运算时需要的数据被存在连续的内存上，这样访问速度大大提升。

  方法参考如下：<https://www.cnblogs.com/marsggbo/p/12074297.html>
* 如何构造一个全连接层来进行物体边缘检测？

  答：全连接层是指使用nn.Dense？

#### 5.2填充和步幅

$$
若输入矩阵:n\_h\times n\_w\ 且卷积核:k\_h\times k\_w\ 则输出形状:(n\_h-k\_h+1) \times (n\_w-k\_w+1)
$$

填充（padding）是指在输入高和宽的两侧填充元素（通常是0元素）。

![在输入的高和宽两侧分别填充了0元素的二维互相关计算](https://zh.d2l.ai/_images/conv_pad.svg)

$$
若高填充p\_h行，宽填充p\_w行，则输出形状(n\_h-k\_h+p\_h+1)\times(n\_w-k\_w+p\_w+1)\ 可以设置p\_h=k\_h-1,p\_w=k\_w-1使得输入和输出形状一致。
$$

步幅（stride）：我们将每次滑动的行数和列数。输出第一列第二个元素时，卷积窗口向下滑动了3行，而在输出第一行第二个元素时卷积窗口向右滑动了2列。当卷积窗口在输入上再向右滑动2列时，由于输入元素无法填满窗口，无结果输出。

![高和宽上步幅分别为3和2的二维互相关运算](https://zh.d2l.ai/_images/conv_stride.svg)

$$
当高上步幅为s\_h ，宽上步幅为s\_w 时，输出形状为:\ \lfloor(n\_h-k\_h+p\_h+s\_h)/s\_h\rfloor \times \lfloor(n\_w-k\_w+p\_w+s\_w)/s\_w\rfloor\如果设置 p\_h=k\_h−1 和 p\_w=k\_w−1 ，那么输出形状将简化为 :\⌊(n\_h+s\_h−1)/s\_h⌋×⌊(n\_w+s\_w−1)/s\_w⌋ \ 更进一步，如果输入的高和宽能分别被高和宽上的步幅整除，那么输出形状将是 (n\_h/s\_h)×(n\_w/s\_w) 。
$$

**练习**

* 对本节最后一个例子通过形状计算公式来计算输出形状，看看是否和实验结果一致。

  一致，分别是8/3和8/4的下取整。
* 在本节实验中，试一试其他的填充和步幅组合。

#### 5.3多输入和多输出通道

通道（channel）维：例如，彩色图像在高和宽2个维度外还有RGB（红、绿、蓝）3个颜色通道。称其通道维为3.

![含2个输入通道的互相关计算](https://zh.d2l.ai/_images/conv_multi_in.svg)

多输出通道：每个输出通道上的结果由卷积核在**该输出通道上的核数组**与**整个输入数组**计算而来。

卷积窗口形状为1×1（k\_h=k\_w=1）的多通道卷积层。

假设我们将通道维当作特征维，将高和宽维度上的元素当成数据样本，那么1×1卷积层的作用与全连接层等价。

**练习**

* 假设输入形状为ci×h×w，且使用形状为co×ci×kh×kw、填充为(ph,pw)、步幅为(sh,sw)的卷积核。那么这个卷积层的前向计算分别需要多少次乘法和加法？

  答：

  输出形状为：

  $$
  c\_o\times\lfloor(n\_h-k\_h+p\_h+s\_h)/s\_h\rfloor \times \lfloor(n\_w-k\_w+p\_w+s\_w)/s\_w\rfloor
  $$

  乘法次数：

  $$
  k\_h\times k\_w \times c\_o\times\lfloor(n\_h-k\_h+p\_h+s\_h)/s\_h\rfloor \times \lfloor(n\_w-k\_w+p\_w+s\_w)/s\_w\rfloor
  $$

  加法次数：

  $$
  \[(k\_h\times k\_w)个数连加]\times(c\_i个数连加)\times\lfloor(n\_h-k\_h+p\_h+s\_h)/s\_h\rfloor \times \lfloor(n\_w-k\_w+p\_w+s\_w)/s\_w\rfloor
  $$
* 翻倍输入通道数ci和输出通道数co会增加多少倍计算？翻倍填充呢？

  答：通过上述公式可以看出，翻倍导致增加的计算程度大致为：c\_o > c\_i > p\_h(p\_w)
* 如果卷积核的高和宽kh=kw=1，能减少多少计算？

  答：乘法降低至1/(k\_h乘k\_w)，加法降低至1/(k\_w\*k\_h-1)
* 本节最后一个例子中的变量`Y1`和`Y2`完全一致吗？原因是什么？

  答：不完全一致，一个是通过矩阵乘法的方式、一个是通过互相关计算。某个地方的精度不一样，比如说没有批量求和。
* 当卷积窗口不为1×1时，如何用矩阵乘法实现卷积计算？

  答：将卷积窗口reshape为行向量或者列向量。

#### 5.4池化层

池化层：池化层每次对输入数据的一个固定形状窗口（又称池化窗口）中的元素计算输出。池化层直接计算池化窗口内元素的最大值或者平均值。该运算也分别叫做最大池化或平均池化。

![池化窗口形状为\ :math:2\times 2\ 的最大池化](https://zh.d2l.ai/_images/pooling.svg)

池化层的输出通道数与输入通道数相等。

```python
pool2d  = nn.MaxPool2D((2, 3), padding=(1, 2), strides=(2, 3)) # 二维最大池化层函数
pool2d(X) # 调用
```

池化层的一个主要作用是缓解卷积层对位置的过度敏感性。（把数组按局部矩阵最大值或者局部矩阵平均值缩小）

* 分析池化层的计算复杂度。假设输入形状为c×h×w，我们使用形状为ph×pw的池化窗口，而且使用(ph,pw)填充和(sh,sw)步幅。这个池化层的前向计算复杂度有多大？

  答：时间复杂度为

  $$
  c×⌊(h−k\_h+p\_h+s\_h)/s\_h⌋×⌊(w−k\_w+p\_w+s\_w)/s\_w⌋×(max的时间)
  $$
* 想一想，最大池化层和平均池化层在作用上可能有哪些区别？

  答：平均池化可以提取背景信息，减少冲击失真，模糊，平滑。最大池化可以提取特征纹理，增强图片亮度
* 觉得最小池化层这个想法有没有意义？

  答：因为像素值为正数决定的，0代表为黑色。采用最小池化很可能全部都是0。最小池化甚至会让你的神经网络轻易过拟合甚至无法训练。

#### 5.5LeNet卷积神经网络

卷积层块里的基本单位是**卷积层后接最大池化层**：卷积层用来识别图像里的空间模式，如线条和物体局部，之后的最大池化层则用来降低卷积层对位置的敏感性。卷积层块由两个这样的基本单位重复堆叠构成。

LeNet交替使用卷积层和最大池化层后接全连接层来进行图像分类。

```python
net.add(nn.Conv2D(channels=6, kernel_size=5, activation='relu'),
        # 池化窗口与步幅形状相同，池化窗口在输入上每次滑动所覆盖的区域互不重叠。
        nn.MaxPool2D(pool_size = 2, strides = 2),
        # 增加输出通道使两个卷积层的参数尺寸类似。
        nn.Conv2D(channels=10, kernel_size=5, activation='relu'),
        nn.MaxPool2D(pool_size = 2, strides = 2),
        # Dense会默认将（批量，通道，宽，高）的输入转换成（批量，通道*宽*高）
        # 全连接层块会将小批量中每个样本变平（flatten）。
        nn.Dense(120, activation='sigmoid'),
        nn.Dense(84, activation='sigmoid'),
        nn.Dense(10))
```

**练习**

* 尝试基于LeNet构造更复杂的网络来提高分类准确率。例如，调整卷积窗口大小、输出通道数、激活函数和全连接层输出个数。在优化方面，可以尝试使用不同的学习率、初始化方法以及增加迭代周期。

  答：1.换成relu激活函数显著提高。2.第二个卷积层的通道可以适当减少。神经网络定义如上，其他超参数如下。

  ```python
  lr, num_epochs = 0.95, 7
  ——————————————————————————结果————————————————————————————————————
  training on gpu(0)
  epoch 1, loss 1.0864, train acc 0.577, test acc 0.778, time 4.3 sec
  epoch 2, loss 0.5540, train acc 0.782, test acc 0.826, time 4.3 sec
  epoch 3, loss 0.4597, train acc 0.828, test acc 0.850, time 4.3 sec
  epoch 4, loss 0.4063, train acc 0.849, test acc 0.862, time 4.3 sec
  epoch 5, loss 0.3754, train acc 0.860, test acc 0.872, time 4.3 sec
  epoch 6, loss 0.3539, train acc 0.868, test acc 0.877, time 4.3 sec
  epoch 7, loss 0.3320, train acc 0.876, test acc 0.881, time 4.3 se
  ```

#### 5.6AlexNet深度卷积网络

LeNet的优缺点：小数据集成绩不错，但在大数据集上表现不尽人意。

AlexNet的实现：

```python
# 使用较大的11 x 11窗口来捕获物体。同时使用步幅4来较大幅度减小输出高和宽。这里使用的输出通
# 道数比LeNet中的也要大很多
net.add(nn.Conv2D(96, kernel_size=11, strides=4, activation='relu'),
        nn.MaxPool2D(pool_size=3, strides=2),
        # 减小卷积窗口，使用填充为2来使得输入与输出的高和宽一致，且增大输出通道数
        nn.Conv2D(256, kernel_size=5, padding=2, activation='relu'),
        nn.MaxPool2D(pool_size=3, strides=2),
        # 连续3个卷积层，且使用更小的卷积窗口。除了最后的卷积层外，进一步增大了输出通道数。
        # 前两个卷积层后不使用池化层来减小输入的高和宽
        nn.Conv2D(384, kernel_size=3, padding=1, activation='relu'),
        nn.Conv2D(384, kernel_size=3, padding=1, activation='relu'),
        nn.Conv2D(256, kernel_size=3, padding=1, activation='relu'),
        nn.MaxPool2D(pool_size=3, strides=2),
        # 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
        nn.Dense(4096, activation="relu"), nn.Dropout(0.5),
        nn.Dense(4096, activation="relu"), nn.Dropout(0.5),
        # 输出层。由于这里使用Fashion-MNIST，所以用类别数为10，而非论文中的1000
        nn.Dense(10))
```

**练习**

* 尝试增加迭代周期。跟LeNet的结果相比，AlexNet的结果有什么区别？为什么？

  答：AlexNet的训练时间长了三倍，训练结果精确度提高了10%左右。AlexNet精确率提高的慢一点。应该是用了dropout以及lr比较低的原因。同时神经网络复杂度大大提高。
* AlexNet对Fashion-MNIST数据集来说可能过于复杂。试着简化模型来使训练更快，同时保证准确率不明显下降。

  答：看5.5章我的笔记中的改进后的LeNet。个人以为，改为relu激活就能显著提高精确率了，对于Fashion-MNIST而言。
* 修改批量大小，观察准确率和内存或显存的变化。

  答：批量改成256，准确率降低。显存没有看出显著变化。可能观测方法有问题，只是通过任务管理器的性能来查看。

#### 5.7VGG使用重复元素的网络

VGG块的组成规律是：连续使用数个相同的填充为1、窗口形状为3×3的卷积层后接上一个步幅为2、窗口形状为2×2的最大池化层。卷积层保持输入的高和宽不变，而池化层则对其减半。

```python
import d2lzh as d2l
from mxnet import gluon, init, nd
from mxnet.gluon import nn
# 它可以指定卷积层的数量num_convs和输出通道数num_channels
def vgg_block(num_convs, num_channels):
    blk = nn.Sequential()
    for _ in range(num_convs):
        blk.add(nn.Conv2D(num_channels, kernel_size=3,
                          padding=1, activation='relu'))
    blk.add(nn.MaxPool2D(pool_size=2, strides=2))
    return blk
```

**练习**

* 与AlexNet相比，VGG通常计算慢很多，也需要更多的内存或显存。试分析原因。

  答：通道更多，刚开始时候的宽高更大。

  ```
  ————————————————AlexNet的每一层输出形状————————————————
  conv0 output shape:	 (1, 96, 54, 54)
  pool0 output shape:	 (1, 96, 26, 26)
  conv1 output shape:	 (1, 256, 26, 26)
  pool1 output shape:	 (1, 256, 12, 12)
  conv2 output shape:	 (1, 384, 12, 12)
  conv3 output shape:	 (1, 384, 12, 12)
  conv4 output shape:	 (1, 256, 12, 12)
  pool2 output shape:	 (1, 256, 5, 5)
  dense0 output shape:	 (1, 4096)
  dropout0 output shape:	 (1, 4096)
  dense1 output shape:	 (1, 4096)
  dropout1 output shape:	 (1, 4096)
  dense2 output shape:	 (1, 10)
  ————————————————VGG的每一层输出形状————————————————
  sequential1 output shape:	 (1, 64, 112, 112)
  sequential2 output shape:	 (1, 128, 56, 56)
  sequential3 output shape:	 (1, 256, 28, 28)
  sequential4 output shape:	 (1, 512, 14, 14)
  sequential5 output shape:	 (1, 512, 7, 7)
  dense0 output shape:	 (1, 4096)
  dropout0 output shape:	 (1, 4096)
  dense1 output shape:	 (1, 4096)
  dropout1 output shape:	 (1, 4096)
  dense2 output shape:	 (1, 10)
  ```
* 尝试将Fashion-MNIST中图像的高和宽由224改为96。这在实验中有哪些影响？

  答：时间缩短到了1/4，精确率略低于224的。
* 参考VGG论文里的表1来构造VGG其他常用模型，如VGG-16和VGG-19 \[1]。

![test](http://qclf7esue.bkt.clouddn.com/image-20200628002402855.png)

#### 5.8NiN网络中的网络

下图是AlexNet和VGG的网络结构局部和NiN的网络结构局部的对比：

![左图是AlexNet和VGG的网络结构局部，右图是NiN的网络结构局部](https://zh.d2l.ai/_images/nin.svg)

1×1卷积层。它可以看成全连接层。其中空间维度（高和宽）上的每个元素相当于样本，通道相当于特征。

```python
# NiN块，三个卷积层，第一层可以指定
def nin_block(num_channels, kernel_size, strides, padding):
    blk = nn.Sequential()
    blk.add(nn.Conv2D(num_channels, kernel_size,
                      strides, padding, activation='relu'),
            nn.Conv2D(num_channels, kernel_size=1, activation='relu'),
            nn.Conv2D(num_channels, kernel_size=1, activation='relu'))
    return blk
```

**练习**

* 调节超参数，提高分类准确率。

  答：1.通过提高迭代次数可以提高准确率。2.降低dropout的值至0.3能有效提高准确率。

  batch\_size过大会内存溢出。lr降低或提高均无显著效果。

  ```
  training on gpu(0)
  epoch 1, loss 1.9254, train acc 0.283, test acc 0.498, time 121.0 sec
  epoch 2, loss 0.9495, train acc 0.651, test acc 0.747, time 118.4 sec
  epoch 3, loss 0.6075, train acc 0.774, test acc 0.812, time 120.9 sec
  epoch 4, loss 0.4930, train acc 0.818, test acc 0.856, time 120.1 sec
  epoch 5, loss 0.4341, train acc 0.840, test acc 0.853, time 119.4 sec
  epoch 6, loss 0.3901, train acc 0.857, test acc 0.870, time 120.3 sec
  epoch 7, loss 0.3619, train acc 0.868, test acc 0.884, time 119.5 sec
  epoch 8, loss 0.3398, train acc 0.876, test acc 0.892, time 121.4 sec
  ```
* 为什么NiN块里要有两个1×1卷积层？去除其中的一个，观察并分析实验现象。

  答：第一个1x1卷积层实现feature map的提取，第二个1x1卷积层进行feature map的组合

  现象：1.时间降低。因为模型复杂度降低。2.第二轮的精确率下降了。3.最终精确率低。

  原因可能是少了一个卷积层导致了feature map没有组合在一起，从而缺少了一部分的特征。直接通过feature map来回归参数。

  feature map：在每个卷积层，数据都是以三维形式存在的。你可以把它看成许多个二维图片叠在一起，其中每一个称为一个feature map。

  ```
  training on gpu(0)
  epoch 1, loss 1.5553, train acc 0.419, test acc 0.684, time 94.8 sec
  epoch 2, loss 2.1125, train acc 0.209, test acc 0.228, time 91.7 sec
  epoch 3, loss 1.8953, train acc 0.313, test acc 0.367, time 91.7 sec
  epoch 4, loss 1.7361, train acc 0.367, test acc 0.373, time 91.3 sec
  epoch 5, loss 1.6190, train acc 0.410, test acc 0.435, time 91.5 sec
  ```

#### 5.9GoogLeNet含并行连结的网络

![Inception块的结构](https://zh.d2l.ai/_images/inception.svg)

Inception块里有4条并行的线路。

前3条线路使用窗口大小分别是1×1、3×3和5×5的卷积层来抽取不同空间尺寸下的信息，其中中间2个线路会对输入先做1×1卷积来减少输入通道数，以降低模型复杂度。

第四条线路则使用3×3最大池化层，后接1×1卷积层来改变通道数。

4条线路都使用了合适的填充来**使输入与输出的高和宽一致**。

最后我们将每条线路的输出在通道维上连结，并输入接下来的层中去。

**练习**

* 对比AlexNet、VGG和NiN、GoogLeNet的模型参数尺寸。为什么后两个网络可以显著减小模型参数尺寸？

  NiN：去掉了AlexNet最后的3个全连接层，取而代之地，NiN使用了输出通道数等于标签类别数的NiN块，然后使用全局平均池化层对每个通道中所有元素求平均并直接用于分类。NiN的这个设计的好处是可以显著减小模型参数尺寸，从而缓解过拟合。

  GoogLeNet：其中中间2个线路会对输入先做1×1卷积来减少输入通道数，以降低模型复杂度。另外，Inception块中可以自定义的超参数是每个层的输出通道数，我们以此来控制模型复杂度。

#### 5.10批量归一化

**全连接层的批量归一化：**

​ 第一步：首先，对小批量BB求均值和方差，其中平方计算是按元素求平方。

$$
\boldsymbol{\mu}*\mathcal{B} \leftarrow \frac{1}{m}\sum*{i = 1}^{m} \boldsymbol{x}^{(i)},\\\boldsymbol{\sigma}*\mathcal{B}^2 \leftarrow \frac{1}{m} \sum*{i=1}^{m}(\boldsymbol{x}^{(i)} - \boldsymbol{\mu}\_\mathcal{B})^2,
$$

​ 第二步：接下来，使用按元素开方和按元素除法对x(i)标准化，ϵ>0是一个很小的常数，保证分母大于0。

$$
\hat{\boldsymbol{x}}^{(i)} \leftarrow \frac{\boldsymbol{x}^{(i)} - \boldsymbol{\mu}*\mathcal{B}}{\sqrt{\boldsymbol{\sigma}*\mathcal{B}^2 + \epsilon}},
$$

​ 第三步：引入两个可学习参数，拉伸（scale）参数 γ和偏移（shift）参数 β，如果批量归一化无益，学出的模型可以不使用批量归一化。两个参数和x(i)形状相同为d维向量。它们与x(i)分别做按元素乘法（符号⊙）和加法计算：

$$
{\boldsymbol{y}}^{(i)} \leftarrow \boldsymbol{\gamma} \odot \hat{\boldsymbol{x}}^{(i)} + \boldsymbol{\beta}.
$$

\*\*卷积层的批量归一化：\*\*批量归一化发生在卷积计算之后、应用激活函数之前。如果卷积计算输出多个通道，我们需要对这些通道的输出分别做批量归一化，且每个通道都拥有独立的拉伸和偏移参数，并均为标量。

\*\*预测时的批量归一化：\*\*训练时，我们可以将批量大小设得大一点。预测时，一种常用的方法是通过移动平均估算整个训练数据集的样本均值和方差，并在预测时使用它们得到确定的输出。ps：[移动平均法](https://baike.baidu.com/item/%E7%A7%BB%E5%8A%A8%E5%B9%B3%E5%9D%87%E6%B3%95/10785547?fr=aladdin)

在模型训练时，批量归一化利用小批量上的均值和标准差，不断调整神经网络的中间输出，从而使整个神经网络在各层的中间输出的数值更稳定。

批量归一化层和丢弃层一样，在训练模式和预测模式的计算结果是不一样的。

```python
# 批量归一化的LeNet,简洁实现版。
net = nn.Sequential()
net.add(nn.Conv2D(6, kernel_size=5),
        nn.BatchNorm(),
        nn.Activation('sigmoid'),
        nn.MaxPool2D(pool_size=2, strides=2),
        nn.Conv2D(16, kernel_size=5),
        nn.BatchNorm(),
        nn.Activation('sigmoid'),
        nn.MaxPool2D(pool_size=2, strides=2),
        nn.Dense(120),
        nn.BatchNorm(),
        nn.Activation('sigmoid'),
        nn.Dense(84),
        nn.BatchNorm(),
        nn.Activation('sigmoid'),
        nn.Dense(10))
```

**练习**

* 能否将批量归一化前的全连接层或卷积层中的偏差参数去掉？为什么？（提示：回忆批量归一化中标准化的定义。）

  答：不行，为了使得均值为0、标准差为1。
* 尝试调大学习率。同[“卷积神经网络（LeNet）”](https://zh.d2l.ai/chapter_convolutional-neural-networks/lenet.html)一节中未使用批量归一化的LeNet相比，现在是不是可以使用更大的学习率？

  答：可以用更大的学习率（例如5.0），因为数据批量大了，且数值稳定性更好。但结果显示，并没有显著提高。
* 尝试将批量归一化层插入LeNet的其他地方，观察并分析结果的变化。

  答：将批量归一化插入到激活函数之后，跟训练集的拟合效果更好了。先通过激活函数过滤了一些数据，所以拟合效果更好了。batchnorm主要是让收敛变快，但对acc影响不大。
* 尝试一下不学习拉伸参数`gamma`和偏移参数`beta`（构造的时候加入参数`grad_req='null'`来避免计算梯度），观察并分析结果。

  答：结果acc略低于原来的1%左右，因为归一化不一定有益，需要通过学习拉伸参数和偏移参数来使其达到最佳的程度。
* 查看`BatchNorm`类的文档来了解更多使用方法，例如，如何在训练时使用基于全局平均的均值和方差。

  答：nn.BatchNorm(use\_global\_stats=True)可以设置使用全局。

#### 5.11ResNet残差网络

![残差网络](https://zh.d2l.ai/_images/residual-block.svg)

设输入为x。假设图中最上方激活函数输入的理想映射为f(x)。

左图虚线框中的部分需要直接拟合出该映射f(x)

右图虚线框中的部分需要拟合出有关恒等映射的残差映射f(x)−x

```python
# 残差块：首先有2个有相同输出通道数的 3×3 卷积层。每个卷积层后接一个批量归一化层和ReLU激活函数。然后我们将输入跳过这2个卷积运算后直接加在最后的ReLU激活函数前。
class Residual(nn.Block):
    def __init__(self, num_channels, use_1x1conv=False, strides=1 , **kwargs):
        super(Residual, self).__init__(**kwargs)
        self.conv1 = nn.Conv2D(num_channels, kernel_size=3, padding=1, strides=strides)
        self.conv2 = nn.Conv2D(num_channels, kernel_size=3, padding=1)
        if use_1x1conv:
            self.conv3 = nn.Conv2D(num_channels, kernel_size=1, strides=strides)
        else:
            self.conv3 = None
        self.bn1 = nn.BatchNorm()
        self.bn2 = nn.BatchNorm()
        
    def forward(self, X):
        Y = nd.relu(self.bn1(self.conv1(X)))
        Y = self.bn2(self.conv2(Y))
        if self.conv3:
            X = self.conv3(X)
        return nd.relu(Y + X)
```

残差块通过跨层的数据通道从而能够训练出有效的深度神经网络。

**练习**

* 参考ResNet论文的表1来实现不同版本的ResNet \[1]。

  <img src="http://qclf7esue.bkt.clouddn.com/image-20200629180406719.png" alt="test2" data-size="original">
* 对于比较深的网络， ResNet论文中介绍了一个“瓶颈”架构来降低模型复杂度。尝试实现它 \[1]。

  答：”For each residual function F, we use a stack of 3 layers instead of 2 (Fig. 5). The three layers are 1×1, 3×3, and 1×1 convolutions, where the 1×1 layers are responsible for reducing and then increasing (restoring) dimensions, leaving the 3×3 layer a bottleneck with smaller input/output dimensions.“

  未实现，思路是：修改resnet\_block函数设置1×1, 3×3, and 1×1 的三个卷积层。
* 在ResNet的后续版本里，作者将残差块里的“卷积、批量归一化和激活”结构改成了“批量归一化、激活和卷积”，实现这个改进（\[2]，图1）。

  ```python
   # 修改残差块中的前向函数如下
   def forward(self, X):
          Y = nd.relu(self.conv1(self.bn1(X)))
          Y = self.conv2(self.bn2(Y))
          if self.conv3:
              X = self.conv3(X)
          return nd.relu(Y + X)
   ————————————运行结果————————————————————
  training on gpu(0)
  epoch 1, loss 0.4935, train acc 0.825, test acc 0.896, time 85.4 sec
  epoch 2, loss 0.2639, train acc 0.903, test acc 0.905, time 81.2 sec
  epoch 3, loss 0.2000, train acc 0.926, test acc 0.915, time 82.9 sec
  epoch 4, loss 0.1534, train acc 0.944, test acc 0.909, time 83.3 sec
  epoch 5, loss 0.1109, train acc 0.960, test acc 0.882, time 83.2 sec
  ```

#### 5.12DenseNet稠密连接网络

**练习**

* DenseNet论文中提到的一个优点是模型参数比ResNet的更小，这是为什么？

  答：ResNet里通过步幅为2的残差块在每个模块之间减小高和宽。DenseNet使用过渡层来减半高和宽，并减半通道数。
* DenseNet被人诟病的一个问题是内存或显存消耗过多。真的会这样吗？可以把输入形状换成224×224，来看看实际的消耗。

  答：cudaMalloc failed: out of memory
* 实现DenseNet论文中的表1提出的不同版本的DenseNet \[1]。

  答：![test3](http://qclf7esue.bkt.clouddn.com/image-20200629175812837.png)
