LK 博客
ConvTranspose2d 详细原理图
大数据
约 1 分钟阅读 0 赞 0 条评论 鸿蒙黑体

ConvTranspose2d 详细原理图

zyd
zyd @zyd
累计点赞 0 登录后每个账号只能点一次
内容长度 0 正文词元数
正文
目录会跟随阅读位置移动。
阅读进度

ConvTranspose2d 详细原理图

1. 它到底是什么

ConvTranspose2d 一般被叫作:

  1. 转置卷积
  2. 反卷积
  3. 上采样卷积

它最常见的作用不是“恢复原图”,而是:

  1. 把特征图空间尺寸放大
  2. 在放大的同时继续学习特征
  3. 用在解码器、分割、生成网络中

一句话理解:

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

这个流程图里最关键的两点是:

  1. stride > 1 时,输出之所以变大,直观上可以理解为“先把网格拉开”
  2. 卷积核覆盖到的多个位置会发生“重叠累加”

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]

那么:

  1. 输入位置 a 会把自己的影响扩散到输出中的一段区域
  2. 输入位置 b 也会扩散到另一段区域
  3. 两段区域如果有重叠,就在重叠处相加

示意:

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. paddingoutput_padding 最容易混淆的地方

10.1 padding

在普通卷积里,padding 往往理解成“在输入周围补零”。

但在转置卷积里,更推荐你这样理解:

padding 是输出尺寸公式中的回缩项

10.2 output_padding

它的作用不是“真的在输出后面补零”,而是:

当 stride > 1 时,
帮助我们在多个可能输出尺寸里选中想要的那个

比如某些情况下你想从:

15 -> 30

也可能想:

15 -> 31

这时就可能用到 output_padding=1

11. 为什么它容易出现棋盘格伪影

转置卷积一个经典问题是:

checkerboard artifacts
棋盘格伪影

原因可以直观理解为:

  1. 不同输出位置被卷积核覆盖的次数可能不同
  2. 重叠不均匀时,不同位置的响应强度容易不均衡
  3. 尤其是 kernel_sizestride 配合不合适时更明显

示意理解:

有些位置被 4 次覆盖
有些位置被 2 次覆盖
有些位置只被 1 次覆盖
-> 输出容易出现周期性纹理

常见缓解方式:

  1. 先上采样,再接普通卷积
  2. 选择更平衡的 kernel_size / stride
  3. 使用插值上采样 + 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)

一般不建议一层直接暴力放太大,因为:

  1. 学习难度更高
  2. 伪影风险更高
  3. 特征恢复通常不如逐步上采样稳定

13. 最后用一句话记住

ConvTranspose2d 的核心不是“反着卷积”,
而是“每个输入点通过卷积核向更大的输出空间投影,并在重叠处相加”。

14. 你可以怎么配合代码看

如果你已经有 ConvTranspose2d 的实现文件,建议对照下面几部分一起看:

  1. 权重张量形状
  2. 输出尺寸公式
  3. output_padding 的求法
  4. forward 里最终调用 F.conv_transpose2d(...)

这样你会把“公式”和“代码”真正对上。

作者名片

zyd
zyd
@zyd

这个作者暂时还没有填写个人简介。

评论区
文章作者和管理员都可以管理这里的评论。
0 条评论
登录后即可参与评论。 去登录
还没有评论,欢迎留下第一条交流内容。