5.7 Chapter 5 练习题:卷积神经网络

Author

Unicornlyy

Published

2026-09-27

Modified

2026-09-27

练习题

1. 在图像分类任务中,CNN 之所以能用远少于 MLP 的参数取得更好的效果,主要依赖两个归纳偏置:一是 ______,即每个输出元素只与输入的一个局部窗口相连;二是 ______,即同一个卷积核在整张图像的所有位置上共用同一组参数。得益于此,卷积层的参数量只由输入通道数、输出通道数和卷积核大小决定,与输入图像的高宽 ______(有关 / 无关)。

2. 输入特征图的高宽为 \(32\times 32\),使用 \(3\times 3\) 的卷积核,当 padding=1、stride=1 时,输出特征图的形状为 ______ \(\times\) ______;保持其他设置不变,把 stride 改为 2,输出特征图的形状为 ______ \(\times\) ______。

3. 关于 padding 与 stride,下列说法正确的是:

A. Padding 的主要作用是在输入四周补零,并控制输出特征图的空间尺寸,缓解边界信息丢失。
B. Stride 越大,输出特征图的空间尺寸越大。
C. Padding 会增加卷积层的参数量。
D. Stride 必须能被输入尺寸整除才能使用。

4. 对于输入形状为 \((N, C_{\text{in}}, H, W)\) 的特征图,nn.Conv2d 的权重形状为 \((C_{\text{out}}, C_{\text{in}}, K_h, K_w)\)。关于多通道卷积与 \(1\times 1\) 卷积,下列说法正确的是:

A. 每个输出通道都由所有输入通道共同计算得到。对于 \(1\times1\) 卷积,每个空间位置上的 \(C_{\text{in}}\) 维特征会被映射为 \(C_{\text{out}}\) 维特征。
B. 每个输入通道只与一个对应的输出通道进行卷积,因此普通卷积不会在不同通道之间进行信息混合。
C. \(1\times1\) 卷积只作用于单个通道,因此它可以改变空间尺寸,但不能融合不同通道的信息。
D. \(1\times1\) 卷积虽然可以融合不同输入通道的信息,但由于卷积核的空间尺寸为 \(1\times 1\),其输出通道数必须与输入通道数相同。

5. 运行下面的代码:

import torch
import torch.nn as nn

model = nn.Sequential(
    nn.Conv2d(1, 6, kernel_size=5, padding=2),  # (1)
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2, stride=2),  # (2)
    nn.Conv2d(6, 16, kernel_size=5),  # (3)
    nn.ReLU(),
    nn.MaxPool2d(kernel_size=2, stride=2),  # (4)
    nn.Flatten(),
    nn.Linear(16 * 5 * 5, 120),
)

x = torch.randn(1, 1, 28, 28)
out = model(x)
print('out.shape:', out.shape)

请回答:

  1. 依次写出(1)至(4)处特征图的形状,以及 out.shape 的打印结果;
  2. 解释 nn.Linear(16 * 5 * 5, 120) 中 16 * 5 * 5 的含义;
  3. 如果把输入改为 torch.randn(1, 1, 32, 32),程序会发生什么?请说明原因,并给出一种改动最小的修复方式。

参考答案

1. 答案:局部性(局部连接);权重共享;无关

MLP 把每个像素与隐层神经元全部相连,参数量会随输入高宽急剧增长;而卷积层每次只在局部窗口内做乘加,并且同一个卷积核在整张图像上滑动、共用同一组权重。因此卷积层的参数量为:

\[ C_{\text{out}} \times C_{\text{in}} \times K_h \times K_w + C_{\text{out}} \]

它只取决于通道数和卷积核大小,与输入图像的高宽无关。

2. 答案:\(32\times 32\);\(16\times 16\)

输出尺寸的计算公式为:

\[ H_{\text{out}} = \left\lfloor \frac{H + 2P - K}{S} \right\rfloor + 1 \]

  • padding=1、stride=1 时:\(\lfloor (32+2-3)/1 \rfloor + 1 = 32\),输出高宽不变;
  • stride=2 时:\(\lfloor (32+2-3)/2 \rfloor + 1 = 16\),输出高宽减半。

因此,stride>1 的卷积在提取特征的同时完成了下采样,因此现代 CNN 常用它代替部分池化层。

3. 答案:A

Padding 的作用是在输入四周补零。一方面控制输出尺寸(如 padding='same' 保持输出尺寸不变),另一方面让边缘、角落的像素能参与更多次计算,缓解边界信息丢失问题。

其他选项错误的原因:

  • B:Stride 越大,窗口起点越稀疏,输出尺寸反而越小;
  • C:Padding 补的是输入的零,不改变卷积核形状,参数量不变;
  • D:Stride 可以是任意正整数,只要第一个窗口能放在输入内即可,没有整除要求。

4. 答案:A

在普通卷积中,每个输出通道都会使用一个覆盖全部 \(C_{\text{in}}\) 个输入通道的卷积核。对于某个输出通道,各输入通道分别与对应的卷积核切片进行卷积,随后沿输入通道维度求和,得到该输出通道的结果。因此,如果希望得到 \(C_{\text{out}}\) 个输出通道,就需要 \(C_{\text{out}}\) 组这样的卷积核。

对于 \(1\times1\) 卷积,虽然卷积核在空间上只覆盖当前位置,但它仍然覆盖全部输入通道。因此,在每个空间位置 \((i,j)\),它实际上是在对一个 \(C_{\text{in}}\) 维特征向量做线性映射:

\[ y_{i,j} = Wx_{i,j} + b, \quad W\in\mathbb{R}^{C_{\text{out}}\times C_{\text{in}}}, \quad b \in\mathbb{R}^{C_{\text{out}}} \]

所以,\(1\times 1\) 卷积既可以混合不同通道的信息,也可以通过设置不同的 \(C_{\text{out}}\) 实现升维或降维。

其他选项错误的原因:

  • B:描述的是类似 depthwise convolution 的情况。普通卷积中,一个输出通道通常会同时使用所有输入通道的信息,而不是一个输入通道只对应一个输出通道。
  • C:\(1\times 1\) 卷积在空间上只作用于当前位置,但在通道维度上会同时使用所有输入通道,因此可以进行通道信息融合。通常在 stride=1 时,它并不会改变空间尺寸。
  • D:卷积核的空间大小 \(1\times 1\) 与输出通道数无关。\(C_{\text{out}}\) 可以自由设置,因此 \(1\times 1\) 卷积经常用于增加或减少通道数。

5. 答案:

(a) 各处特征图的形状依次为:

位置 计算 输出形状
(1) Conv2d(1, 6, k=5, p=2) \((28 + 2\times 2 - 5) + 1 = 28\) \((1, 6, 28, 28)\)
(2) MaxPool2d(2, 2) \(28 / 2 = 14\) \((1, 6, 14, 14)\)
(3) Conv2d(6, 16, k=5) \((14 - 5) + 1 = 10\) \((1, 16, 10, 10)\)
(4) MaxPool2d(2, 2) \(10 / 2 = 5\) \((1, 16, 5, 5)\)

经过 nn.Flatten() 后形状变为 \((1, 400)\),再过 nn.Linear(400, 120),因此打印结果为:

torch.Size([1, 120])

(b) nn.Flatten() 会把 \(16 \times 5 \times 5\) 的特征图展平成一维向量, 所以 \(16\times 5\times 5 = 400\) 就是展平后的特征维数,也就是全连接层期望的输入特征数。它由通道数乘以最终特征图的高和宽得到。

(c) 输入改为 \(32\times 32\) 后,特征图形状会变成:

\[ (1, 6, 32, 32) \rightarrow (1, 6, 16, 16) \rightarrow (1, 16, 12, 12) \rightarrow (1, 16, 6, 6) \]

展平后得到 \(16\times 6\times 6 = 576\) 维,而 nn.Linear 期望的输入是 400 维,于是程序会抛出形状不匹配的错误。原因在于卷积层天然支持任意大小的输入,但全连接层的输入维数是固定的。

改动量最小的修复方式是把(4)处换成自适应池化:

nn.AdaptiveMaxPool2d(5)

它无论输入特征图多大,都输出固定的 \(5\times 5\),从而保证展平后的维数恒为 400。另一种方式是把

nn.Linear(16 * 5 * 5, 120)

改成

nn.Linear(16 * 6 * 6, 120)

但这样模型又被绑死在 \(32\times 32\) 的输入上,因此自适应池化的方案更通用。