如前所述,标准前馈网络独立处理输入特征,因此会丢失图像等数据中内含的空间关系。卷积神经网络通过使用一种称为卷积的特殊操作作为其主要构件来解决此问题。此操作使网络能够识别和检测输入中的局部模式,从而维持空间层级。滤波器(核):一种特征检测器卷积操作的中心是滤波器,也称为核。可以将滤波器视为一个小的、可学习的权重矩阵。它的作用是扫描输入数据并识别特定的特征或模式。例如,在图像中,一个滤波器可以学会检测垂直边缘,另一个可以检测水平边缘,还有一个可能对特定的纹理或颜色梯度有强烈反应。滤波器在空间上通常较小(例如 3x3、5x5 像素),但会延伸到输入体量的完整深度。如果输入是彩色图像(具有红、绿、蓝通道),则滤波器的深度也将为 3。卷积过程:滑动与计算卷积操作包含系统地将滤波器在输入数据(例如图像或来自前一层的特征图)上滑动。在每个位置,滤波器会覆盖输入的一小块区域。主要计算包含三个步骤:逐元素相乘: 将滤波器中的权重乘以其当前覆盖的输入区域中的对应值。求和: 将所有逐元素相乘的结果相加。添加偏置(可选): 通常,一个可学习的偏置项会加到总和中。这个单独计算出的值(总和 + 偏置)表示滤波器在输入中该特定位置的响应。一个强烈的正值表明滤波器旨在识别的特征在该位置强烈存在。然后,滤波器滑动到下一个位置,该过程重复进行,直到覆盖整个输入。graph G { layout=neato; node [shape=square, width=0.4, height=0.4, label=""]; edge [style=invis]; subgraph cluster_input { label="输入区域(例如 5x5)"; style=dashed; bgcolor="#e9ecef"; node [style=filled, fillcolor="#dee2e6"]; i00 [pos="0,2!", label="I00"]; i01 [pos="0.5,2!", label="I01"]; i02 [pos="1,2!", label="I02"]; i03 [pos="1.5,2!", label="I03"]; i04 [pos="2,2!", label="I04"]; i10 [pos="0,1.5!", label="I10"]; i11 [pos="0.5,1.5!", label="I11"]; i12 [pos="1,1.5!", label="I12"]; i13 [pos="1.5,1.5!", label="I13"]; i14 [pos="2,1.5!", label="I14"]; i20 [pos="0,1!", label="I20"]; i21 [pos="0.5,1!", label="I21"]; i22 [pos="1,1!", label="I22"]; i23 [pos="1.5,1!", label="I23"]; i24 [pos="2,1!", label="I24"]; i30 [pos="0,0.5!", label="I30"]; i31 [pos="0.5,0.5!", label="I31"]; i32 [pos="1,0.5!", label="I32"]; i33 [pos="1.5,0.5!", label="I33"]; i34 [pos="2,0.5!", label="I34"]; i40 [pos="0,0!", label="I40"]; i41 [pos="0.5,0!", label="I41"]; i42 [pos="1,0!", label="I42"]; i43 [pos="1.5,0!", label="I43"]; i44 [pos="2,0!", label="I44"]; } subgraph cluster_filter { label="滤波器 (3x3)"; style=dashed; bgcolor="#ffec99"; node [style=filled, fillcolor="#ffe066"]; f00 [pos="0.25,1.75!", label="F00"]; f01 [pos="0.75,1.75!", label="F01"]; f02 [pos="1.25,1.75!", label="F02"]; f10 [pos="0.25,1.25!", label="F10"]; f11 [pos="0.75,1.25!", label="F11"]; f12 [pos="1.25,1.25!", label="F12"]; f20 [pos="0.25,0.75!", label="F20"]; f21 [pos="0.75,0.75!", label="F21"]; f22 [pos="1.25,0.75!", label="F22"]; } subgraph cluster_output { label="输出值"; style=dashed; bgcolor="#a5d8ff"; node [style=filled, fillcolor="#74c0fc"]; out [pos="3,1.25!", label="Sum(I*F)+b"]; } # Edges to show calculation dependency edge [style=solid, color="#adb5bd", arrowhead=none]; f00 -- i01; f01 -- i02; f02 -- i03; f10 -- i11; f11 -- i12; f12 -- i13; f20 -- i21; f21 -- i22; f22 -- i23; edge [style=solid, color="#495057", arrowhead=vee]; node [style=invis]; dummy [pos="1.5,1.25!"] dummy -- out [minlen=1.5]; # Indicate sliding node [shape=none, label="滤波器在输入上\n滑动"]; slide_lbl [pos="1.5, -0.75!"]; }一个 3x3 的滤波器覆盖输入的一个区域。对应元素相乘,结果求和(加偏置)以在输出特征图中生成一个值。然后滤波器移动到下一个位置。特征图(激活图)通过将单个滤波器应用于整个输入所得到的输出是一个 2D 矩阵,称为特征图或激活图。该图中的每个元素代表着滤波器在输入中特定空间位置的响应。高激活值表示滤波器识别出的特征在该位置强烈存在。通常,一个卷积层会使用多个滤波器(例如 32、64 或更多),每个滤波器初始化不同,从而识别不同的特征。将所有这些滤波器应用于相同的输入体量,会形成一系列 2D 特征图,构成卷积层的输出体量。此输出体量的深度等于所用滤波器的数量。控制滑动:步幅步幅规定了滤波器在输入上移动时的步长。步幅为 1 ($S=1$) 表示滤波器每次移动一个像素(水平和垂直方向)。这会导致感受野重叠,并通常生成更大的输出特征图。步幅为 2 ($S=2$) 表示滤波器每次跳过两个像素。这会导致更少的重叠,更快地处理输入,并生成更小的输出特征图(下采样)。选择步幅是一个设计决策,会影响输出的空间维度和计算成本。维持维度:填充当应用滤波器时,尤其是在输入边界附近,输出特征图的尺寸自然会比输入小。此外,输入最边缘的像素被滤波器覆盖的次数少于中心像素,这可能会引起信息丢失。填充通过在应用卷积之前在输入体量的边界周围添加额外像素(通常值为零)来处理这些问题。有两种常见的填充策略:有效填充(Valid Padding): 这表示不添加任何填充。输出尺寸将小于输入尺寸。输出维度的公式(假设输入为 $W \times W$ 的正方形,滤波器为 $F \times F$,步幅为 $S$)是: $$ W_{out} = \lfloor \frac{W - F}{S} \rfloor + 1 $$相同填充(Same Padding): 这里的目的是添加足够的零填充,使得输出特征图的高度和宽度与输入特征图相同(当使用步幅 $S=1$ 时)。这非常常见,因为它有助于构建更深的网络,而无需顾虑每一层的空间维度急剧缩小。对于“相同填充”(步幅为 $S=1$),所需的填充量 $P$ 通常为 $P = (F - 1) / 2$(适用于奇数滤波器尺寸)。卷积总结简而言之,卷积操作使用可学习的滤波器扫描输入数据,进行逐元素乘法和求和以生成特征图。这些图显示了特定模式(由滤波器学到)在不同空间位置的存在。滤波器数量、滤波器尺寸、步幅和填充等参数可以控制特征的提取方式以及输出的空间维度。此过程是 CNN 有效处理图像等网格状数据的基础。这是一个使用 PyTorch 定义 2D 卷积层的简单示例:import torch import torch.nn as nn # 示例:3 通道输入(例如 RGB 图像),批次大小 1 # 输入维度:(批次大小, 通道数, 高度, 宽度) input_tensor = torch.randn(1, 3, 64, 64) # 定义一个卷积层 # in_channels=3: 与输入深度(RGB)匹配 # out_channels=16: 我们希望使用 16 个不同的滤波器 # kernel_size=3: 每个滤波器将是 3x3 # stride=1: 滤波器每次移动一个像素 # padding=1: 使用“相同”填充(对于 3x3 核,步幅 1) conv_layer = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1) # 应用卷积 output_feature_map = conv_layer(input_tensor) # 打印输出形状 # 输出:torch.Size([1, 16, 64, 64]) # 批次大小 1,16 个特征图(每个滤波器一个),高度 64,宽度 64 # 注意:由于 padding='same' (padding=1) 和 stride=1,高度和宽度保持为 64。 print(output_feature_map.shape) 这种基本操作在多个层中重复进行,让 CNN 能够学到分层特征,从早期层中的简单边缘和纹理发展到后续层中更复杂的物体部分。