
ConvTranspose2d 详细原理图
ConvTranspose2d 详细原理图
1. 它到底是什么
ConvTranspose2d 一般被叫作:
- 转置卷积
- 反卷积
- 上采样卷积
它最常见的作用不是“恢复原图”,而是:
- 把特征图空间尺寸放大
- 在放大的同时继续学习特征
- 用在解码器、分割、生成网络中
一句话理解:
Conv2d 更像“压缩和提取”,ConvTranspose2d 更像“展开和重建”。
2. 总体流程图
flowchart LR
A[输入特征图<br/>N x C_in x H x W]
B[按 stride 在元素之间插入空位<br/>直观上可理解为插零]
C[卷积核在更大网格上滑动]
D[重叠位置相加]
E[加上 bias]
F[得到输出特征图<br/>N x C_out x H_out x W_out]
A --> B --> C --> D --> E --> F
这个流程图里最关键的两点是:
stride > 1时,输出之所以变大,直观上可以理解为“先把网格拉开”- 卷积核覆盖到的多个位置会发生“重叠累加”
3. 最核心直觉
普通卷积常见理解:
小窗口在输入上滑动
-> 每次取一个局部区域
-> 做加权求和
-> 生成一个输出点
转置卷积的直观理解:
输入中的一个点
-> 通过卷积核“投影”到输出中的一片区域
-> 多个输入点投影到同一位置时再相加
所以你可以把它记成:
Conv2d : 一片输入 -> 一个输出点
ConvTranspose2d : 一个输入点 -> 一片输出区域
4. 一维直观图
先看一维,最容易理解。
设输入为:
[a, b, c]
设参数:
kernel_size = 3
stride = 2
padding = 1
4.1 直观上的“插零展开”
当 stride = 2 时,可以把它先理解为在元素之间插入 1 个空位:
原输入:
[a, b, c]
插零后:
[a, 0, b, 0, c]
注意:
这里的“插零”是帮助理解的视角,不一定等于底层实现真的先构造这样一个张量,但对理解输出变大非常有帮助。
4.2 卷积核开始作用
设卷积核权重为:
[w1, w2, w3]
那么:
- 输入位置
a会把自己的影响扩散到输出中的一段区域 - 输入位置
b也会扩散到另一段区域 - 两段区域如果有重叠,就在重叠处相加
示意:
a 对输出的贡献: [a*w1, a*w2, a*w3, 0, 0, 0, ...]
b 对输出的贡献: [ 0, b*w1, b*w2, b*w3, 0, 0, ...]
c 对输出的贡献: [ 0, 0, 0, c*w1, c*w2, c*w3, ...]
最终输出 = 三者逐位置相加
这就是转置卷积的本质直觉:
每个输入点都在“生成一小段输出”
最后把所有输入点生成的结果叠加起来
5. 二维详细原理图
现在看二维情况。
设输入特征图是一个 2 x 2 小矩阵:
输入 X:
+---+---+
| a | b |
+---+---+
| c | d |
+---+---+
设:
kernel_size = 3 x 3
stride = 2
padding = 1
5.1 第一步:按 stride 拉开网格
直观上把元素之间插入空位:
插零后的网格:
+---+---+---+
| a | 0 | b |
+---+---+---+
| 0 | 0 | 0 |
+---+---+---+
| c | 0 | d |
+---+---+---+
如果 stride = 3,则元素之间会插入更多空位。
所以:
stride 越大,输出特征图通常越大
5.2 第二步:卷积核覆盖输出区域
假设卷积核是:
K =
+----+----+----+
| k1 | k2 | k3 |
+----+----+----+
| k4 | k5 | k6 |
+----+----+----+
| k7 | k8 | k9 |
+----+----+----+
对输入中的单个元素 a 来说,它会对输出左上角附近一块 3 x 3 区域产生影响:
a 的贡献:
+------+------+------+
| a*k1 | a*k2 | a*k3 |
+------+------+------+
| a*k4 | a*k5 | a*k6 |
+------+------+------+
| a*k7 | a*k8 | a*k9 |
+------+------+------+
同理,b 会在更靠右的位置生成另一块 3 x 3 区域,c 会在更靠下的位置生成,d 会在右下生成。
5.3 第三步:重叠区域求和
由于这些 3 x 3 小块会互相覆盖,所以输出不是简单拼接,而是“重叠相加”。
示意图:
a 生成一块
+
b 生成一块
+
c 生成一块
+
d 生成一块
----------------
= 最终输出
更具体地说:
输出某个位置的值
= 该位置上所有输入点投影过来的贡献之和
+ bias
6. 逐点扩散图
这个角度尤其适合理解“为什么转置卷积能放大尺寸”。
输入中的每个点:
a b
c d
不会只生成一个输出点,
而是各自“铺开”为一个局部响应块。
示意:
a -> 左上区域
b -> 右上区域
c -> 左下区域
d -> 右下区域
把它画得更直观一点:
输入:
+---+---+
| a | b |
+---+---+
| c | d |
+---+---+
扩散后:
a 影响区域 b 影响区域
┌───────┐ ┌───────┐
│ * * * │ │ * * * │
│ * * * │ + │ * * * │
│ * * * │ │ * * * │
└───────┘ └───────┘
c 影响区域 d 影响区域
┌───────┐ ┌───────┐
│ * * * │ │ * * * │
│ * * * │ + │ * * * │
│ * * * │ │ * * * │
└───────┘ └───────┘
所有重叠位置相加 -> 输出
7. 输出尺寸公式图解
二维转置卷积输出大小公式:
H_out = (H_in - 1) * stride_h
- 2 * padding_h
+ dilation_h * (kernel_h - 1)
+ output_padding_h
+ 1
W_out = (W_in - 1) * stride_w
- 2 * padding_w
+ dilation_w * (kernel_w - 1)
+ output_padding_w
+ 1
7.1 每一项分别在干什么
(H_in - 1) * stride_h
表示输入点之间被“拉开”后的主尺度。
可以理解成:
输入有 H_in 个点
点和点之间有 H_in - 1 个间隔
每个间隔按 stride 拉开
- 2 * padding_h
表示对边界做回缩。
这里的 padding 和普通卷积里的直观感觉不完全一样,转置卷积里它更像是:
在输出尺寸公式里做反向抵消
+ dilation_h * (kernel_h - 1)
表示卷积核的实际覆盖范围。
如果有空洞卷积:
kernel 本身看起来还是 3x3
但有效感受野会更大
+ output_padding_h
这是一个“尺寸微调项”。
要特别注意:
output_padding 不是在输出右边真的拼一列零
它只是公式上的补偿项,用来解决某些尺寸歧义
+ 1
这是卷积尺寸公式推导里自然留下来的常数项。
8. 一个完整尺寸计算例子
设输入大小:
H_in = 32
W_in = 32
参数:
kernel_size = 4
stride = 2
padding = 1
output_padding = 0
dilation = 1
代入公式:
H_out = (32 - 1) * 2 - 2 * 1 + 1 * (4 - 1) + 0 + 1
= 31 * 2 - 2 + 3 + 1
= 64
W_out = 64
所以:
32 x 32 -> 64 x 64
这就是很多网络里常见的“放大 2 倍”配置:
nn.ConvTranspose2d(
in_channels=64,
out_channels=32,
kernel_size=4,
stride=2,
padding=1
)
9. 和普通卷积的关系图
9.1 空间直觉上的关系
Conv2d:
大图 -> 小图
ConvTranspose2d:
小图 -> 大图
9.2 矩阵角度上的关系
如果把卷积操作写成矩阵乘法:
y = W x
那么转置卷积对应的就是:
x' = W^T y
所以它叫“transpose convolution”。
注意:
转置 ≠ 逆
也就是说:
ConvTranspose2d 不是严格意义上把 Conv2d 完全还原回去
它只是对应卷积线性变换矩阵的“转置形式”。
10. padding 和 output_padding 最容易混淆的地方
10.1 padding
在普通卷积里,padding 往往理解成“在输入周围补零”。
但在转置卷积里,更推荐你这样理解:
padding 是输出尺寸公式中的回缩项
10.2 output_padding
它的作用不是“真的在输出后面补零”,而是:
当 stride > 1 时,
帮助我们在多个可能输出尺寸里选中想要的那个
比如某些情况下你想从:
15 -> 30
也可能想:
15 -> 31
这时就可能用到 output_padding=1。
11. 为什么它容易出现棋盘格伪影
转置卷积一个经典问题是:
checkerboard artifacts
棋盘格伪影
原因可以直观理解为:
- 不同输出位置被卷积核覆盖的次数可能不同
- 重叠不均匀时,不同位置的响应强度容易不均衡
- 尤其是
kernel_size和stride配合不合适时更明显
示意理解:
有些位置被 4 次覆盖
有些位置被 2 次覆盖
有些位置只被 1 次覆盖
-> 输出容易出现周期性纹理
常见缓解方式:
- 先上采样,再接普通卷积
- 选择更平衡的
kernel_size / stride - 使用插值上采样 + Conv2d
12. 最常见的工程配置
放大 2 倍
nn.ConvTranspose2d(
in_channels=64,
out_channels=32,
kernel_size=4,
stride=2,
padding=1
)
放大 4 倍
可以串联两次 2 倍上采样:
nn.ConvTranspose2d(64, 32, kernel_size=4, stride=2, padding=1)
nn.ConvTranspose2d(32, 16, kernel_size=4, stride=2, padding=1)
一般不建议一层直接暴力放太大,因为:
- 学习难度更高
- 伪影风险更高
- 特征恢复通常不如逐步上采样稳定
13. 最后用一句话记住
ConvTranspose2d 的核心不是“反着卷积”,
而是“每个输入点通过卷积核向更大的输出空间投影,并在重叠处相加”。
14. 你可以怎么配合代码看
如果你已经有 ConvTranspose2d 的实现文件,建议对照下面几部分一起看:
- 权重张量形状
- 输出尺寸公式
output_padding的求法forward里最终调用F.conv_transpose2d(...)
这样你会把“公式”和“代码”真正对上。