\[ \begin{align}\begin{aligned}\newcommand{\ba}{\boldsymbol{a}} \newcommand{\bb}{\boldsymbol{b}} \newcommand{\be}{\boldsymbol{e}} \newcommand{\bq}{\boldsymbol{q}} \newcommand{\bk}{\boldsymbol{k}} \newcommand{\bw}{\boldsymbol{w}} \newcommand{\bx}{\boldsymbol{x}} \newcommand{\by}{\boldsymbol{y}} \newcommand{\bz}{\boldsymbol{z}} \newcommand{\bd}{\boldsymbol{d}} \newcommand{\bv}{\boldsymbol{v}} \newcommand{\bs}{\boldsymbol{s}}\\\newcommand{\btheta}{\boldsymbol{\theta}} \newcommand{\bbeta}{\boldsymbol{\beta}} \newcommand{\bgamma}{\boldsymbol{\gamma}} \newcommand{\bsigma}{\boldsymbol{\sigma}} \newcommand{\md}{\mbox{d}} \newcommand{\bmu}{\boldsymbol{\mu}} \newcommand{\bone}{\boldsymbol{1}} \newcommand{\bzero}{\boldsymbol{0}} \newcommand{\bepsilon}{\boldsymbol{\epsilon}} \newcommand{\bphi}{\boldsymbol{\phi}} \newcommand{\bh}{\boldsymbol{h}} \newcommand{\bc}{\boldsymbol{c}} \newcommand{\br}{\boldsymbol{r}} \newcommand{\bQ}{\boldsymbol{Q}} \newcommand{\bK}{\boldsymbol{K}} \newcommand{\bV}{\boldsymbol{V}} \newcommand{\bSigma}{\boldsymbol{\Sigma}} \newcommand{\bg}{\boldsymbol{g}} \newcommand{\bxi}{\boldsymbol{\xi}} \newcommand{\bvarepsilon}{\boldsymbol{\varepsilon}} \newcommand{\bdelta}{\boldsymbol{\delta}} \newcommand{\bq}{\boldsymbol{q}} \newcommand{\bk}{\boldsymbol{k}} \newcommand{\bJ}{\boldsymbol{J}} \newcommand{\bp}{\boldsymbol{p}} \newcommand{\bi}{\boldsymbol{i}} \newcommand{\bo}{\boldsymbol{o}} \newcommand{\bE}{\boldsymbol{E}} \newcommand{\bH}{\boldsymbol{H}} \newcommand{\bL}{\boldsymbol{L}} \newcommand{\bu}{\boldsymbol{u}} \newcommand{\bLambda}{\boldsymbol{\Lambda}} \newcommand{\trans}{^{\rm\scriptsize T}} \newcommand{\var}{\mathrm{var}}\\\newcommand{\bA}{\boldsymbol{A}} \newcommand{\bB}{\boldsymbol{B}} \newcommand{\bC}{\boldsymbol{C}} \newcommand{\bD}{\boldsymbol{D}} \newcommand{\bG}{\boldsymbol{G}} \newcommand{\bI}{\boldsymbol{I}} \newcommand{\bM}{\boldsymbol{M}} \newcommand{\bP}{\boldsymbol{P}} \newcommand{\bS}{\boldsymbol{S}} \newcommand{\bU}{\boldsymbol{U}} \newcommand{\bW}{\boldsymbol{W}} \newcommand{\bX}{\boldsymbol{X}} \newcommand{\bY}{\boldsymbol{Y}} \newcommand{\bZ}{\boldsymbol{Z}} \newcommand{\cotp}{\textcolor[RGB]{48,209,88}{TP}} \newcommand{\cotn}{\textcolor[RGB]{100,210,255}{TN}} \newcommand{\cofp}{\textcolor[RGB]{94,92,230}{FP}} \newcommand{\cofn}{\textcolor[RGB]{191,90,242}{FN}}\\\newcommand{\numcotp}{\textcolor[RGB]{48,209,88}{50}} \newcommand{\numcotn}{\textcolor[RGB]{100,210,255}{30}} \newcommand{\numcofp}{\textcolor[RGB]{94,92,230}{10}} \newcommand{\numcofn}{\textcolor[RGB]{191,90,242}{10}} \DeclareMathOperator*{\argmin}{arg\,min}\end{aligned}\end{align} \]

卷积操作#

学习目标与记号#

  1. 准确解释一维互相关、二维核、填充、步幅、感受野与空洞卷积;

  2. 根据公式和张量维度分析多通道卷积,并识别常见实现错误;

  3. Python 实现或验证输出尺寸;在其他条件相同的情况下,只改变一个需要研究的因素进行比较,并根据实验结果分析该因素可能带来的影响。

  本节沿用统一记号:普通小写字母表示标量,粗体小写字母表示向量,粗体大写字母表示矩阵或高阶张量,转置写作 \(\trans\)⁠。对于二维输入,\(d_C\)⁠、\(d_H\)\(d_W\) 分别表示通道数、高和宽;卷积核尺寸、填充、步幅和空洞率分别写作 \(f_1\times f_2\)⁠、\(p_1\times p_2\)⁠、\(s_1\times s_2\)\(r_1\times r_2\)⁠。默认两个空间方向采用相同设置,即 \(f_1=f_2=f\)⁠、\(p_1=p_2=p\)⁠、\(s_1=s_2=s\)\(r_1=r_2=r\)⁠。输出的通道数、高和宽分别写作 \(d'_C\)⁠、\(d'_H\)\(d'_W\)⁠。正文与练习中的程序都应同时检查数值结果和数组维度。本节练习的参考答案见 离散卷积与多通道互相关答案⁠。

  数组切片和核的批量计算会用到 NumPy 数组广播机制⁠;本节结果将在 卷积层 中组合成网络模块。

  本节从一维序列出发,依次介绍核、填充、步幅、感受野、空洞率、二维运算和多通道运算。深度学习框架中名为“卷积”的操作通常数学上是 互相关⁠:卷积核不翻转。由于核参数由数据学习,两种约定的表达能力相同,但在手工推导和实现时必须保持约定一致。

  在概率论中,若独立随机变量 \(X\)\(Y\) 关于同一控制测度 \(\mu\) 的密度分别为 \(f_X\)\(f_Y\)⁠,则 \(Z=X+Y\) 的密度为

\[\begin{equation} f_Z(z)=\int_{-\infty}^{\infty}f_X(x)f_Y(z-x)\mu(\mathrm{d}x), \end{equation}\]

其中,连续变量通常以 Lebesgue 测度作为 \(\mu\)⁠,此时 \(\mu(\mathrm{d}x)\) 可以写作 \(\mathrm{d}x\)⁠;离散变量通常以计数测度作为 \(\mu\)⁠。对于独立离散随机变量,相应公式为

\[\begin{equation} f_Z(z) = \sum_{x} f_X(x)f_Y(z-x). \end{equation}\]

  更一般地,函数 \(f\)\(g\) 的卷积记作

\[\begin{equation} h(z)=(f\ast g)(z) =\int f(x)g(z-x)\mu(\mathrm{d}x) =\int f(z-x)g(x)\mu(\mathrm{d}x). \end{equation}\]

上述公式是真正的数学卷积,其中第二个函数的自变量含 \(z-x\)⁠,相当于先翻转再平移。后文采用深度学习框架的互相关约定,并沿用“卷积层”这一通行名称 1后文只讨论离散序列或离散数组上的卷积与互相关。

一维卷积#

  “一维”表示输入数据沿一个方向排列,例如时间序列、声音信号或词语序列。“互相关”表示让一个较短的权重序列在输入序列上逐步移动;每到一个位置,就将权重与当前位置覆盖的输入元素分别相乘,再把所有乘积相加。互相关直接使用原来的权重排列顺序,不会像严格定义的数学卷积那样先将权重前后翻转。

  设输入序列为 \(\bx=(x_1,\ldots,x_{d})\trans\)⁠,长度为 \(f\) 的权重序列为 \(\bw=(w_1,\ldots,w_f)\trans\)⁠。在不添加填充值、每次向右移动一个元素时,一维互相关的第 \(i\) 个输出为

\[\begin{equation} z_i=\sum_{j=1}^{f}x_{i+j-1}w_j, \qquad i=1,\ldots,d-f+1, \end{equation}\]

其中 \(x_{i+j-1}\) 是当前覆盖位置的输入元素,\(w_j\) 是与它对应的权重,输出序列的长度为 \(d_x-f+1\)⁠。

  例如,当 \(f=3\)\(\bw=(w_1,w_2,w_3)\trans\) 时,上式变为

\[\begin{equation} z_i = w_1x_{i} + w_2x_{i+1} + w_3x_{i+2}. \end{equation}\]

  我们将权重向量 \(\bw\) 称为卷积核(kernel)或滤波器(filter)。卷积核的大小决定加权求和的范围,通常远小于输入序列长度。下面的视频展示一个简单的一维互相关过程:

  在这个例子中,原始序列 \([3,6,5,4,8,9,1,7,9,10,10]\) 的维度为 \(d=11\)⁠,卷积核 \([-1,0,1]\) 的维度为 \(f=3\)⁠。按照本节采用的互相关约定,将卷积核沿原始序列滑动,每次计算卷积核与当前覆盖元素的加权和。在序列最开始的位置,卷积核与前三个元素 \([3,6,5]\) 分别相乘并求和,得到第一个结果;然后卷积核向右移动一个元素,与 \([6,5,4]\) 进行相同计算,得到第二个结果;以此类推,直到卷积核移动到最后一个有效位置。具体计算如下:

\[\begin{split}\begin{array}{ccccr} z_1 &=& 3\times (-1) + 6\times (0) + 5\times (1) &=& 2, \\ z_2 &=& 6\times (-1) + 5\times (0) + 4\times (1) &=& -2, \\ &&\vdots& \\ z_9 &=& 9\times (-1) + 10\times (0) + 10\times (1) &=& 1. \end{array}\end{split}\]

最终得到互相关结果 \([2,-2,3,5,-7,-2,8,3,1]\)⁠,其长度为 \(d-3+1=d-2=9\)⁠。按照深度学习中的通行名称,后文仍将这种结果称为卷积结果。

  不同核参数会产生不同效果。例如,大小为 \(f=3\) 的均值核 \(\bw=(1/3,1/3,1/3)\trans\) 等价于滑动窗口平均,可平滑输入;差分核 \(\bw=(-1,0,1)\trans\) 则对窗口两端作差,可突出局部跃迁。下面的程序把一个长度为 50、索引 20--29 为 1 的阶跃序列作为输入,用 conv1d 接收一维输入、卷积核、步幅和两端填充量,并返回一维互相关结果。示例使用核 [-1, 0, 1]步幅 1 和填充 1,因而预期输出仍有 50 个元素,并在高值区间的两个边界附近出现非零响应;最后用上下两幅曲线对照输入和输出。

 1import numpy as np
 2import matplotlib.pyplot as plt
 3from matplotlib import font_manager
 4
 5# 为中文标题选择系统中可用的字体,避免中文显示为方框。
 6available_fonts = {font.name for font in font_manager.fontManager.ttflist}
 7chinese_font_name = next(
 8    (
 9        name
10        for name in (
11            "Songti SC",
12            "PingFang SC",
13            "Microsoft YaHei",
14            "SimHei",
15            "Noto Sans CJK SC",
16            "Arial Unicode MS",
17        )
18        if name in available_fonts
19    ),
20    None,
21)
22if chinese_font_name is None:
23    raise RuntimeError("未找到支持中文的 Matplotlib 字体")
24chinese_font = font_manager.FontProperties(family=chinese_font_name)
25
26def conv1d(input_signal, kernel, stride=1, padding=0):
27    """
28    1D 卷积实现
29    :param input_signal: 输入信号 (1D numpy array)
30    :param kernel: 卷积核 (1D numpy array)
31    :param stride: 步长 (int)
32    :param padding: 填充大小 (int)
33    :return: 卷积结果 (1D numpy array)
34    """
35    # 在输入信号两端添加零填充
36    input_signal = np.pad(input_signal, (padding, padding), mode='constant')
37    input_length = len(input_signal)
38    kernel_length = len(kernel)
39
40    # 计算输出信号长度
41    output_length = (input_length - kernel_length) // stride + 1
42    output_signal = np.zeros(output_length)
43
44    # 卷积计算
45    for i in range(output_length):
46        start = i * stride
47        end = start + kernel_length
48        output_signal[i] = np.sum(input_signal[start:end] * kernel)
49
50    return output_signal
51
52# 创建一个输入信号(阶跃信号)
53length = 50
54input_signal = np.zeros(length)
55input_signal[20:30] = 1  # 在20到30位置有一个高值区域
56
57# 定义卷积核(边缘检测)
58kernel = np.array([-1, 0, 1])  # 检测信号的变化
59
60# 应用卷积
61output_signal = conv1d(input_signal, kernel, stride=1, padding=1)
62
63# 绘制输入和输出信号
64plt.figure(figsize=(10, 5))
65
66plt.subplot(2, 1, 1)
67plt.plot(input_signal, label="Input Signal", drawstyle='steps-mid', linewidth=2)
68plt.title("输入信号(阶跃信号)", fontproperties=chinese_font)
69# plt.legend()
70
71plt.subplot(2, 1, 2)
72plt.plot(output_signal, label="Output Signal (After 1D Convolution)", color="orange", drawstyle='steps-mid', linewidth=2)
73plt.title("输出信号(边缘检测)", fontproperties=chinese_font)
74# plt.legend()
75plt.tight_layout()
76# plt.show()
../_images/5_1_conv_operation_0_0.png

  conv1d 先用 0 扩展输入两端,再由输入长度、核大小和步幅计算合法窗口数;循环每次截取一个窗口,与核逐元素相乘后求和。由于 padding=1 抵消了大小为 3 的核带来的长度缩减,绘图中的输入、输出横轴可以直接对齐。如图所示,代码在索引 20 到 29 的位置设置高值区间,核 \([-1,0,1]\) 会对窗口两侧样本作差,因此在 0 到 1 和 1 到 0 的跃迁附近产生符号相反的响应。响应符号还取决于采用互相关还是翻转核后的卷积约定。

  这个函数是帮助理解计算过程的单通道一维实现,只接受一维数组和对称的整数填充,没有检查非法步幅、核比填充后输入更长等输入,也没有处理批量、多个通道或空洞率。实际建模通常使用框架提供的卷积算子,以获得完整的参数检查、自动微分和更高的运行效率;字体选择代码也只服务于本页中文图题,与卷积计算无关。

  在上面的代码中,注意到卷积操作会使输出序列的长度减小,因此我们在输入序列的两端添加了 0 值填充。在实际应用中,我们可以通过 填充(Padding) 的方式使输出序列长度与原始序列长度相同,或者使序列边界元素的利用率与中间元素相似。填充是在序列的两端添加一个或多个元素,填充的元素通常为 0。例如,对于大小为 \(f=3\) 的卷积核,我们可以在序列的两端各填充一个元素。

  常见的填充方式有 ValidSameValid 表示不添加填充,因此卷积核只在能够完全覆盖输入的位置进行计算;Same 表示选择适当的填充,使输出尺寸按照框架规定与输入尺寸对应。在步幅和空洞率都为 1、卷积核大小为奇数时,两端各填充 \(p=(f-1)/2\) 个元素便可使输出长度与输入长度相同。当步幅大于 1、卷积核大小为偶数或两个边界的填充量不同时,Same 的具体填充方式还取决于所使用的框架。下面这个视频展示了一个添加填充 \(p=1\) 的一维卷积操作,其输入和输出序列长度相同:

  对于声音、高频信号等序列数据,由于序列元素密度较高,相邻元素之间的相关性较强,近距离的元素之间重叠的信息较多,此时我们可以通过调整 步幅(Stride) 来减少卷积操作的次数并减少输出序列的长度。步幅是卷积核每次移动的距离。例如,对于步幅为 \(s=2\) 的卷积操作,卷积核每次移动两个元素再执行一次卷积运算。下面这个视频展示了一个添加了步幅 \(s=2\) 的一维卷积操作的过程:

感受野

  某个输出能够直接或经过前面若干层间接利用的输入范围,称为该输出的 感受野(receptive field)⁠。感受野回答的是“这个输出可以受到哪些输入位置的影响”,它描述输入信息的覆盖范围,并不等同于卷积核的参数数量。

  对于大小为 3、步幅为 1 的普通一维卷积,\(z_i\) 直接由 \(x_i\)⁠、\(x_{i+1}\)\(x_{i+2}\) 决定,因此它在输入序列上的感受野大小为 3。若再接一个大小为 3、步幅为 1 的卷积层,第二层的一个输出会综合第一层相邻的三个输出,最终可以利用原输入中连续的 5 个位置。因此,堆叠卷积层可以逐步扩大深层输出的感受野。

  较大的感受野有助于模型结合距离较远的信息,但并不表示范围内的每个输入位置都会产生同样大的影响,也不能保证模型一定表现得更好。选择卷积核大小、网络层数、步幅和空洞率时,还需要同时考虑局部细节、输出分辨率和计算量。

空洞卷积(Dilated Convolution)

  普通的一维互相关会读取连续的输入元素。例如,大小为 3 的卷积核在第 \(i\) 个位置读取 \(x_i\)⁠、\(x_{i+1}\)\(x_{i+2}\)⁠。空洞卷积(dilated convolution,也称膨胀卷积)在相邻的读取位置之间留出固定间隔,使卷积核能够利用距离更远的输入信息。设卷积核大小为 \(f\)⁠,空洞率(dilation rate,也称膨胀率)为正整数 \(r\)⁠,在不填充、步幅为 1 时,第 \(i\) 个输出为

(82)#\[z_i^{(r)}=\sum_{j=1}^{f}w_jx_{i+r(j-1)},\]

其中 \(r=1\) 时,式 (82) 就是普通的一维互相关;当 \(r>1\) 时,卷积核相邻权重读取的输入位置相隔 \(r\) 个元素。也可以把它理解为在相邻的卷积核元素之间插入 \(r-1\) 个 0,但这些 0 只是帮助理解计算位置,并不会真正插入输入序列。

  例如,若 \(f=3\)⁠、\(r=2\)⁠,三个权重依次读取 \(x_i\)⁠、\(x_{i+2}\)\(x_{i+4}\)⁠,因此计算结果为 \(z_i^{(2)}=w_1x_i+w_2x_{i+2}+w_3x_{i+4}\)⁠。此时卷积核实际覆盖的输入范围为 \(2(3-1)+1=5\) 个元素,但仍然只有 3 个权重参数。一般地,有效核大小为 \(f_{\mathrm{eff}}=r(f-1)+1\)⁠。

  主要优点。 空洞卷积可以在不增加卷积核参数量的情况下扩大输出的 感受野⁠,并且不必像增大步幅或使用池化那样降低输出序列的分辨率。因此,它适合处理需要结合较远信息的序列,也常用于图像分割等需要同时保留位置细节和较大范围信息的任务。

  需要注意。 较大的空洞率会跳过更多相邻元素,可能遗漏连续的局部变化,并产生不连续的采样现象。实际模型通常把普通卷积与不同空洞率的空洞卷积结合使用,使模型既能利用附近信息,也能利用较远信息。

  若输入长度为 \(d\)⁠、两端各填充 \(p\)⁠、核大小为 \(f\)⁠、步幅为 \(s\)⁠、空洞率为 \(r\)⁠,有效核大小为 \(r(f-1)+1\)⁠,输出长度为

\[d' =\left\lfloor \frac{d+2p-r(f-1)-1}{s}+1 \right\rfloor.\]

  只有分子对应的位置能被完整覆盖;若不能整除,末端未覆盖的元素会被舍弃。所谓 Same 填充需要结合核大小、空洞率和步幅理解,并不总是简单地令 \(p=(f-1)/2\)⁠。

二维卷积#

  二维卷积是卷积神经网络中最常用的卷积操作。二维卷积的卷积核是一个矩阵,与一维卷积类似,卷积核在输入图像上由左到右、由上到下滑动。当卷积核在图像上滑动时,它会与图像中的局部区域进行元素乘法,然后将所有乘积相加,生成一个新的数值。这个过程在图像的每一个有效位置重复进行,最终生成一张新的二维数组,称为特征图(feature map)。具体来说,使用一个大小为 \(f_1\times f_2\) 的卷积核 \(\bW=(w_{uv})\) 对一个大小为 \(d_H\times d_W\) 的单通道图像 \(\bX=(x_{ij})\) 进行互相关,在无填充、步幅和空洞率均为 1 时,可得到大小为 \((d_H-f_1+1)\times(d_W-f_2+1)\) 的特征图 \(\bZ=(z_{ij})\)⁠。计算公式 2严格来说,此处的卷积操作是互相关(Cross-Correlation)操作,而不是卷积(Convolution)操作。但在深度学习中,我们通常将互相关操作称为卷积操作。

\[\begin{equation} z_{ij} = \sum_{u=1}^{f_1}\sum_{v=1}^{f_2} x_{i+u-1,j+v-1}w_{uv}. \end{equation}\]

将其表示为矩阵形式如下:

\[\begin{equation} \bZ = \bX \ast \bW, \end{equation}\]

其中 \(\ast\) 表示本章采用的互相关运算。与一维卷积类似,我们可以通过填充、步幅和空洞率来控制输出图像的大小。下面这个视频展示了一个填充 \(p=1\)⁠、步幅 \(s=2\) 的二维卷积操作过程:

  卷积核的不同参数会导致不同的卷积操作,从而提取不同的特征。例如,以下几个较为常见的卷积核:

\[\begin{split}\begin{aligned} \bW_1 &= \begin{bmatrix} 1/9 & 1/9 & 1/9 \\ 1/9 & 1/9 & 1/9 \\ 1/9 & 1/9 & 1/9 \end{bmatrix},\\ \bW_2 &= \begin{bmatrix} 1/16 & 1/8 & 1/16 \\ 1/8 & 1/4 & 1/8 \\ 1/16 & 1/8 & 1/16 \end{bmatrix},\\ \bW_3 &= \begin{bmatrix} -1 & -1 & -1 \\ -1 & 9 & -1 \\ -1 & -1 & -1 \end{bmatrix},\\ \bW_4 &= \begin{bmatrix} -1 & 0 & 1 \\ -2 & 0 & 2 \\ -1 & 0 & 1 \end{bmatrix},\\ \bW_5 &= \begin{bmatrix} -1 & -2 & -1 \\ 0 & 0 & 0 \\ 1 & 2 & 1 \end{bmatrix}, \end{aligned}\end{split}\]

其中: 1. \(\bW_1\) 是一个平滑均值卷积核,用于平滑图像。 2. \(\bW_2\) 是一个高斯卷积核,其权重更加集中在中心附近;与均值核相比,它在平滑图像时通常较少产生生硬的块状模糊。 3. \(\bW_3\) 是一个锐化卷积核,用于增强图像的边缘。 4. \(\bW_4\)\(\bW_5\) 是两个 Sobel 卷积核,分别用于检测图像的垂直边缘和水平边缘。

  下图展示了这些卷积核的效果:

../_images/Figure_5_1_conv_kernel.png

图 14 卷积核的效果。为了更好的展示卷积核的效果,均值卷积核和高斯卷积核的大小为 \(5\times5\)⁠,其余卷积核的大小仍为 \(3\times3\)⁠。#

  通过比较上图可以看出,均值核和高斯核使边界更平滑,锐化核增强局部高频变化,两个 Sobel 核分别响应不同方向的边缘。具体效果还会受到边界填充、像素尺度和互相关方向约定影响。

  在卷积神经网络中,正是通过这些卷积核的不同参数,使得卷积神经网络能够提取图像的不同特征,从而实现图像的分类、识别等任务。在实际应用中,我们通常不会手动设计卷积核,而是通过训练的方式,使得卷积核能够自动学习到合适的参数。

两个空间方向可以分别设置

  二维卷积不要求竖直方向与水平方向使用相同的设置,其中竖直方向对应图像的高度方向、水平方向对应图像的宽度方向。设输入图片的空间尺寸为 \(d_H\times d_W\)⁠,卷积核尺寸为 \(f_1\times f_2\)⁠,填充尺寸为 \(p_1\times p_2\)⁠,步幅为 \(s_1\times s_2\)⁠,空洞率为 \(r_1\times r_2\)⁠。这里,\(p_1\) 表示在图片上方和下方各填充 \(p_1\) 行,\(p_2\) 表示在左侧和右侧各填充 \(p_2\) 列;\(s_1\)\(s_2\) 分别表示卷积核每次向下和向右移动的距离。

  考虑空洞率后,卷积核在两个方向上的有效尺寸分别为

\[f_{1,\mathrm{eff}}=r_1(f_1-1)+1, \qquad f_{2,\mathrm{eff}}=r_2(f_2-1)+1.\]

  因此,输出图片的高度和宽度分别为

(83)#\[\begin{split}\begin{aligned} d'_H &=\left\lfloor \frac{d_H+2p_1-r_1(f_1-1)-1}{s_1}+1 \right\rfloor,\\ d'_W &=\left\lfloor \frac{d_W+2p_2-r_2(f_2-1)-1}{s_2}+1 \right\rfloor, \end{aligned}\end{split}\]

其中向下取整表示:如果卷积核在图片末端只能覆盖一部分输入,就不再计算该位置。只有当填充后的高度和宽度分别不小于两个方向上的有效卷积核尺寸时,才能得到有效输出。若使用 \(d'_C\) 个卷积核产生 \(d'_C\) 个输出通道,则单张图片的输出张量维度为 \(d'_C\times d'_H\times d'_W\)⁠;对于包含 \(m\) 张图片的批量,输出张量维度为 \(m\times d'_C\times d'_H\times d'_W\)⁠。

  例如,设 \(d_H=32\)⁠、\(d_W=48\)⁠,卷积核尺寸为 \(f_1\times f_2=3\times5\)⁠,填充为 \(p_1\times p_2=1\times2\)⁠,步幅为 \(s_1\times s_2=2\times3\)⁠,空洞率为 \(r_1\times r_2=1\times2\)⁠。两个方向上的有效卷积核尺寸分别为 \(3\)\(9\)⁠。代入式 (83) 可得 \(d'_H=16\)⁠、\(d'_W=15\)⁠,所以每个输出通道对应一张 \(16\times15\) 的特征图。

  填充也可以进一步区分为上、下、左、右四个数。此时,只需在式 (83) 中分别用上、下填充之和替代 \(2p_1\)⁠,用左、右填充之和替代 \(2p_2\)⁠。为了简化符号,本节通常默认两个方向采用相同的卷积核大小、填充、步幅和空洞率;若只写一个数,就表示该数同时用于竖直方向和水平方向。

多通道卷积#

  实际图像或中间特征通常有多个通道。设单张输入图片为 \(\bX\in\mathbb{R}^{d_C\times d_H\times d_W}\)⁠。生成一个输出通道需要一个跨越全部输入通道的卷积核,其维度为 \(d_C\times f_1\times f_2\)⁠;这个卷积核与每个输入通道分别做互相关,再把所有通道的结果相加并加上一个偏置,因此一个卷积核得到一张二维特征图。若需要 \(d'_C\) 个输出通道,就需要学习 \(d'_C\) 个这样的卷积核。

multi_channel

  记输入的第 \(c\) 个通道为 \(\bX_c\)⁠,生成第 \(q\) 个输出通道时作用于第 \(c\) 个输入通道的核为 \(\bW_{q,c}\)⁠,则

\[\begin{equation} \bZ_q=b_q+\sum_{c=1}^{d_C} \bX_c\ast\bW_{q,c}, \qquad q=1,\ldots,d'_C. \end{equation}\]

把全部卷积核放在一起,权重张量的维度为 \(d'_C\times d_C\times f_1\times f_2\)⁠,偏置的维度为 \(d'_C\times1\times1\)⁠。因此该层参数量为 \(d'_C(d_Cf_1f_2+1)\)⁠,与输入图像的高和宽无关。对于包含 \(m\) 张图片的批量,输入和输出维度分别为 \(m\times d_C\times d_H\times d_W\)\(m\times d'_C\times d'_H\times d'_W\)⁠。

平移等变性

  卷积核在不同空间位置重复使用同一组参数,因此卷积具有重要的 平移等变性(translation equivariance)⁠。为了准确描述这一性质,记 \(\mathcal C\) 为使用固定卷积核和偏置进行一次互相关的整体计算。换句话说,输入 \(\bX\) 经过该计算后得到的输出记为 \(\mathcal C(\bX)\)⁠。

  再记 \(T_\delta\) 为平移运算,其中 \(\delta=(\delta_1,\delta_2)\) 分别表示沿高度和宽度方向移动的距离。对于多通道输入 \(\bX=(X_{cij})\)⁠,定义

\[(T_\delta\bX)_{cij} =X_{c,i-\delta_1,j-\delta_2},\]

其中,\(T_\delta\bX\) 表示把输入的所有通道同时沿高度方向移动 \(\delta_1\) 个位置、沿宽度方向移动 \(\delta_2\) 个位置,而不改变各位置的数值和通道关系。

  在无限网格上,或者暂时忽略有限图像的边界,并且卷积步幅为 1 时,互相关满足

(84)#\[\mathcal C(T_\delta\bX) =T_\delta\mathcal C(\bX)\text{。}\]

  式 (84) 左边表示“先平移输入,再做卷积”,右边表示“先做卷积,再把输出作同样的平移”。二者相等说明:输入中的特征移动到新位置后,卷积提取出的相应特征也会移动到新位置。这就是平移等变性。它并不是平移不变性;平移不变性要求输入平移后最终结果完全不变,而平移等变性允许输出的位置随输入一起改变。

  该结论有明确的适用条件。对于有限图像,平移可能改变哪些位置接触人工填充区域,因此边界附近的结果不一定只是简单平移。步幅大于 1 时,卷积只计算部分空间位置;只有与步幅相配合的输入平移才能稳定地对应为输出网格上的整数平移,而任意一个像素的平移通常不再严格满足式 (84)⁠。

三维卷积

  多通道二维卷积的卷积核只沿图像的高和宽移动。计算每个输出位置时,它会同时读取所有输入通道,并把各通道的加权计算结果相加,再加上相应的偏置。三维卷积还会沿深度或时间方向移动。二者使用的张量都可能是五维的,不能只根据数组维度判断运算类型,还应查看卷积核实际沿哪些方向移动。

  下面这个视频展示了一个简单的多通道卷积操作的过程:

Shiny 交互演示:二维卷积与多通道卷积

  第一个交互页面可以设置输入矩阵、卷积核、步长和填充,并逐项观察二维输出的计算过程。第二个页面进一步展示多个输入通道各自形成的贡献、输出维度和参数维度,并提供理论感受野与残差连接标签页。与常见深度学习框架相同,两个页面实际执行的都是卷积核不翻转的互相关运算。

核心推导与实现核验#

核心关系

\[d'_H=\left\lfloor\frac{d_H+2p-r(f-1)-1}{s}+1\right\rfloor.\]

  推导路径。 有效核跨度为 \(r(f-1)+1\)⁠;填充后可用高度为 \(d_H+2p\)⁠,核上端从 0 开始每次移动 \(s\)⁠,可放置次数由最后合法起点除以步幅再加 1。

关键条件

  输出位置数必须是所有满足“有效核完全落入填充输入”的起点数;合法起点为 \(0,s,2s,\ldots\le d_H+2p-r(f-1)-1\)⁠,计数即得公式。

数据规模

  采用 NCHW 排列时,一批输入图像的维度为 \(m\times d_C\times d_H\times d_W\)⁠:依次表示图片数、输入通道数、高和宽。若要产生 \(d'_C\) 个输出通道,每个卷积核的空间尺寸为 \(f_1\times f_2\)⁠,则全部卷积核的维度为 \(d'_C\times d_C\times f_1\times f_2\)⁠。

常见误区

  整数除法截断意味着部分边界位置可能被舍弃;同一个 Same 设置在偶数核、步幅大于 1 时还依赖框架的上下和左右填充约定。

动手检查

  用全 1 输入、单位脉冲输入和非对称核核对索引、核方向与输出尺寸,再与框架 conv2d 在相同约定下比较。

数值稳定性与规模

  先用整数公式验证输出尺寸非负;卷积累加采用足够精度。后向检查用 float64 小张量和 中心差分⁠,窗口重叠处必须累加而不是覆盖。

本节小结#

  1. 我们所提到的卷积通常是离散互相关。

  2. 输出尺寸来自有效核与合法起点计数。

  3. 多通道输出需要先跨输入通道求和。

综合练习#

  程序题应固定随机种子、写出维度断言并报告运行环境;自行实现与框架函数的数值比较应遵循 手算结果与可信实现对照 的原则。比较题还应固定数据划分、随机种子集合和训练预算。全部参考答案见 离散卷积与多通道互相关答案⁠。

  1. 输出尺寸推导。 对长度为 \(d\) 的输入,在两端各填充 \(p\) 个元素,卷积核大小为 \(f\)⁠、空洞率\(r\)⁠、步幅为 \(s\)⁠。从合法窗口起点的计数出发,推导 \(d'=\lfloor[d+2p-r(f-1)-1]/s+1\rfloor\)⁠,并写出输出存在的条件。

  2. 多通道互相关计算。 输入有两个 \(3\times3\) 通道,第一通道为 \(\begin{bmatrix}1&2&0\\0&1&3\\2&1&0\end{bmatrix}\)⁠,第二通道元素全为 1。一个输出通道使用两个 \(2\times2\) 核:第一通道核元素全为 1,第二通道核元素全为 \(-1\)⁠,偏置为 0。按步幅 1、无填充计算完整输出。

  3. 参数量与计算量。 某卷积层输入通道数为 3、输出通道数为 16,核大小为 \(3\times3\)⁠,每个输出通道有一个偏置;输入空间尺寸为 \(32\times32\)⁠,步幅为 1 且保持空间尺寸不变。计算参数量以及一次单样本前向传播中卷积部分的乘加次数。

  4. 平移等变性。 根据正文对 平移等变性⁠、卷积计算 \(\mathcal C\) 与平移运算 \(T_\delta\) 的定义,在无限网格或忽略边界、步幅为 1 的条件下,用下标变换证明 \(\mathcal C(T_\delta\bX)=T_\delta\mathcal C(\bX)\)⁠。再分别说明有限填充边界和步幅大于 1 为什么会限制该结论。

  5. 核心实现。 用 NumPy 实现 NCHW 约定下的多通道二维互相关,支持批量、偏置以及两个方向可以分别设置的填充、步幅和空洞率;函数应检查通道数与输出尺寸,并返回输出及便于核验的窗口信息。

  6. 实现与库对照。 在相同输入、核和约定下,实现朴素循环版与 im2coleinsum 版,并与 PyTorch conv2d 比较输出;使用非对称核专门检查程序是否误翻转卷积核。

  7. 测试设计。 至少测试:单位脉冲输入、全 1 输入、非对称核、多输入/输出通道、步幅和空洞率组合、最小合法尺寸、非法通道数,以及理论输出尺寸与数组维度一致。

  8. 实现效率比较。 把朴素循环、向量化和框架卷积嵌入权重相同的小型分类器。固定数据划分、随机种子集合、训练预算和硬件;报告任务性能、数值误差、训练时间、固定批量预测时间、参数量及峰值内存。

  9. 感受野配置比较。 根据正文对 感受野 的介绍,比较 \(5\times5\) 普通卷积、两个串联的 \(3\times3\) 卷积以及 \(3\times3\)空洞卷积⁠。固定数据划分、随机种子集合、训练预算,并尽量匹配通道数;报告任务性能、训练时间、固定批量预测时间、参数量、乘加量及峰值内存。

  10. 填充策略比较。 在同一图像分类任务中比较 Valid 填充与保持空间尺寸的 Same 填充,并相应调整分类输出层以保证输出类别相同。固定数据划分、随机种子集合、训练预算和评价程序;报告任务性能、训练时间、固定批量预测时间、参数量及峰值内存,并解释边界信息与特征图尺寸的影响。