转置卷积核(Transpose Convolution Kernel)

转置卷积核(Transpose Convolution Kernel)是深度学习中用于进行反卷积操作的核心组件之一。虽然有时也被称为 “逆卷积”,但实际上它用于执行上采样操作,而不是数学上的卷积的逆运算

在深度学习中,转置卷积核常用于图像分割、图像重建、生成对抗网络(GAN)等任务中。它的作用是将输入特征图放大,通常在图像生成过程中用于从低分辨率图像生成高分辨率图像。

再次强调:转置卷积操作并不是真正的卷积的逆运算,因为信息的损失是不可逆的。逆卷积核的权重在训练过程中需要学习,类似于正常卷积操作中的卷积核。

参考资料:

  1. conv_arithmetic/README.md at master · vdumoulin/conv_arithmetic · GitHub
  2. 1603.07285.pdf (arxiv.org)

1. 转置卷积参数

在 PyTorch 中,我们可以使用以下 API 进行转置卷积计算:

nn.ConvTranspose2d(
        in_channels: int,
        out_channels: int,
        kernel_size: _size_2_t,
        stride: _size_2_t = 1,
        padding: _size_2_t = 0,
        output_padding: _size_2_t = 0,
        groups: int = 1,
        bias: bool = True,
        dilation: _size_2_t = 1,
        padding_mode: str = 'zeros',
        device=None,
        dtype=None
)

参数含义如下:

  1. in_channels 表示输入图像的通道数
  2. out_channels 表示输出图像的通道数
  3. kernel_size 表示每个通道卷积核大小
  4. stride 表示每个像素点之间填充多少 s-1 个 0
  5. padding 表示在特征图周围填充 k-p-1 行或列
  6. bias 表示卷积核的偏置参数是否需要
  7. padding_mode 在输入数据周围填充的数据,默认填充0

out_channels 也表示包含多少个卷积核,in_channels 表示每个卷积核中包含多少个 kernel_size 大小的卷积核参数。例如:in_channels=5,kernel_size=3,则表示每个卷积核的尺寸为 (5, 3, 3),即:5 个 3×3 的矩阵。

转置卷积计算的过程如下:

  1. 首先,在输入图像每个像素之间填充 s-1 行或列 0
  2. 其次,在输入图像周围填充 k-p-1 行或列 0
  3. 然后,将卷积核参数上下左右翻转
  4. 最后,使用卷积核对填充后的图像进行正常的卷积操作

需要注意的是,进行转置卷积操作时,padding 和 stride 只参与对输入图像的填充处理,后续只需要对处理后的图像进行正常的步长为 1 的卷积操作。

2. padding 实验

在进行转置卷积操作时,padding 只会影响对输入图像周围的填充。填充行列数为:k – p – 1。接下来我们做实验来检验。

为了能够理解计算过程,我们这里使用 F.conv_transpose2d 函数,并使用固定的输入和卷积核参数,示例代码如下(p=0, k=3):

import torch
import torch.nn as nn
import torch.nn.functional as F


def test01():

    p = 0
    k = 3

    # 固定随机数
    torch.manual_seed(0)
    # 输入数据
    inputs = torch.randint(1, 10, size=(1, 1, 3, 3), dtype=torch.float32)
    # 卷积核参数: (每个卷积核的通道数,输出通道数量,卷积核宽高)
    weight = torch.randint(1, 5, size=(1, 1, 3, 3), dtype=torch.float32)
    # 偏置形状要和输出通道数一样
    bias = torch.zeros(size=(1,), dtype=torch.float32)

    outputs = F.conv_transpose2d(inputs, weight, bias, padding=p)
    print('输出结果:\n', outputs)


# 分解动作
def test02():

    p = 0
    k = 3

    # 固定随机数
    torch.manual_seed(0)
    # 输入数据
    inputs = torch.randint(1, 10, size=(1, 1, 3, 3), dtype=torch.float32)
    weight = torch.randint(1, 5, size=(1, 1, k, k), dtype=torch.float32)

    # 1. 在输入张量的左、右、上、下填充 n 行列 0
    inputs = F.pad(inputs, (k - p - 1,) * 4, value=0.0)
    print('周围填充:\n', inputs)

    # 2. 卷积核参数向下左右翻转
    weight = torch.flip(weight, dims=[2, 3])
    print('参数翻转:\n', weight)

    # 3. 正常进行卷积操作
    outputs = F.conv2d(inputs, weight=weight, padding=0, stride=1)
    print('输出结果:\n', outputs)


if __name__ == '__main__':
    test01()
    print('-' * 50)
    test02()

程序输出结果:

输出结果:
 tensor([[[[ 36.,  22.,  41.,   9.,   9.],
          [ 37.,  83.,  99.,  53.,  30.],
          [ 48.,  70., 109.,  66.,  30.],
          [ 15.,  49.,  56.,  29.,  13.],
          [  8.,  10.,  12.,   4.,   2.]]]])
--------------------------------------------------
周围填充:
 tensor([[[[0., 0., 0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0., 0., 0.],
          [0., 0., 9., 1., 3., 0., 0.],
          [0., 0., 7., 8., 7., 0., 0.],
          [0., 0., 8., 2., 2., 0., 0.],
          [0., 0., 0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0., 0., 0.]]]])
参数翻转:
 tensor([[[[1., 1., 1.],
          [3., 4., 1.],
          [3., 2., 4.]]]])
输出结果:
 tensor([[[[ 36.,  22.,  41.,   9.,   9.],
          [ 37.,  83.,  99.,  53.,  30.],
          [ 48.,  70., 109.,  66.,  30.],
          [ 15.,  49.,  56.,  29.,  13.],
          [  8.,  10.,  12.,   4.,   2.]]]])

3. stride 实验

stride 表示每个像素点之间填充多少 s-1 个 0。

import torch
import torch.nn as nn
import torch.nn.functional as F


def test01():

    s = 2
    p = 0
    k = 3

    # 固定随机数
    torch.manual_seed(0)
    # 输入数据
    inputs = torch.randint(1, 10, size=(1, 1, 3, 3), dtype=torch.float32)
    # 卷积核参数: (每个卷积核的通道数,输出通道数量,卷积核宽高)
    weight = torch.randint(1, 5, size=(1, 1, 3, 3), dtype=torch.float32)
    # 偏置形状要和输出通道数一样
    bias = torch.zeros(size=(1,), dtype=torch.float32)

    outputs = F.conv_transpose2d(inputs, weight, bias, padding=p, stride=s)
    print('输出结果:\n', outputs)


# 分解动作
def test02():

    s = 2
    p = 0
    k = 3

    # 固定随机数
    torch.manual_seed(0)
    # 输入数据
    inputs = torch.randint(1, 10, size=(1, 1, 3, 3), dtype=torch.float32)
    weight = torch.randint(1, 5, size=(1, 1, k, k), dtype=torch.float32)

    # 1. 元素之间填充0
    new_inputs = torch.zeros(1, 1, 5, 5)
    new_inputs[:, :, ::s, ::s] = inputs
    print('元素填充:\n', new_inputs)

    # 2. 矩阵周围填充0
    inputs = F.pad(new_inputs, (k - p - 1,) * 4, value=0.0)
    print('周围填充:\n', inputs)

    # 3. 卷积核参数向下左右翻转
    weight = torch.flip(weight, dims=[2, 3])
    print('参数翻转:\n', weight)

    # 4. 正常进行卷积操作
    outputs = F.conv2d(inputs, weight=weight, padding=0, stride=1)
    print('输出结果:\n', outputs)


if __name__ == '__main__':
    test01()
    print('-' * 50)
    test02()

程序输出结果:

输出结果:
 tensor([[[[36., 18., 31.,  2., 15.,  6.,  9.],
          [ 9., 36., 28.,  4.,  6., 12.,  9.],
          [37., 23., 63., 17., 56., 17., 24.],
          [ 7., 28., 29., 32., 31., 28., 21.],
          [39., 23., 47., 12., 29., 11., 13.],
          [ 8., 32., 26.,  8.,  8.,  8.,  6.],
          [ 8.,  8., 10.,  2.,  4.,  2.,  2.]]]])
--------------------------------------------------
元素填充:
 tensor([[[[9., 0., 1., 0., 3.],
          [0., 0., 0., 0., 0.],
          [7., 0., 8., 0., 7.],
          [0., 0., 0., 0., 0.],
          [8., 0., 2., 0., 2.]]]])
周围填充:
 tensor([[[[0., 0., 0., 0., 0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0., 0., 0., 0., 0.],
          [0., 0., 9., 0., 1., 0., 3., 0., 0.],
          [0., 0., 0., 0., 0., 0., 0., 0., 0.],
          [0., 0., 7., 0., 8., 0., 7., 0., 0.],
          [0., 0., 0., 0., 0., 0., 0., 0., 0.],
          [0., 0., 8., 0., 2., 0., 2., 0., 0.],
          [0., 0., 0., 0., 0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0., 0., 0., 0., 0.]]]])
参数翻转:
 tensor([[[[1., 1., 1.],
          [3., 4., 1.],
          [3., 2., 4.]]]])
输出结果:
 tensor([[[[36., 18., 31.,  2., 15.,  6.,  9.],
          [ 9., 36., 28.,  4.,  6., 12.,  9.],
          [37., 23., 63., 17., 56., 17., 24.],
          [ 7., 28., 29., 32., 31., 28., 21.],
          [39., 23., 47., 12., 29., 11., 13.],
          [ 8., 32., 26.,  8.,  8.,  8.,  6.],
          [ 8.,  8., 10.,  2.,  4.,  2.,  2.]]]])
未经允许不得转载:一亩三分地 » 转置卷积核(Transpose Convolution Kernel)
评论 (0)

1 + 1 =