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 k , s , p , p o k, s, p, p_o k,s,p,po

  • 输入形状为 ( N , C i , H i , W i ) (N, C_i, H_i, W_i) (N,Ci,Hi,Wi) 或者为 ( C i , H i , W i ) (C_i, H_i, W_i) (Ci,Hi,Wi)
  • 输出形状为 ( N , C o , H o , W o ) (N, C_o, H_o, W_o) (N,Co,Ho,Wo) 或者为 ( C o , H o , W o ) (C_o, H_o, W_o) (Co,Ho,Wo)
  • 卷积权重形状为 ( C i , C o / / G , k [ 0 ] , k [ 1 ] ) (C_i, C_o//G, k[0], k[1]) (Ci,Co//G,k[0],k[1])
  • 卷积偏置形状为 ( C o ) (C_o) (Co)
  • H o = ( H i − 1 ) × s [ 0 ] − 2 p [ 0 ] + d [ 0 ] × ( k [ 0 ] − 1 ) + p o [ 0 ] + 1 H_o = (H_i - 1) \times s[0] - 2p[0] + d[0] \times (k[0] - 1) + p_o[0] + 1 Ho=(Hi1)×s[0]2p[0]+d[0]×(k[0]1)+po[0]+1
  • W o = ( W i − 1 ) × s [ 1 ] − 2 p [ 1 ] + d [ 1 ] × ( k [ 1 ] − 1 ) + p o [ 1 ] + 1 W_o = (W_i - 1) \times s[1] - 2p[1] + d[1] \times (k[1] - 1) + p_o[1] + 1 Wo=(Wi1)×s[1]2p[1]+d[1]×(k[1]1)+po[1]+1

转置卷积的过程

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

k = 3 , s = 1 , p = 0 k=3,s=1,p=0 k=3,s=1,p=0 k = 3 , s = 2 , p = 0 k=3,s=2,p=0 k=3,s=2,p=0
!在这里插入图片描述
k = 4 , s = 1 , p = 2 k=4,s=1,p=2 k=4,s=1,p=2 k = 3 , s = 1 , p = 1 k=3,s=1,p=1 k=3,s=1,p=1
在这里插入图片描述在这里插入图片描述
k = 3 , s = 2 , p = 1 k=3,s=2,p=1 k=3,s=2,p=1 k = 3 , s = 2 , p = 1 , p o = 1 k=3,s=2,p=1,p_o=1 k=3,s=2,p=1,po=1
在这里插入图片描述在这里插入图片描述

调整形状

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

对输入使用 stride 处理,注意,转置卷积的 stride 并不同于标准卷积,而是在各个输入元素之间插入 s − 1 s-1 s1 个 0。这样的设定,可以使得转置卷积的输入数据,就像是转置卷积的输出数据通过标准卷积经过这样的 stride 滑动后得到的一样。那些转置卷积的输入数据中间插进去的 0,实际上就是这种标准卷积因为步长的设定而被跳过的输出。

而且另外一点需要注意,为了确保对卷积输入输出运算过程形状的对应性,所以 在卷积核滑动之前,须要在输入的四边上进行 padding,这里默认 padding 的值与卷积核的形状有关,即四边的 padding 为 ( k [ 0 ] − 1 , k [ 1 ] − 1 , k [ 0 ] − 1 , k [ 1 ] − 1 ) (k[0]-1, k[1]-1, k[0]-1, k[1]-1) (k[0]1,k[1]1,k[0]1,k[1]1)

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

所以当 s = ( 1 , 1 ) s=(1,1) s=(1,1) 的时候,输入需要 padding 才能够实现输出尺寸的增大。

  1. p [ ⋆ ] = 0 p[\star]=0 p[]=0:这是转置卷积的默认情况,此时转置卷积会对输入进行隐式的 padding,如前所述为 ( k [ 0 ] − 1 , k [ 1 ] − 1 , k [ 0 ] − 1 , k [ 1 ] − 1 ) (k[0]-1, k[1]-1, k[0]-1, k[1]-1) (k[0]1,k[1]1,k[0]1,k[1]1)
  2. p [ ⋆ ] > 1 p[\star] > 1 p[]>1:相当于是标准卷积 p [ ⋆ ] > 1 p[\star] > 1 p[]>1 的时候。在其他参数不变的时候,标准卷积的输出会相对扩大,对应回来,也就是转置卷积的输出应该相对缩小。于是我们可以看到, p p p 实际上起到一个反向调整的作用,即在默认的隐式 padding 上减去转置卷积设置中的 p p p。因而实际的隐式 padding 会变为 ( k [ 0 ] − 1 − p [ 0 ] , k [ 1 ] − 1 − p [ 1 ] , k [ 0 ] − 1 − p [ 0 ] , k [ 1 ] − 1 − p [ 1 ] ) (k[0]-1-p[0], k[1]-1-p[1], k[0]-1-p[0], k[1]-1-p[1]) (k[0]1p[0],k[1]1p[1],k[0]1p[0],k[1]1p[1])

[!important] p p p 的取值范围
结合上面的推理,进一步可以推测出转置卷积中对 p p p 的限制。

  • 首先必须大于等于 0,所以左边界为 0。
  • 对于右侧边界,必须要保证一点,即经过默认 padding 调整后的输入,在使用 p p p 剪裁后剩下的必须大于等于 k [ 0 ] × k [ 1 ] k[0] \times k[1] k[0]×k[1],也就是卷积操作必须有效。所以可以推算出,最大值为 k [ 0 ] − 1 + ( H i − k [ 0 ] ) / / 2 k[0]-1+(H_i-k[0])//2 k[0]1+(Hik[0])//2 k [ 1 ] − 1 + ( W i − k [ 1 ] ) / / 2 k[1]-1+(W_i-k[1])//2 k[1]1+(Wik[1])//2

[!questions] 操作顺序
此处可能有一个问题,究竟是“先将隐式 padding 使用 p p p 处理后再卷积?”还是“先基于默认隐式 padding 卷积后再使用 p p p 来剪裁数据?”从实际效果上来看二者是一样的。所以问题不大。

这里有一个额外的参数 p o p_o po 也非常重要。在原始文档中提到,这一参数主要的用处是为了保证在 s [ ⋆ ] > 0 s[\star]>0 s[]>0 的时候可以在形状上对齐标准卷积

对于标准卷积而言,如果 s [ ⋆ ] > 0 s[\star]>0 s[]>0,则同一种输出形状可以存在多种输入相形状。所以转置卷积通过用户指定的参数来消除这种不确定性,从而明确输出形状的具体尺寸。如果输出形状不合适,可以使用这一参数来进行单边的补齐。注意,这一参数仅是作用于单边,对于 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。

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

如果单纯使用框架自带的卷积函数,标准卷积只能实现 s = 1 s=1 s=1 的转置卷积。而且在使用相同的卷积参数的时候,需要注意的是卷积权重的索引顺序。从 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) 处理。

参考链接

  • 3
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值