卷积层需要分别计算三件事:输出的空间尺寸、输出通道数、每个输出对应的输入范围。它们不能混为“卷积核大小”。本页按 PyTorch 的二维卷积参数记号推导,最后再计算参数量与计算量。
1. 张量与通道的含义
跳转到“1. 张量与通道的含义”批量图像输入通常写成 ,输出为 。 是批量大小; 是输入通道数; 由卷积层的 out_channels 指定,不要求等于输入通道数。
普通 groups=1 卷积中,每个输出通道都对所有输入通道的局部加权结果求和,再加一个偏置。PyTorch 的 Conv2d 实际使用互相关约定,即不先把核在空间方向翻转。PyTorch Conv2d
设分组数为 ,则 、 都应能被 整除。Conv2d 权重张量形状为:
因此“核的输入通道数等于输入通道数”只适用于不分组的情形。分组卷积每个输出通道只连接本组的 个输入通道;深度卷积是 且输出通道为输入通道整数倍的情况。
2. 从一维窗口推导普通卷积尺寸
跳转到“2. 从一维窗口推导普通卷积尺寸”高度和宽度分别计算。对其中任意一个方向,使用以下符号:
| 符号 | 含义 | 约束或常见默认值 |
|---|---|---|
| 输入长度 | 正整数 | |
| 核在该方向的采样点数 | 正整数 | |
| 该方向每一侧的对称填充量 | padding 默认 0 | |
| 相邻窗口起点的间隔 | stride 默认 1 | |
| 核内相邻采样点的间隔 | dilation 默认 1 |
没有空洞时 ,不是 。核的采样位置为 ,因此它覆盖的有效长度是:
例如 时采样位置为 ,有效长度为 5,但可学习的核权重仍只有 3 个。
填充后长度为 。若第 个窗口起点为 ,其末端不得越过填充后区域,所以:
从 开始数整数窗口,即得:
这里假设至少能放下一个窗口,否则参数组合无效。分母是实际步长 ,不能固定写成 2;取整必须保留。二维情形分别代入 和 。不对称填充时将 换为该方向两侧填充之和。官方输出形状定义
| 输入长度 | 有效核长 | 输出长度 | ||||
|---|---|---|---|---|---|---|
| 32 | 3 | 1 | 2 | 1 | 3 | 16 |
| 31 | 3 | 1 | 2 | 1 | 3 | 16 |
| 30 | 3 | 0 | 2 | 1 | 3 | 14 |
| 32 | 3 | 1 | 3 | 1 | 3 | 11 |
| 32 | 3 | 2 | 1 | 2 | 5 | 32 |
保持尺寸与整数倍下采样
跳转到“保持尺寸与整数倍下采样”当 且使用对称填充时,保持长度的条件是 。常见的 只是其中一例。
若仍满足 ,但步长改为 ,则:
只有输入长度能被步长整除时,输出才恰好是 ;不需要把步长限定为偶数。若对称整数 padding 无法满足条件,需考虑不对称填充。PyTorch padding='same' 的 Conv2d 目前只支持步长 1,应区分 API 选项和手工尺寸推导。
3. 转置卷积与 output_padding
跳转到“3. 转置卷积与 output_padding”转置卷积常用于可学习上采样。它对应卷积线性映射的转置关系,不是一般意义上能恢复原图的逆运算;输出尺寸恢复也不代表数值信息恢复。
在一个方向上,设 output_padding 为 ,输出长度为:
其中默认 。只有在 时,才化简为 。PyTorch ConvTranspose2d
可以从正向卷积的取整看出 的作用。设正向输入长度为 、输出长度为 ,余数为 :
反向选择原长度时便有:
由正向步长造成的多种可能尺寸,可用这里的 来区分。比如 时,正向输入 27 和 28 都得到 14;转置输入 14 时, 得到 27, 得到 28。
output_padding 用于消除尺寸歧义,不是在已经计算出的输出后面简单补一圈零。它不能任意取值;上面推导针对 的尺寸选择。官方 output_padding 说明
4. 多层感受野与累积步长
跳转到“4. 多层感受野与累积步长”感受野描述一个输出单元可能依赖的原始输入范围。令 是第 层的理论感受野边长, 是该层相邻单元在原始输入上的间隔;初始值为 。
前一层相邻单元已经相隔 个输入位置。当前核在前一层覆盖 个这样的间隔,所以:
应先使用上一层的 扩展感受野,再更新累积步长。原笔记的三层例子为:
| 层 | 的计算 | |||||
|---|---|---|---|---|---|---|
| 输入 | — | — | — | — | 1 | 1 |
| 第 1 层 | 3 | 1 | 0 | 1 | 1 | |
| 第 2 层 | 3 | 2 | 0 | 1 | 2 | |
| 第 3 层 | 3 | 1 | 0 | 1 | 2 |
如果只把第 3 层 dilation 改为 2,感受野变成 ,累积步长仍为 2。这个例子保留了原稿的 推导,并补齐空洞卷积情形。
以上递推针对串联的卷积/局部池化路径。padding 改变边界与中心位置,但不直接改变此处理论跨度递推;边界范围可能包含填充值。空洞可能导致范围内某些位置没有连接,理论感受野跨度也不同于训练后各像素实际影响强弱的“有效感受野”。有分支、跳连或全局注意力时,应分析各路径依赖再合并,不能直接套单路径表。
5. 参数量与计算量
跳转到“5. 参数量与计算量”设 表示使用偏置, 表示无偏置。分组 Conv2d 参数量为:
stride、padding、dilation 不直接增加核权重个数;但它们可能改变输出尺寸,因此影响计算量。参数量不随 batch 大小或输入图像面积成倍增加。
令每个输出单元参与点积的项数为 ,单张图像输出单元数为 。按直接点积计数:
这些是普通卷积的理论运算计数,包含对 padding 位置执行的常规乘加,不计激活、归一化和内存访问;实际算法可能不同。MAC 常按一次乘累加记 1,FLOPs 常把乘和加各记 1,故常用 估计。报告性能时应注明口径,不能混用 MACs 和 FLOPs;批量 张再乘 。
例如输入 ,Conv2d 输出通道 16, 且有偏置:输出为 ,参数量为 ,单张图像 MACs 为 。
6. 可复核的计算代码
跳转到“6. 可复核的计算代码”下面仅需 Python 标准库。// 在这些整数表达式中实现向下取整;窗口枚举从另一个角度核对尺寸公式。
def conv_size(length, kernel, stride=1, padding=0, dilation=1): if min(length, kernel, stride, dilation) < 1 or padding < 0: raise ValueError("invalid convolution parameters") effective = dilation * (kernel - 1) + 1 if length + 2 * padding < effective: raise ValueError("kernel does not fit") return (length + 2 * padding - effective) // stride + 1
def enumerate_windows(length, kernel, stride=1, padding=0, dilation=1): positions = range(0, length + 2 * padding, stride) return sum(start + dilation * (kernel - 1) < length + 2 * padding for start in positions)
cases = [ (32, 3, 2, 1, 1, 16), (31, 3, 2, 1, 1, 16), (30, 3, 2, 0, 1, 14), (32, 3, 3, 1, 1, 11), (32, 3, 1, 2, 2, 32),]for length, kernel, stride, padding, dilation, expected in cases: arguments = (length, kernel, stride, padding, dilation) assert conv_size(*arguments) == expected assert enumerate_windows(*arguments) == expected
assert (14 - 1) * 2 - 2 + (3 - 1) + 0 + 1 == 27assert (14 - 1) * 2 - 2 + (3 - 1) + 1 + 1 == 28field, jump = 1, 1for kernel, stride, dilation in [(3, 1, 1), (3, 2, 1), (3, 1, 1)]: field += dilation * (kernel - 1) * jump jump *= strideassert (field, jump) == (9, 2)assert 16 * 3 * 3 * 3 + 16 == 448assert 16 * 16 * 16 * 27 == 110592print("all examples passed")在已经安装 PyTorch 的环境中,还可用张量直接复核形状及参数量:
import torchfrom torch import nn
with torch.no_grad(): x = torch.zeros(1, 3, 32, 32) down = nn.Conv2d(3, 16, 3, stride=2, padding=1) y = down(x) assert tuple(y.shape) == (1, 16, 16, 16) assert sum(parameter.numel() for parameter in down.parameters()) == 448
up = nn.ConvTranspose2d(16, 3, 3, stride=2, padding=1, output_padding=1) assert tuple(up(y).shape) == tuple(x.shape) # 只验证尺寸,不表示恢复原值
grouped = nn.Conv2d(8, 12, 3, padding=1, groups=4) assert tuple(grouped.weight.shape) == (12, 2, 3, 3) assert sum(parameter.numel() for parameter in grouped.parameters()) == 228本次修订已执行标准库示例与窗口枚举核验;所检查的本机 Python 环境未安装 PyTorch,因此第二段保留为框架复核代码,不宣称已在本机运行。