阅读 CNN 代码时,我先给每个张量写下形状,再检查卷积核、步长和通道数。这样比只记“提取局部特征”更容易发现实现错误。

四个维度分别是什么

PyTorch 的常见输入排列是 (N, C, H, W):批大小、通道数、高度、宽度。普通二维卷积的权重排列为 (C_out, C_in / groups, K_h, K_w);输出通道数来自不同的可学习卷积核,并不是输入 RGB 通道数的另一种叫法。

对单个空间方向,输出尺寸为:

L_out = floor((L_in + 2 * padding - dilation * (kernel - 1) - 1) / stride) + 1

使用偏置时,参数数量为:

C_out * (C_in / groups) * K_h * K_w + C_out

这些定义与分组约束见 PyTorch Conv2d 官方文档。框架通常实现的是互相关计算;不要因为接口叫卷积,就额外在代码里翻转一次卷积核。

一个不依赖深度学习框架的小检查

def output_size(length, kernel, stride=1, padding=0, dilation=1):
    if min(length, kernel, stride, dilation) <= 0 or padding < 0:
        raise ValueError("invalid convolution geometry")
    result = (length + 2 * padding - dilation * (kernel - 1) - 1) // stride + 1
    if result <= 0:
        raise ValueError("kernel does not fit")
    return result

def parameter_count(cin, cout, kernel, groups=1, bias=True):
    if min(cin, cout, kernel, groups) <= 0 or cin % groups or cout % groups:
        raise ValueError("invalid channels or groups")
    return cout * (cin // groups) * kernel * kernel + (cout if bias else 0)

assert output_size(32, 3, stride=2, padding=1) == 16
assert output_size(32, 3, dilation=2) == 28
assert parameter_count(3, 16, 3) == 448
assert parameter_count(3, 12, 3, groups=3) == 120

这只是形状和参数的算术检查,不是一个卷积训练实验。通过这些断言不代表模型已经学到了有效特征。

三处概念纠偏

  1. 卷积不等于天然的平移不变性。 共享权重意味着同一局部运算用于不同位置;输入移动时,特征也可能随之移动。边界、步长和池化会进一步影响结果,不能据此保证整网输出始终不变。
  2. 展平不等于信息在数学上被删除。 位置仍可由向量索引表示;问题主要在于普通全连接层没有直接采用卷积的局部连接、共享参数等归纳偏置。
  3. 训练与推理不是同一套 BN 行为。 常见设置下,训练使用当前批次统计并更新运行统计,推理使用运行统计。细节及 track_running_stats=False 的例外见 BatchNorm2d 官方文档。

练习

用纸笔计算两层卷积后的尺寸,再在框架中核对。把 groups 改成无法整除输入或输出通道的值,确认程序会拒绝;然后检查模型切换到 eval() 后,哪些层的行为改变。

课程对应与整理边界

对应疏锦行《26 计算机视觉专题》的《02_卷积神经网络》讲义与卷积、池化、BN、感受野视频目录。本文围绕实现核验重组知识,未复制讲义图示;视频口头补充尚未完整核验。

原课程