PyTorch 中的转置卷积 ConvTranspose2d_深度学习

PyTorch 中的转置卷积 ConvTranspose2d

现有的关于转置卷积的介绍大多流于表面,并未详细的说明这一操作内部具体的操作流程。由于转置卷积的设计主要是为了对标标准卷积,所以其实现流程与标准卷积基本相反,所以内部的操作逻辑并不直观。其按照卷积的相反逻辑的参数设置方式,这种反逻辑的形式使得我们很难直接从参数的角度去理解。

​torch.nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1, padding_mode='zeros', device=None, dtype=None)​

This module can be seen as the gradient of Conv2d with respect to its input. It is also known as a fractionally-strided convolution or a deconvolution (although it is not an actual deconvolution operation as it does not compute a true inverse of convolution). For more information, see the visualizations ​​here​​​ and the ​​Deconvolutional Networks​​ paper.

这里面涉及到了多个参数,包括 ​​in_channels, out_channels, kernel_size, groups=1, bias=True, dilation=1, padding_mode='zeros', device=None, dtype=None​​​ 这样的一看就可以理解对的参数,也有一些实际情况和我们想的并不一致的参数 ​​stride=1, padding=0, output_padding=0​​。

首先要明确的一点,由于转置卷积可以看做是标准卷积的相反流程(虽然细节处理不同,并非真正的转置),考虑到标准卷积的直观性,所以我们可以从对应的标准卷积的角度去理解转置卷积的参数含义。即反着来看参数的作用。对于相同的参数设置,转置卷积输出和输出的形状,一般与标准卷积的输入和输出一致。

对于 2d 形式,我们分别设置 ​​kernel_size, stride, padding, output_padding​​​ 为 PyTorch 中的转置卷积 ConvTranspose2d_神经网络_02

  • 输入形状为 PyTorch 中的转置卷积 ConvTranspose2d_cnn_03 或者为 PyTorch 中的转置卷积 ConvTranspose2d_pytorch_04
  • 输出形状为 PyTorch 中的转置卷积 ConvTranspose2d_深度学习_05 或者为 PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_06
  • 卷积权重形状为 PyTorch 中的转置卷积 ConvTranspose2d_神经网络_07
  • 卷积偏置形状为 PyTorch 中的转置卷积 ConvTranspose2d_深度学习_08
  • PyTorch 中的转置卷积 ConvTranspose2d_神经网络_09
  • PyTorch 中的转置卷积 ConvTranspose2d_深度学习_10

转置卷积的过程

总体而言,结合对应的标准卷积,转置卷积的计算可以拆分为两部分:调整形状和局部聚合。这里的介绍可以结合 ​​A guide to convolution arithmetic for deep learning​​ 中提供的图示进行理解。

PyTorch 中的转置卷积 ConvTranspose2d_pytorch_11

PyTorch 中的转置卷积 ConvTranspose2d_神经网络_12

PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_13

PyTorch 中的转置卷积 ConvTranspose2d_深度学习_14

PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_15

PyTorch 中的转置卷积 ConvTranspose2d_cnn_16

PyTorch 中的转置卷积 ConvTranspose2d_神经网络_17

PyTorch 中的转置卷积 ConvTranspose2d_cnn_18

PyTorch 中的转置卷积 ConvTranspose2d_深度学习_19

PyTorch 中的转置卷积 ConvTranspose2d_神经网络_20

PyTorch 中的转置卷积 ConvTranspose2d_pytorch_21

PyTorch 中的转置卷积 ConvTranspose2d_神经网络_22

调整形状

实际上,转置卷积最难理解的还是这一步。

对输入使用 stride 处理,注意,转置卷积的 stride 并不同于标准卷积,而是在各个输入元素之间插入 PyTorch 中的转置卷积 ConvTranspose2d_深度学习_23

而且另外一点需要注意,为了确保对卷积输入输出运算过程形状的对应性,所以 在卷积核滑动之前,须要在输入的四边上进行 padding,这里默认 padding 的值与卷积核的形状有关,即四边的 padding 为 PyTorch 中的转置卷积 ConvTranspose2d_pytorch_24

这里需要强调的是,不论如何,转置卷积的本质还是卷积,仍然是对输入的局部聚合。所以如果不考虑 padding 和插 0 的情况,输出必然要比输入的尺寸要小。

所以当 PyTorch 中的转置卷积 ConvTranspose2d_pytorch_25

  1. PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_26:这是转置卷积的默认情况,此时转置卷积会对输入进行隐式的 padding,如前所述为 PyTorch 中的转置卷积 ConvTranspose2d_深度学习_27
  2. PyTorch 中的转置卷积 ConvTranspose2d_深度学习_28:相当于是标准卷积 PyTorch 中的转置卷积 ConvTranspose2d_深度学习_28 的时候。在其他参数不变的时候,标准卷积的输出会相对扩大,对应回来,也就是转置卷积的输出应该相对缩小。于是我们可以看到,PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_30 实际上起到一个反向调整的作用,即在默认的隐式 padding 上减去转置卷积设置中的 PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_30。因而实际的隐式 padding 会变为 PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_32

[!important] PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_30 的取值范围
结合上面的推理,进一步可以推测出转置卷积中对 PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_30

  • 首先必须大于等于 0,所以左边界为 0。
  • 对于右侧边界,必须要保证一点,即经过默认 padding 调整后的输入,在使用 PyTorch 中的转置卷积 ConvTranspose2d_深度学习_35 剪裁后剩下的必须大于等于 PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_36,也就是卷积操作必须有效。所以可以推算出,最大值为 PyTorch 中的转置卷积 ConvTranspose2d_深度学习_37PyTorch 中的转置卷积 ConvTranspose2d_神经网络_38

[!questions] 操作顺序
此处可能有一个问题,究竟是“先将隐式 padding 使用 PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_30 处理后再卷积?”还是“先基于默认隐式 padding 卷积后再使用 PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_30

这里有一个额外的参数 PyTorch 中的转置卷积 ConvTranspose2d_计算机视觉_41 也非常重要。在原始文档中提到,这一参数主要的用处是为了保证在 PyTorch 中的转置卷积 ConvTranspose2d_cnn_42

对于标准卷积而言,如果 PyTorch 中的转置卷积 ConvTranspose2d_pytorch_43,则同一种输出形状可以存在多种输入相形状。所以转置卷积通过用户指定的参数来消除这种不确定性,从而明确输出形状的具体尺寸。如果输出形状不合适,可以使用这一参数来进行单边的补齐。注意,这一参数仅是作用于单边,对于 2D 情况,在 H 和 W 轴的末端上补 0。

另外,虽然文档提到“Note that output_padding is only used to find output shape, but does not actually add zero-padding to output.”,但是从实际效果来看,就是 0 的补齐,文档这一句话应该是在强调内部实现并非直接补 0。

局部聚合

使用卷积核在插 0 后的输入上滑动从而获得初步的输出。要注意,这里的 stride 参数并不会影响转置卷积本身卷积核的滑动,可以认为转置卷积核步长始终为 1。

使用标准卷积实现转置卷积

如果单纯使用框架自带的卷积函数,标准卷积只能实现 PyTorch 中的转置卷积 ConvTranspose2d_pytorch_44 的转置卷积。而且在使用相同的卷积参数的时候,需要注意的是卷积权重的索引顺序。从 ​​PyTorch 中的转置卷积详解——全网最细​​ 中我们可以知道,如果使用相同的卷积权重,标准卷积与转置卷积的权重索引方式不同,需要进行 ​​.flip(dim=*)​​ 来调整。

典型案例为:

# 1-D
In [59]: a = torch.arange(0, 3, 1).float().reshape(1, 1, 3)

In [60]: b = torch.arange(3, 6, 1).float().reshape(1, 1, 3)

In [65]: F.conv_transpose1d(a, b, stride=1, padding=0, output_padding=0)
Out[65]: tensor([[[ 0., 3., 10., 13., 10.]]])

In [66]: F.conv1d(a, b.transpose(0, 1).flip(-1), stride=1, padding=2)
Out[66]: tensor([[[ 0., 3., 10., 13., 10.]]])

# 2-D
In [67]: a = torch.arange(0, 9, 1).float().reshape(1, 1, 3, 3)

In [68]: b = torch.arange(3, 12, 1).float().reshape(1, 1, 3, 3)

In [69]: F.conv_transpose2d(a, b, stride=1, padding=0, output_padding=0)
Out[69]:
tensor([[[[ 0., 3., 10., 13., 10.],
[ 9., 30., 65., 62., 41.],
[ 36., 99., 192., 165., 102.],
[ 63., 150., 263., 206., 119.],
[ 54., 123., 208., 157., 88.]]]])

In [71]: F.conv2d(a, b.transpose(0, 1).flip(-1).flip(-2), stride=1, padding=2)
Out[71]:
tensor([[[[ 0., 3., 10., 13., 10.],
[ 9., 30., 65., 62., 41.],
[ 36., 99., 192., 165., 102.],
[ 63., 150., 263., 206., 119.],
[ 54., 123., 208., 157., 88.]]]])

[!important] 标准卷积与转置卷积共用权重需要注意的地方
PyTorch 中标准卷积与转置卷积的权重形状中,输入输出维度恰好相反,所以相同的权重需要进行额外交换轴的操作,即上面代码中 ​​​.transpose(0, 1)​​ 处理。

参考链接