了解 Conv2DTranspose 的 PyTorch 实现

Understanding the PyTorch implementation of Conv2DTranspose

我试图理解一个使用 PyTorch 转置卷积函数的示例片段,以及文档 here,作者在文档中写道:

"The padding argument effectively adds dilation * (kernel_size - 1) - padding amount of zero padding to both sizes of the input."

考虑下面的代码片段,其中 [1, 1, 4, 4] 个样本图像被输入到 ConvTranspose2D 操作,参数为 stride=2padding=1,权重矩阵为形状(1, 1, 4, 4) 具有介于 116 之间的条目(在本例中为 dilation=1added_padding = 1*(4-1)-1 = 2

sample_im = torch.ones(1, 1, 4, 4).cuda()
sample_deconv2 = nn.ConvTranspose2d(1, 1, 4, 2, 1, bias=False).cuda()
sample_deconv2.weight = torch.nn.Parameter(
    torch.tensor([[[[ 1.,  2.,  3.,  4.], 
                    [ 5.,  6.,  7.,  8.], 
                    [ 9., 10., 11., 12.], 
                    [13., 14., 15., 16.]]]]).cuda())

产生:

>>> sample_deconv2(sample_im)
tensor([[[[ 6., 12., 14., 12., 14., 12., 14.,  7.],
          [12., 24., 28., 24., 28., 24., 28., 14.],
          [20., 40., 44., 40., 44., 40., 44., 22.],
          [12., 24., 28., 24., 28., 24., 28., 14.],
          [20., 40., 44., 40., 44., 40., 44., 22.],
          [12., 24., 28., 24., 28., 24., 28., 14.],
          [20., 40., 44., 40., 44., 40., 44., 22.],
          [10., 20., 22., 20., 22., 20., 22., 11.]]]], device='cuda:0',
       grad_fn=<CudnnConvolutionTransposeBackward>)

现在我看到了没有步长和填充的转置卷积的简单例子。例如,如果输入是 2x2 图像 [[2, 4], [0, 1]],并且具有一个输出通道的卷积滤波器是 [[3, 1], [1, 5]],那么生成的形状为 (1, 1, 3, 3) 的张量可以看作是下图中最右边四个矩阵的和:

问题是我似乎无法在同一可视化中找到使用步长 and/or 填充的示例。根据我的代码片段,我很难理解填充是如何应用于样本图像的,或者步幅是如何工作来获得这个输出的。任何见解表示赞赏,即使只是了解如何计算结果矩阵的 (0,0) 条目中的 6(0,1) 条目中的 12 也会非常有帮助。

nn.ConvTranspose2d 的输出空间维度由下式给出:

out = (x - 1)s - 2p + d(k - 1) + op + 1

其中x输入空间维度和out相应的输出大小,s 是步幅,d 扩张,p 填充,k 内核大小,op 输出填充。


如果我们保留以下操作数:

对于输入的每个值,我们通过计算内核的每个元素的乘积来计算一个缓冲区(具有相应的颜色)。

以下是 s=1, p=0s=1, p=1s=2, p=0s=2, p=1 的可视化:

  • s=1, p=0:输出为3x3

对于蓝色缓冲区,我们有 (1) 2*k_top-left = 2*3 = 6; (2) 2*k_top-right = 2*1 = 2; (3) 2*k_bottom-left = 2*1 = 2; (4) 2*k_bottom-right = 2*5 = 10.

  • s=1, p=1:输出为1x1

  • s=2, p=0:输出为4x4

  • s=2, p=2:输出为2x2

我认为让事情变得混乱的原因是他们对文档中“输入”或“输出”的含义以及术语“步幅”和“填充”的重载不是很在意。

我发现通过问自己以下问题更容易理解 PyTorch 中的转置卷积:我会给一个正常的前向卷积层提供什么参数,这样它就会给出手头的张量,我正在将其输入转置卷积层?

比如“stride”应该理解为forwardconv中的“stride”,即滑动核的移动步长。

在转置转换中,“步幅”实际上意味着不同的东西:stride-1 是进入 转置 转换的输入单元之间的交错空槽的数量层。那是因为 forward 转换中的 greater-than-1 “步幅”造成了这样的漏洞。请参见下图的说明:

该图还表明,转置卷积层中的内核移动步长始终为 1,而与“步幅”的值无关。我发现牢记这一点非常重要。

padding 参数类似。应该理解为应用于 forward conv 的 0-padding。由于这种填充,我们在 forward conv 的输出中得到了一些额外的单元。因此,如果我们随后将此输出输入到转置转换中,为了回到原来的 non-padded 长度,那些额外的东西应该被删除,因此等式中的 -2p 项。

请参见下图。

总之,在张量形状变换的意义上,它们的设计使得普通 conv 和转置 conv 是彼此的“逆”操作。 (但我确实认为应该改进文档。)

有了这个原则,dilationoutput_padding 的论点也可以相对容易地计算出来。我已经写了一个blog,以防有人感兴趣。