卷积与感受野数学:5×5 输入、3×3 kernel、padding 和 im2col
卷积与感受野数学:5×5 输入、3×3 kernel、padding 和 im2col
站内搜索
直接问 AI

卷积与感受野数学:5×5 输入、3×3 kernel、padding 和 im2col

把全连接层换成卷积层,参数量会掉几个数量级,而效果反而更好。原因不在于卷积「更先进」,在于它对图像做了三个假设:特征是局部的、同一个特征在哪个位置都算数、以及像素之间的空间关系有意义。这三条恰好是全连接层完全没有利用的信息。

这篇先算清楚输出尺寸的公式(padding 和 stride 各自在里面起什么作用),再讲感受野——一个输出像素到底「看到」了原图多大一块区域,以及为什么它随层数增长得比直觉慢。最后用 Numpy 手写一遍二维卷积,把前面的公式落到代码上。

一、卷积输出尺寸的数学公式

当您在输入张量上滑动卷积核时,输出特征图的空间尺寸取决于四个因素:输入尺寸 ($W, H$)、卷积核大小 ($K$)、填充/Padding ($P$) 和步长/Stride ($S$)。

计算输出尺寸的通用公式为:

Output_Size = floor((Input_Size + 2 * Padding - Kernel_Size) / Stride) + 1

我们来拆解一个经典场景:输入图像为 5x5,卷积核为 3x3,填充为 0 (Valid 卷积),步长为 1。代入公式:

floor((5 + 2*0 - 3) / 1) + 1 = 3

因此,输出的特征图精确地为 3x3。如果我们希望输出保持 5x5 的尺寸,则需要添加 1 的填充 (Same 卷积),假设步长依然为 1

二、手算单个输出像素点

卷积的一个输出值,等于输入的一个局部图像块(Patch)与卷积核矩阵之间逐元素相乘后的总和。这本质上就是一个点积操作。

5x5 输入、3x3 卷积核和 3x3 输出特征图
高亮图像块与卷积核做点积,得到输出特征图上的一个像素。

实验包输出的 conv2d-results.csv 显示中间位置 row=1,col=1 的结果是 -1.000000。如果您手动把高亮的 3x3 区域和卷积核对齐相乘并求和,也会得到一模一样的结果。

三、不断扩张的感受野 (Receptive Field)

感受野 (Receptive Field, RF) 是指神经网络中某个特征影响了原始输入空间多大的区域。单层 3x3 卷积只能看到 3x3 的局部区域。然而,深度神经网络堆叠了多个卷积层,网络到底是如何“看懂”整张图片的呢?

当你在第一层上再堆叠一个 3x3 卷积层时,第二层的一个输出神经元连接到第一层隐藏层中 3x3 的区域。但是,这 9 个神经元中的每一个,都分别连接到原始输入中 3x3 的区域。因此,第二层中的单个神经元在原始输入上具有 5x5 的有效感受野。


graph TD
    sublayer_2["第 2 层 (1x1 输出)"] --> sublayer_1["第 1 层 (3x3 特征图)"]
    sublayer_1 --> input["原始输入 (5x5 感受野)"]
    
    style sublayer_2 fill:#f9f,stroke:#333,stroke-width:2px
    style sublayer_1 fill:#bbf,stroke:#333,stroke-width:2px
    style input fill:#bfb,stroke:#333,stroke-width:2px

在数学上,第 $l$ 层的感受野尺寸 $RF_l$ 可以用以下公式计算:

RF_l = RF_{l-1} + (Kernel_Size_l - 1) * Stride_Product_{i=1 to l-1}

这就解释了为什么卷积网络可以从检测微小的边缘开始,逐渐组合以识别复杂的纹理、形状,最终识别出人脸或汽车等完整对象。

四、代码实现:im2col 与矩阵乘法

在工程实践中,使用嵌套循环(滑动窗口)遍历图像的速度极其缓慢。现代深度学习框架(如 PyTorch 和 TensorFlow)通过将卷积转换为庞大的矩阵乘法来对该操作进行向量化加速。这种技术被称为 im2col (Image to Column)。

im2col 会从输入图像中提取每个局部块(Patch),将其展平为一维向量,并将它们堆叠成一个大型矩阵。卷积核也会被展平。随后,卷积操作就变成了一次高度优化的矩阵乘法 (GEMM)。


import numpy as np

def conv2d_im2col(image, kernel, stride=1):
    """
    一个使用 im2col 实现 2D 卷积的实用 Numpy 代码。
    """
    h_in, w_in = image.shape
    k_h, k_w = kernel.shape
    
    # 计算输出尺寸
    out_h = (h_in - k_h) // stride + 1
    out_w = (w_in - k_w) // stride + 1
    
    # 提取图像块 (im2col 步骤)
    # cols 矩阵的形状: (out_h * out_w, k_h * k_w)
    cols = []
    for r in range(0, h_in - k_h + 1, stride):
        for c in range(0, w_in - k_w + 1, stride):
            patch = image[r:r+k_h, c:c+k_w]
            cols.append(patch.reshape(-1))
            
    im_matrix = np.vstack(cols)
    
    # 展平卷积核
    weight_matrix = kernel.reshape(-1, 1)
    
    # 执行矩阵乘法
    result = im_matrix @ weight_matrix
    
    # 将结果 reshape 回特征图的尺寸
    return result.reshape(out_h, out_w)

# 测试实现
test_img = np.arange(25).reshape(5, 5)
test_kernel = np.ones((3, 3))
output = conv2d_im2col(test_img, test_kernel)
print("输出形状:", output.shape)
print(output)

对于 5x5 输入和 3x3 卷积核,共有 9 个有效图像块,每个块包含 9 个值。生成的 im_matrix 形状将是 (9, 9)

五、卷积维度检查表

卷积代码最常见的问题不是公式不会写,而是张量维度在某一层悄悄错了。下面这张表可以在调试模型时逐层检查。

检查项 应该确认什么 常见错误
输入格式 NCHW 还是 NHWC 通道维和高度维写反
输出尺寸 是否符合公式 padding 或 stride 少写导致 flatten 失败
感受野 深层特征是否覆盖足够输入区域 网络太浅,只能看到局部纹理
参数量 out_channels * in_channels * k_h * k_w 通道数暴涨导致显存超限
边界填充 zero / reflect / replicate 是否符合任务 边缘目标预测明显差

六、动画演示核心过程

动画展示了卷积窗口扫描、输出像素填充以及感受野扩大的全过程。

在观看动画时,请注意两个核心属性:相同的卷积核权重在许多空间位置被重复使用(这就是权重共享),并且每个输出最初只“看到”输入的一个局部区域(这就是局部连接)。

七、个人经验与工程师视角

在真实的业务场景中使用卷积神经网络,会遇到一些单看数学公式很难发现的工程挑战:

显存与算力的博弈: im2col 方法非常巧妙,它通过矩阵乘法充分压榨 GPU 核心的算力,但代价极其昂贵:内存复制冗余。通过提取重叠的图像块,im2col 会成倍地膨胀输入张量的显存占用。如果您处理的是大型医疗影像(如 3D CT 扫描),调用 im2col 很容易直接导致 Out-Of-Memory (OOM) 报错。在生产级的 C++/CUDA 开发中,我们通常采用显存效率更高的隐式 GEMM (Implicit GEMM) 或 Winograd 算法。

  • 维度不匹配的噩梦: 初级工程师遇到的最常见的错误,就是从最后一个卷积层过渡到第一个全连接(Dense)层时,突然跳出 RuntimeError: size mismatch。在执行 flatten() 操作之前,务必养成习惯:打印张量形状,或者使用输出尺寸公式手动验算最后的尺寸。
  • 棋盘伪影 (Checkerboard Artifacts): 在生成对抗网络 (GANs) 中使用转置卷积(常被误称为反卷积)进行上采样时,您经常会遇到棋盘状的伪影。当卷积核的大小不能被步长整除时,就会发生这种现象。我常用的一个实用解决方案是:放弃转置卷积,改用“最近邻插值上采样 (Nearest-neighbor upsample) + 标准步长为 1 的普通卷积”组合来替代。
  • Padding 对边缘的影响: 补零 (Zero-padding) 是框架的默认选项,但它人为地在特征图中引入了黑色边缘。如果您发现模型在处理图像边缘的对象时性能不佳,可以考虑将其切换为 “Reflect” 或 “Replicate” 填充模式。

下一篇,我们将离开死板、局部连接的卷积,进入 Transformer 中极其灵活、基于全局 Token-to-Token 交互的注意力机制世界。

发表回复

向下探索