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

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

卷积代码和配图算出不同数值时,随意改 padding 并不能定位问题。应该先固定输入、卷积核方向和图像块展开顺序。本篇用一个非对称卷积核让这些差异显现出来,再检查 stride、dilation、边界填充和感受野。

实验修订日期:2026 年 9 月 8 日。下面的正文代码、手算和原静态图已经统一为同一组输入。卷积审计实验包包含完整程序、CSV 和测试。旧版 Deep Learning Math Lab 仍能复现基础的 -1 示例,新包额外提供形状与敏感度检查。

一、先明确:这里计算的是什么运算

本文采用神经网络中常见的约定:图像块与卷积核直接逐元素相乘,不翻转卷积核。对于实数输入,这个运算是互相关;数学卷积则需将核沿两个空间轴翻转。全一这样的对称核会掩盖差别,非对称核不会。PyTorch 的 Conv2d 定义也明确采用互相关。

局部连接与权重共享是卷积施加的结构约束,全连接层没有同样的约束。这能减少特定配置下的参数量,但不能保证任务效果更好。带 bias 的分组卷积参数量为 Cout * (Cin / groups) * kh * kw + Cout,并要求通道数满足整除条件。本文程序只实现单通道,不含 bias 或 groups。

二、输出尺寸不能漏掉 dilation 和两侧填充

effective_kernel = dilation * (kernel_size - 1) + 1
output_size = floor((input_size + pad_before + pad_after
                     - effective_kernel) / stride) + 1

高度与宽度分别代入。把两侧填充写成 2P,隐含的是对称填充;直接使用 kernel_size 而不考虑扩张,隐含的是 dilation=1。步长和扩张率应为正整数,实验包会拒绝有效卷积核无法容纳在填充后图像中的情况。

5×5 输入、3×3 核 输出
stride 1,padding 0,dilation 1 3×3
stride 1,padding 1,dilation 1 5×5
stride 2,padding 0,dilation 1 2×2
stride 1,padding 0,dilation 2 1×1
stride 2;padding (1,0,2,1);dilation 1 2×3

填充元组顺序为上、下、左、右,表中各项均由 conv_core.py 实际运行。dilation=2 的 3×3 核在每个轴上跨越五个像素,但只采样其中三个位置。“Same”也有接口约定:上面链接中的 PyTorch 模式只支持 stride=1,不能把某个库的简写直接推广到所有框架或步长。

三、手算的中心值必须与程序使用同一组输入

Input                         Kernel
[[1, 2, 0, 1, 3],             [[1, 0, -1],
 [0, 1, 2, 2, 1],              [1, 0, -1],
 [3, 1, 0, 2, 0],              [0, 1,  0]]
 [2, 2, 1, 0, 1],
 [1, 0, 3, 1, 2]]

Center patch                  Elementwise products
[[1, 2, 2],                   [[1, 0, -2],
 [1, 0, 2],                    [1, 0, -2],
 [2, 1, 0]]                    [0, 1,  0]]

Output
[[0,  0, 0],
 [3, -1, 1],
 [4,  4, 1]]

中心位置的求和为 1 + 0 - 2 + 1 + 0 - 2 + 0 + 1 + 0 = -1。输出坐标从零开始,所以它是第 1 行、第 1 列;按行优先顺序展开后,对应 im2col 的第 4 行,因为 1 * output_width + 1 = 4。

与正文一致的五乘五输入、非对称卷积核和输出矩阵,中心结果为负一
高亮区域就是上面列出的中心图像块。与旧版示意动画不同,这张静态图的数值可以用正文程序逐项复核。
查看可直接运行的 im2col 示例
import numpy as np


def conv2d_im2col(image, kernel, stride=1):
    """Single-channel valid cross-correlation, with no kernel flip."""
    image, kernel = (np.asarray(a, dtype=np.float64) for a in (image, kernel))
    if image.ndim != 2 or kernel.ndim != 2 or 0 in image.shape or 0 in kernel.shape:
        raise ValueError("nonempty two-dimensional arrays required")
    if not np.isfinite(image).all() or not np.isfinite(kernel).all():
        raise ValueError("finite inputs required")
    if isinstance(stride, (bool, np.bool_)) or not isinstance(stride, (int, np.integer)) or stride < 1:
        raise ValueError("stride must be a positive integer")
    h, w = image.shape
    kh, kw = kernel.shape
    if kh > h or kw > w:
        raise ValueError("kernel must fit the input")
    out_h, out_w = (h - kh) // stride + 1, (w - kw) // stride + 1
    cols = np.stack([
        image[r:r + kh, c:c + kw].reshape(-1)
        for r in range(0, h - kh + 1, stride)
        for c in range(0, w - kw + 1, stride)
    ])
    return (cols @ kernel.reshape(-1)).reshape(out_h, out_w)


image = np.array([[1, 2, 0, 1, 3],
                  [0, 1, 2, 2, 1],
                  [3, 1, 0, 2, 0],
                  [2, 2, 1, 0, 1],
                  [1, 0, 3, 1, 2]], dtype=np.float64)
kernel = np.array([[1, 0, -1], [1, 0, -1], [0, 1, 0]], dtype=np.float64)

if __name__ == "__main__":
    output = conv2d_im2col(image, kernel)
    print("Output shape:", output.shape)
    print(output)
    print("Center:", output[1, 1])

这个最小程序只做 valid 单通道互相关。扩展的 conv_core.py 支持补零和 dilation,但两者都不是完整神经网络层,计算统一使用 float64。im2col 矩阵形状为 (9,9):每行对应一个输出位置,每个图像块展开为九个数。

四、用反例暴露“形状正确、数值错误”

计算方式 中心值
当前输入与未翻转的核 -1
同一输入,核沿两个轴翻转 +1
旧代码:arange(25) 配全一卷积核 108

旧代码的 108 来自另一组图像块求和,不是 -1 附近的浮点误差。保留为反例后,读者可以复现原先的不一致。另一个检查故意按列优先展开卷积核,却继续按行优先展开图像块,最大输出误差达到 6;仅检查 shape 无法发现这种顺序错误。

写新实现时,先用非方形图像、非方形卷积核和非对称核测试,再尝试大模型。扩展实验还检查了带负步幅的反向数组视图,并确认函数没有修改输入图像或卷积核。

五、已经执行的检查结果

参考环境为 CPU 上的 Python 3.13.9 和 NumPy 2.3.5。独立逐元素对照直接计算原图坐标,不使用 NumPy padding、滑动窗口、reshape 或矩阵乘法。固定种子 42,共尝试 640 组候选形状,前 512 组使用小整数,后 128 组使用正态分布数值。

检查 结果
有效形状对照 501 组通过
有效核无法容纳 139 组拒绝
最大数值误差 3.6e-15
明确构造的非法输入 12 类拒绝
感受野坐标对照 18 项通过

数值容差为 atol=1e-12、rtol=1e-12。非法输入包括零、小数或布尔步长,零 dilation,负数或格式错误的 padding,非有限值、空数组和意外的通道维。检查证明的是这些小数组的实现约定,不是 PyTorch 数值一致性、GPU 加速或模型精度。完整数值与源文件哈希见 audit.json。

六、感受野覆盖范围,不等于均匀影响

对于单条串行路径,每个轴跟踪三个量:理论感受野跨度 r,相邻输出中心在原图坐标中的间隔 j,以及第零个输出对应的最左坐标 a。初始值依次为 1、1、0。

a_new = a_old - pad_left * j_old
r_new = r_old + (kernel_size - 1) * dilation * j_old
j_new = j_old * stride

顺序很重要,扩大感受野时使用的是上一层累计间隔。Distill 的感受野推导讨论了串行路径、空间对齐,以及多分支情况下需要额外处理的问题。

依次经过的层 (r, j)
3×3,stride 1,dilation 1 (3, 1)
3×3,stride 2,dilation 1 (5, 2)
3×3,stride 1,dilation 2 (13, 2)

审计脚本用原图位置的直接枚举来核对递推式。单个三点扩张核可以只访问位置 0、2、4,理论跨度为五,但中间有空洞。padding 还可能让部分理论范围落在真实图像之外,所以只给一个尺寸,不能确定哪些像素实际参与了计算。

下面是另一个条件完整明确的敏感度实验:5×5 输入、两层 valid 的 3×3 全一卷积核、stride=1,不含 bias 或激活函数,最终输出一个标量。从全零输入出发,依次把每个输入像素加一,记录输出变化:

两层全一三乘三核的输入敏感度实测矩阵:角落为一,中心为九,二十五个位置均有连接
每个数来自一次独立输入扰动。5×5 的支持范围表示理论连接,系数不相等说明覆盖不等于均匀影响。这是固定的线性实验,不是训练后模型的有效感受野估计。

该矩阵等于 [1,2,3,2,1] 与自身的外积,角落系数为 1,中心为 9,全部系数之和为 81。独立解析表达式核对了全部 25 个实测值。它不能直接等同于 Luo 等人的有效感受野研究;后者关注深层网络中影响如何分布在理论支持范围内。

七、统计实际展开矩阵,不虚构 GPU 性能结论

本例 float64 数组 字节
5×5 输入 200
9×9 显式 im2col 矩阵 648
3×3 输出 72

这是实际 NumPy nbytes 数值,不是进程峰值内存。padding、reshape 或显式复制产生的临时数组、Python 对象与分配器开销还需另外计算。本例仅展开矩阵的负载就是原输入的 3.24 倍。

举一个只做估算的配置:batch=1、64 通道、224×224、float32,使用 stride=1、padding=1 的 3×3 核。输入负载为 12.25 MiB,完整显式展开矩阵为 110.25 MiB。脚本没有实际申请这些大数组,也没有由此声称观察到了 OOM 或测得加速比。

不能据此说所有框架都会申请这块展开矩阵。NVIDIA 卷积指南区分了隐式 GEMM、显式展开以及变换类算法,具体实现取决于后端和配置。边界填充同样是建模选择:归一化后的零未必代表黑色像素,改用反射填充也不保证精度提高。

八、复现后,一次只改变一个条件

解压实验 ZIP,在其目录运行:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements.txt
python conv_example.py
python audit_convolution.py --out results

成功时最后输出 CONVOLUTION_AUDIT_OK。默认 results 不会覆盖包内 reference;重新生成敏感度图需安装 requirements-plot.txt,再运行 plot_sensitivity.py。CSV 保留完整显示精度,跨平台复现按文档中的容差比较。

两个练习有明确预期:在原始输入上使用 dilation=2,应得到数值为 4 的 1×1 输出;改为 padding=1,应得到 5×5 输出,其左上角为 -2。先分别解释这些变化,再添加通道、groups、非线性或新后端。

下一篇可接着阅读Attention 与 KV Cache 实验。那里需要检查的是 Query/Key 的可见边界,而不是卷积核方向;两者都说明形状正确并不代表计算含义正确。

发表回复

向下探索