卷积神经网络基础:卷积运算原理、特征图与感受野、标准卷积的FLOPs计算
好,咱们正式开始啃卷积神经网络(CNN)这块硬骨头。说实话,很多搞算法的人把CNN当黑盒用,调个参数就完事。但我个人习惯是,先把最底层的运算逻辑吃透,后面做稀疏加速时才能游刃有余。
这一节,咱们就聚焦三个核心问题:卷积到底怎么算的?特征图上的一个点对应原图多大区域?一次标准卷积要多少计算量?
卷积运算原理:从数学到工程
先别被“卷积”这名字吓到。在CNN里,它其实就是一种滑动窗口的加权求和。
想象一下,你有一个小窗口(叫卷积核或滤波器),在输入图像上从左到右、从上到下地滑动。每到一个位置,就把窗口内的像素值与卷积核上对应的权重相乘,然后把所有乘积加起来,得到一个输出值。
核心公式(2D卷积,单通道):
输出特征图 (i, j) = Σ_m Σ_n [ 输入 (i+m, j+n) × 卷积核 (m, n) ] + 偏置
其中 m, n 遍历卷积核的行和列。
举个例子。假设输入是 5×5 的矩阵,卷积核是 3×3 的矩阵,步长(stride)为1,无填充(padding)。
输入矩阵(5×5):
[[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10],
[11,12,13,14,15],
[16,17,18,19,20],
[21,22,23,24,25]]
卷积核(3×3):
[[1, 0, -1],
[1, 0, -1],
[1, 0, -1]]
输出特征图第一个元素(左上角)的计算过程:
= (1×1 + 2×0 + 3×(-1)) + (6×1 + 7×0 + 8×(-1)) + (11×1 + 12×0 + 13×(-1))
= (1 + 0 - 3) + (6 + 0 - 8) + (11 + 0 - 13)
= (-2) + (-2) + (-2)
= -6
嗯,这里要注意:卷积核的权重是网络学习出来的,不是我们手工设计的。不同的卷积核能提取不同的特征,比如边缘、纹理、颜色变化等。
我的经验:刚开始学的时候,我总把卷积和互相关搞混。在数学上,卷积需要把核翻转180度再滑动,但深度学习框架(如PyTorch、TensorFlow)实现的其实是互相关运算。不过这不影响模型训练,因为核的权重是学出来的,翻转不翻转都能学到有效特征。你只要知道框架里“卷积”函数实际做的是互相关就行。
特征图与感受野
卷积层输出的结果,我们叫特征图(Feature Map)。特征图上的每一个像素点,都对应着输入图像上的一个区域。这个区域的大小,就是感受野(Receptive Field)。
为什么会这样?你想想看,一个3×3的卷积核,它一次只能“看到”输入上3×3的区域。所以输出特征图上的一个点,它的感受野就是3×3。
但问题来了:如果堆叠多个卷积层呢?
假设我们有两个3×3的卷积层,步长都为1。第一层输出的特征图,每个点的感受野是3×3。第二层在这个特征图上再做卷积,第二层输出的每个点,它的感受野就变成了5×5。因为它“间接”看到了第一层3×3区域周围的更多信息。
感受野计算公式(简化版):
RF_{l} = RF_{l-1} + (kernel\_size - 1) × stride\_product
其中 stride_product 是之前所有层步长的乘积。
我用一个表格来展示不同层数的感受野变化(假设所有层 kernel_size=3, stride=1):
| 层数 | 感受野大小 | 说明 |
|---|---|---|
| 1 | 3×3 | 直接看到输入上的3×3区域 |
| 2 | 5×5 | 间接看到更大的上下文 |
| 3 | 7×7 | 感受野随层数线性增长 |
| 4 | 9×9 | 深层网络能捕获全局信息 |
我曾经踩过的坑:在做图像分割任务时,我设计了一个网络,用了很多3×3的小卷积核,但下采样步长设得太大。结果发现,深层特征图的感受野虽然理论值很大,但因为步长导致空间分辨率骤降,很多细节信息丢失了。后来我改用空洞卷积(Dilated Convolution)来扩大感受野,同时保持分辨率。所以,感受野大不等于效果好,还要考虑特征图的分辨率。
标准卷积的FLOPs计算
FLOPs(Floating Point Operations,浮点运算次数)是衡量模型计算量的关键指标。对于标准卷积,它的计算量主要来自乘法和加法。
咱们直接上公式。对于一个卷积层:
- 输入特征图大小:H_in × W_in × C_in
- 卷积核大小:K × K
- 输出通道数:C_out
- 输出特征图大小:H_out × W_out
一次卷积操作的FLOPs:
一次卷积 = K × K × C_in 次乘法 + (K × K × C_in - 1) 次加法 ≈ 2 × K × K × C_in
注意,这里我们把乘法和加法都算作一次浮点运算。有些框架只算乘法,但业界主流是乘加一起算。
整个卷积层的FLOPs:
总FLOPs = H_out × W_out × C_out × (2 × K × K × C_in)
举个例子。假设输入是 224×224×3,卷积核是 3×3,输出通道是 64,输出特征图大小是 224×224(假设padding=1, stride=1)。
总FLOPs = 224 × 224 × 64 × (2 × 3 × 3 × 3)
= 224 × 224 × 64 × 54
≈ 173,408,256 次浮点运算
≈ 173.4 MFLOPs
我的习惯:在项目初期,我会用这个公式快速估算模型的计算量。比如,一个ResNet-50大约有3.8 GFLOPs,而MobileNetV2只有约0.3 GFLOPs。这个数量级差异,直接决定了模型能否在移动端或嵌入式设备上实时运行。另外,FLOPs和模型参数量(Params)是两个概念,参数量只跟权重数量有关,而FLOPs跟输入大小和计算过程都有关。
知识体系总览
为了让你更直观地理解这一节的知识结构,我画了一张图:
这张图把三个核心知识点串联起来了。从左到右,从原理到量化,是一个完整的认知链条。我个人觉得,只有把这三个点都吃透,你才能理解为什么稀疏化能加速卷积——因为很多权重和激活值其实是冗余的,我们可以跳过那些不必要的计算。