您现在的位置是:首页 >其他 >转置卷积(一) 搞懂转置卷积的计算网站首页其他

转置卷积(一) 搞懂转置卷积的计算

ZhaoDongyu_AK47 2024-06-14 17:19:13
简介转置卷积(一) 搞懂转置卷积的计算

文章首发于https://zhaodongyu-ak47.github.io/Transposed_Convolution/

最近做了一些转置卷积的部署工作,最开始搞的时候其实有点晕头转向的,总是在用卷积的计算方式反过来理解转置卷积,尤其是padding部分和stride部分,搞得我头更大了。

现在也算是了解了具体工作机制以及加速方式,在这里整理总结一下。欢迎留言、指正 ?

0、参考文档

先敬上各位大佬的文档,这对我非常非常有帮助!

tf.keras.layers.Conv2DTranspose

What is Transposed Convolutional Layer?

一文搞懂反卷积,转置卷积

Up-sampling with Transposed Convolution

转置卷积(Transpose Convolution)

conv_transpose depth-wise优化技巧

图解转置卷积,我分别在conv_arithmeticWhat is Transposed Convolutional Layer里看到,感觉后者更容易理解。

1、转置卷积是什么?

1.1 定义

转置卷积有时候也被称为反卷积,我个人认为反卷积有很强的误导性,因为这并不是卷积的逆运算,还是叫转置卷积比较好。

转置卷积在深度学习中表示为卷积的一个逆向过程,可以根据卷积核大小和输出的大小,恢复卷积前的feature map尺寸,而不是恢复原始值。

如果将卷积表示为y=Cx,转置卷积则是将的输入输出互换:x = CTy

其中, CT表示矩阵转置。

详细定义这里就不仔细介绍了,上文里的各个参考文档里说的都很明白。

1.2 需要注意

总结一下我认为的最重要的(最开始纠结了很久的)几个点:

  • 转置卷积不是恢复原始值,而是恢复原始尺寸(所以不要试图从卷积的逆运算角度考虑)

  • padding方式和卷积的padding是不一样的,转置卷积的实际padding是k-p-1

  • stride在这里用途不是跳几个数,而是用于判断填充几个0

  • 用公式法直接计算的话,首先对卷积核做中心对称操作(矩阵旋转180°)

  • 不考虑性能的话,直接按照转置卷积定义写。反之,一定要优化,不然慢得很。

The table below summarizes the two convolutions, standard and transposed.

Conv TypeOperationZero InsertionsPaddingStrideOutput Size
StandardDownsampling0ps(i+2p-k)/s+1
TransposedUpsampling(s-1)(k-p-1)1(i-1)*s+k-2p

2、转置卷积的计算

2.1 从最简单的开始

conv_transpose有一种最直接的计算方式:首先对卷积核做中心对称操作(矩阵旋转180°),并对输入feature map进行插0,然后把旋转后的卷积核和插0后的feature map进行卷积操作


现在假设输入的feature map是3x3大小,kernel size是3x3大小,stride为1, padding为0,即:

input_sz:     3
kernel_sz =   3
stride =      1
padding_sz =  0

写一段torch代码计算一下:

import torch
X = torch.Tensor([[[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]])
K = torch.Tensor([[[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]])
Y = torch.nn.functional.conv_transpose2d(X, K, stride=1, padding=0)
print(Y)

得到输出结果:

tensor([[[[  1.,   4.,  10.,  12.,   9.],
          [  8.,  26.,  56.,  54.,  36.],
          [ 30.,  84., 165., 144.,  90.],
          [ 56., 134., 236., 186., 108.],
          [ 49., 112., 190., 144.,  81.]]]])

计算过程:

(1) 对输入X进行处理,插入(s-1)的0,做(k-p-1)的padding

在这个例子中,s=1,则无需插入0,只进行(k-p-1)=(3-0-1)=2的padding。输入X则转化为

[ 1 2 3 4 5 6 7 8 9 ] − > [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 3 0 0 0 0 4 5 6 0 0 0 0 7 8 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] egin{bmatrix} 1 & 2 & 3 \ 4 & 5 & 6 \ 7 & 8 & 9 end{bmatrix} -> egin{bmatrix} 0 & 0 & 0 & 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 & 0 & 0 & 0 \ 0 & 0 & 1 & 2 & 3 & 0 & 0 \ 0 & 0 & 4 & 5 & 6 & 0 & 0 \ 0 & 0 & 7 & 8 & 9 & 0 & 0 \ 0 & 0 & 0 & 0 & 0 & 0 & 0 \0 & 0 & 0 & 0 & 0 & 0 & 0 end{bmatrix} 147258369 > 0000000000000000147000025800003690000000000000000

(2) 对卷积核K进行中心对称操作

卷积核K则转化为为
[ 1 2 3 4 5 6 7 8 9 ] − > [ 9 8 7 6 5 4 3 2 1 ] egin{bmatrix} 1 & 2 & 3 \ 4 & 5 & 6 \ 7 & 8 & 9 end{bmatrix} -> egin{bmatrix} 9 & 8 & 7 \ 6 & 5 & 4 \ 3 & 2 & 1 end{bmatrix} 147258369 > 963852741

(3) 进行卷积计算
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 2 3 0 0 0 0 4 5 6 0 0 0 0 7 8 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] ∗ [ 9 8 7 6 5 4 3 2 1 ] = [ 1 4 10 12 9 8 26 56 54 36 30 84 165 144 90 56 134 236 186 108 49 112 190 144 81 ] egin{bmatrix} 0 & 0 & 0 & 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 & 0 & 0 & 0 \ 0 & 0 & 1 & 2 & 3 & 0 & 0 \ 0 & 0 & 4 & 5 & 6 & 0 & 0 \ 0 & 0 & 7 & 8 & 9 & 0 & 0 \ 0 & 0 & 0 & 0 & 0 & 0 & 0 \0 & 0 & 0 & 0 & 0 & 0 & 0 end{bmatrix} * egin{bmatrix} 9 & 8 & 7 \ 6 & 5 & 4 \ 3 & 2 & 1 end{bmatrix} = egin{bmatrix} 1 & 4 & 10 & 12 & 9 \ 8 & 26 & 56 & 54 & 36 \ 30 & 84 & 165 & 144 & 90 \ 56 & 134 & 236 & 186 & 108 \ 49 & 112 & 190 & 144 & 81 end{bmatrix} 0000000000000000147000025800003690000000000000000 963852741 = 1830564942684134112105616523619012541441861449369010881

(4) gif图解

transposed_conv_S1P0

2.2 考虑stride

我个人建议不要用卷积的stride来理解转置卷积的stride,stride在这里用途不是跳几个数,而是用于判断填充几个0。


现在假设输入的feature map是3x3大小,kernel size是3x3大小,stride为2, padding为0,即:

input_sz:     3
kernel_sz =   3
stride =      2
padding_sz =  0

同样,写一段torch代码计算一下:

import torch
X = torch.Tensor([[[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]])
K = torch.Tensor([[[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]])
Y = torch.nn.functional.conv_transpose2d(X, K, stride=2, padding=0)
print(Y)

得到输出结果:

tensor([[[[  1.,   2.,   5.,   4.,   9.,   6.,   9.],
          [  4.,   5.,  14.,  10.,  24.,  15.,  18.],
          [ 11.,  16.,  40.,  26.,  60.,  36.,  45.],
          [ 16.,  20.,  44.,  25.,  54.,  30.,  36.],
          [ 35.,  46., 100.,  56., 120.,  66.,  81.],
          [ 28.,  35.,  74.,  40.,  84.,  45.,  54.],
          [ 49.,  56., 119.,  64., 135.,  72.,  81.]]]])

计算过程:

(1) 对输入X进行处理,插入(s-1)的0,做(k-p-1)的padding

在这个例子中,s=2,需插入1个0,进行(k-p-1)=(3-0-1)=2的padding。输入X则转化为

[ 1 2 3 4 5 6 7 8 9 ] − > [ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 2 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 5 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 7 0 8 0 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] egin{bmatrix} 1 & 2 & 3 \ 4 & 5 & 6 \ 7 & 8 & 9 end{bmatrix} -> egin{bmatrix} 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \ 0 & 0 & 1 & 0 & 2 & 0 & 3 & 0 & 0 \0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ 0 & 0 & 4 & 0 & 5 & 0 & 6 & 0 & 0 \0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ 0 & 0 & 7 & 0 & 8 & 0 & 9 & 0 & 0 \ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 end{bmatrix} 147258369 > 000000000000000000001040700000000000002050800000000000003060900000000000000000000

(2) 对卷积核K进行中心对称操作

卷积核K则转化为为
[ 1 2 3 4 5 6 7 8 9 ] − > [ 9 8 7 6 5 4 3 2 1 ] egin{bmatrix} 1 & 2 & 3 \ 4 & 5 & 6 \ 7 & 8 & 9 end{bmatrix} -> egin{bmatrix} 9 & 8 & 7 \ 6 & 5 & 4 \ 3 & 2 & 1 end{bmatrix} 147258369 > 963852741

(3) 进行卷积计算
[ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 2 0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 4 0 5 0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 7 0 8 0 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ] ∗ [ 9 8 7 6 5 4 3 2 1 ] = [ 1 2 5 4 9 6 9 4 5 14 10 24 15 18 11 16 40 26 60 36 45 16 20 44 25 54 30 36 35 46 100 56 120 66 81 28 35 74 40 84 45 54 49 56 119 64 135 72 81 ] egin{bmatrix} 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \ 0 & 0 & 1 & 0 & 2 & 0 & 3 & 0 & 0 \0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ 0 & 0 & 4 & 0 & 5 & 0 & 6 & 0 & 0 \0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0\ 0 & 0 & 7 & 0 & 8 & 0 & 9 & 0 & 0 \ 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 \0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 & 0 end{bmatrix}* egin{bmatrix} 9 & 8 & 7 \ 6 & 5 & 4 \ 3 & 2 & 1 end{bmatrix} = egin{bmatrix} 1 & 2 & 5 & 4 & 9 & 6 & 9\ 4 & 5 & 14 & 10 & 24 & 15 & 18\ 11 & 16 & 40 & 26 & 60 & 36 & 45\ 16 & 20 & 44 & 25 & 54 & 30 & 36\ 35 & 46 & 100 & 56 & 120 & 66 & 81\ 28 & 35 & 74 & 40 & 84 & 45 & 54\ 49 & 56 & 119 & 64 & 135 & 72 & 81 end{bmatrix} 000000000000000000001040700000000000002050800000000000003060900000000000000000000 963852741 = 141116352849251620463556514404410074119410262556406492460541208413561536306645729184536815481

(4) gif图解

transposed_conv_S2P0

2.3 考虑padding

我最开始在padding这里疑惑了好一会儿,老是在从卷积的角度想转置卷积的padding。就很疑惑,怎么padding越大,计算结果的feature map越小呢?

后来也不想具体物理含义了,直接认为转置卷积的实际paddingk-p-1,万事大吉。


实际上,tensorflow的padding计算还是有点差异的,除了上面所说的计算,在计算padding的时候还有一个专门针对转置卷积的offset,这可能会导致 左右/上下 的padding数不一致。

为什么这么做呢?个人认为要从转置卷积的目的来看————还原原始feature map的尺寸。

本文暂不考虑这种情况,感兴趣的可以查看tensorflow源码。


现在假设输入的feature map是3x3大小,kernel size是3x3大小,stride为1, padding为1,即:

input_sz:     3
kernel_sz =   3
stride =      1
padding_sz =  1

写一段torch代码计算一下:

import torch
X = torch.Tensor([[[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]])
K = torch.Tensor([[[[1, 2, 3], [4, 5, 6], [7, 8, 9]]]])
Y = torch.nn.functional.conv_transpose2d(X, K, stride=1, padding=1)
print(Y)

得到输出结果:

tensor([[[[ 26.,  56.,  54.],
          [ 84., 165., 144.],
          [134., 236., 186.]]]])

计算过程:

(1) 对输入X进行处理,插入(s-1)的0,做(k-p-1)的padding

在这个例子中,s=1,则无需插入0,只进行(k-p-1)=(3-1-1)=1的padding。输入X则转化为

[ 1 2 3 4 5 6 7 8 9 ] − > [ 0 0 0 0 0 0 1 2 3 0 0 4 5 6 0 0 7 8 9 0 0 0 0 0 0 ] egin{bmatrix} 1 & 2 & 3 \ 4 & 5 & 6 \ 7 & 8 & 9 end{bmatrix} -> egin{bmatrix} 0 & 0 & 0 & 0 & 0 \ 0 & 1 & 2 & 3 & 0 \ 0 & 4 & 5 & 6 & 0 \ 0 & 7 & 8 & 9 & 0 \ 0 & 0 & 0 & 0 & 0 end{bmatrix} 147258369 > 0000001470025800369000000

(2) 对卷积核K进行中心对称操作

卷积核K则转化为为

[ 1 2 3 4 5 6 7 8 9 ] − > [ 9 8 7 6 5 4 3 2 1 ] egin{bmatrix} 1 & 2 & 3 \ 4 & 5 & 6 \ 7 & 8 & 9 end{bmatrix} -> egin{bmatrix} 9 & 8 & 7 \ 6 & 5 & 4 \ 3 & 2 & 1 end{bmatrix} 147258369 > 963852741

(3) 进行卷积计算
[ 0 0 0 0 0 0 1 2 3 0 0 4 5 6 0 0 7 8 9 0 0 0 0 0 0 ] ∗ [ 9 8 7 6 5 4 3 2 1 ] = [ 26 56 54 84 165 144 134 236 186 ] egin{bmatrix} 0 & 0 & 0 & 0 & 0 \ 0 & 1 & 2 & 3 & 0 \ 0 & 4 & 5 & 6 & 0 \ 0 & 7 & 8 & 9 & 0 \ 0 & 0 & 0 & 0 & 0 end{bmatrix}* egin{bmatrix} 9 & 8 & 7 \ 6 & 5 & 4 \ 3 & 2 & 1 end{bmatrix} = egin{bmatrix} 26 & 56 & 54\ 84 & 165 & 144 \ 134 & 236 & 186end{bmatrix} 0000001470025800369000000 963852741 = 26841345616523654144186

(4) gif图解

transposed_conv_S1P1

2.4 考虑dilation

这里就不考虑了,和卷积一样的,很容易理解。


3 转置卷积的加速

篇幅有限,转置卷积的加速工作就放到下一篇 转置卷积(二) 对转置卷积进行加速


本文用图参考了aqeelanwar的代码,非常感谢。

图像压缩用了iloveimg,非常好用~

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。