# 转置卷积

# 1.基础情况介绍

在语义分割任务使用的网络结构中,除了使用卷积神经网络进行降采样,为了恢复图像的大小,还需要使用转置卷积。转置卷积被称为transposed convolutionfractionally-strided convolution也有些地方称为deconvolution。转置卷积只是恢复的feature map的空间尺寸并不等同于卷积的逆运算,所以称之为deconvolution是不合适的。

为了简化介绍,假设通道channle=1,对于的输入和的卷积核,对于stride=1, padding=0的转置卷积,其计算过程为,将的卷积核在的输入的每一行每一列上滑动,得到个中间结果,每个中间结果的空间尺寸为,其值初时化为0,然后对于输入中的每个元素,与卷积核相乘得到的结果替换掉矩阵中的对应部分,最后将所有的中间结果相加得到最后的输出

使用numpy数组实现上述过程为:

import numpy as np
X = np.array([[0.0, 1.0], [2.0, 3.0]])
K = np.array([[0.0, 1.0], [2.0, 3.0]])
def trans_conv(X, K):
    K = K.reshape((2,2))
    X = X.reshape((2, 2))
    h, w = X.shape
    kh, kw = K.shape
    o = np.zeros((h + kh - 1, w + kw - 1))
    for i in range(h):
        for j in range(w):
            o[i : i + h, j : j + w] += X[i][j] * K
    return o
    
trans_conv(X, K)
"""output
array([[ 0.,  0.,  1.],
       [ 0.,  4.,  6.],
       [ 4., 12.,  9.]])
"""

可见程序的输出与上图计算过程的结果一致。

借用pytorch中的ConvTranspose2d实现的计算的过程为:

import torch
import torch.nn as nn

X = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
K = torch.tensor([[0.0, 1.0], [2.0, 3.0]])
X, K = X.reshape(1, 1, 2, 2), K.reshape(1, 1, 2, 2)
tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, bias=False)
tconv.weight.data = K
tconv(X)
"""output
tensor([[[[ 0.,  0.,  1.],
          [ 0.,  4.,  6.],
          [ 4., 12.,  9.]]]], grad_fn=<SlowConvTranspose2DBackward>)
"""

可见这三个地方计算的结果是一致的。

# 2.使用paddding/stride/dilation/多通道时

# 2.1 stride

stride表示的是卷积核在输出张量上滑动的步长,前面的例子中使用的是stride=1的场景,在使用stride时,输出张量的空间尺寸的计算方式为, 如下图:


如上图中,则

使用pytorch验证上述计算过程,则

X = torch.range(0, 8).reshape((1, 1, 3,3))
K = torch.range(0, 3).reshape((1, 1, 2,2))
tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, stride=2, bias=False)
tconv.weight.data = K
Y = tconv(X)
print(Y.shape)
print(Y)

"""output
torch.Size([1, 1, 6, 6])
tensor([[[[ 0.,  0.,  0.,  1.,  0.,  2.],
          [ 0.,  0.,  2.,  3.,  4.,  6.],
          [ 0.,  3.,  0.,  4.,  0.,  5.],
          [ 6.,  9.,  8., 12., 10., 15.],
          [ 0.,  6.,  0.,  7.,  0.,  8.],
          [12., 18., 14., 21., 16., 24.]]]],
       grad_fn=<SlowConvTranspose2DBackward>)
"""

# 2.2 padding

padding是在transpose convolution计算过程中作用在输出张量上面的,因此计算结束后需要裁剪掉padding的大小。

如 (2.1)[### 2.1 stride]中的例子,假设padding=1,则输出张量的最外围一圈将被裁剪掉,如下图:


此时

oh=(nh1)×stride+(kh1)+12×padding

使用pytorch ConvTranspose2d API时,只需要加入padding=1参数即可,

X = torch.range(0, 8).reshape((1, 1, 3,3))
K = torch.range(0, 3).reshape((1, 1, 2,2))
tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, padding=1, stride=2, bias=False)
tconv.weight.data = K
Y = tconv(X)
print(Y.shape)
print(Y)
"""output
torch.Size([1, 1, 4, 4])
tensor([[[[ 0.,  2.,  3.,  4.],
          [ 3.,  0.,  4.,  0.],
          [ 9.,  8., 12., 10.],
          [ 6.,  0.,  7.,  0.]]]], grad_fn=<SlowConvTranspose2DBackward>)
"""

此外还有output_padding,这个是对转置卷积后输出的张量进行的扩充,这会增大输出张量的尺寸,不过pytorch ConvTranspose2d API的实现只对输出张量的一边(H,W较大的那一边)进行了扩充,因此其对输出张量空间尺寸的影响为:

oh=(nh1)×stride+(kh1)+12×padding+output_padding

# 2.3 dilation

dilation即扩张卷积,卷积核中存在空洞,因此也称空洞卷积。如下图是dilation=2dilated convolution

Transposed Convolution中使用dilation时,输出的空间尺寸计算可参考下图:


由上图可得:

oh=(nh1)×stride+dilation×(kh1)+12×paddingoh=2×2+2×1+1=7

使用pytorch验证上图中的例子,可得

X = torch.range(0, 8).reshape((1, 1, 3,3))
K = torch.range(0, 3).reshape((1, 1, 2,2))
tconv = nn.ConvTranspose2d(1, 1, kernel_size=2, dilation=2, stride=2, bias=False)
tconv.weight.data = K
Y = tconv(X)
print(Y.shape)
print(Y)

"""output
torch.Size([1, 1, 7, 7])
tensor([[[[ 0.,  0.,  0.,  0.,  1.,  0.,  2.],
          [ 0.,  0.,  0.,  0.,  0.,  0.,  0.],
          [ 0.,  0.,  5.,  0., 11.,  0., 11.],
          [ 0.,  0.,  0.,  0.,  0.,  0.,  0.],
          [ 6.,  0., 23.,  0., 29.,  0., 23.],
          [ 0.,  0.,  0.,  0.,  0.,  0.,  0.],
          [12.,  0., 32.,  0., 37.,  0., 24.]]]],
       grad_fn=<SlowConvTranspose2DBackward>)
"""

综上,对于由padding/stride/dilation/output_padding时,输出张量的计算方式为:

oh=(nh1)×stride+dilation×(kh1)+12×padding+output_padding

# 2.4 multi-channles

同常规的卷积操作,转置卷积对多通道的处理,同样是将每个卷积核在输入张量的各个维度上进行卷积,然后在将各个维度上的卷积结果相加求和即可。

如下代码示例,可见最后的输出正是两个通道上卷积结果求和所得:

X = torch.tensor([[[0.0, 1.0], [2.0, 3.0]],[[0.0, 1.0], [2.0, 3.0]]])
K = torch.tensor([[[0.0, 1.0], [2.0, 3.0]],[[0.0, 1.0], [2.0, 3.0]]])
X, K = X.reshape(1, 2, 2, 2), K.reshape(2, 1, 2, 2)
tconv = nn.ConvTranspose2d(2, 1, kernel_size=2, bias=False)
tconv.weight.data = K
tconv(X)
"""output
tensor([[[[ 0.,  0.,  2.],
          [ 0.,  8., 12.],
          [ 8., 24., 18.]]]], grad_fn=<SlowConvTranspose2DBackward>)
"""
(adsbygoogle = window.adsbygoogle || []).push({});

# 参考资料