天天看點

轉置卷積(Transposed Convolution)前言卷積操作轉置卷積操作Pytorch中的轉置卷積參數Pytorch轉置卷積實驗

文章目錄

  • 前言
  • 卷積操作
  • 轉置卷積操作
  • Pytorch中的轉置卷積參數
  • Pytorch轉置卷積實驗

前言

轉置卷積(Transposed Convolution) 在語義分割或者對抗神經網絡(GAN)中比較常見,其主要作用就是做上采樣(UpSampling)。在有些地方轉置卷積又被稱作fractionally-strided convolution或者deconvolution,但deconvolution具有誤導性,不建議使用。對于轉置卷積需要注意的是:

  • 轉置卷積不是卷積的逆運算、不是逆運算、不是逆運算(重要的事情說三遍)
  • 轉置卷積也是卷積

本文主要介紹轉置卷積是如何計算的,關于轉置卷積詳細内容可以參考下面的文章:

  • A guide to convolution arithmetic for deep learning
  • https://blog.csdn.net/tsyccnh/article/details/87357447

卷積操作

首先回顧下普通卷積,下圖以stride=1,padding=0,kernel_size=3為例,假設輸入特征圖大小是4x4的(假設輸入輸出都是單通道),通過卷積後得到的特征圖大小為2x2。一般使用卷積的情況中,要麼特征圖變小(stride > 1),要麼保持不變(stride = 1),當然也可以通過四周padding讓特征圖變大但沒有意義。關于卷積的詳細介紹可以參考我之前的博文。

轉置卷積(Transposed Convolution)前言卷積操作轉置卷積操作Pytorch中的轉置卷積參數Pytorch轉置卷積實驗

轉置卷積操作

轉置卷積剛剛說了,主要作用就是起到上采樣的作用。但轉置卷積不是卷積的逆運算(一般卷積操作是不可逆的),它隻能恢複到原來的大小(shape)數值與原來不同。轉置卷積的運算步驟可以歸為以下幾步:

  • 在輸入特征圖元素間填充s-1行、列0(其中s表示轉置卷積的步距)
  • 在輸入特征圖四周填充k-p-1行、列0(其中k表示轉置卷積的kernel_size大小,p為轉置卷積的padding,注意這裡的padding和卷積操作中有些不同)
  • 将卷積核參數上下、左右翻轉
  • 做正常卷積運算(填充0,步距1)

下面假設輸入的特征圖大小為2x2(假設輸入輸出都為單通道),通過轉置卷積後得到4x4大小的特征圖。這裡使用的轉置卷積核大小為k=3,stride=1,padding=0的情況(忽略偏執bias)。

  • 首先在元素間填充s-1=0行、列0(等于0不用填充)
  • 然後在特征圖四周填充k-p-1=2行、列0
  • 接着對卷積核參數進行上下、左右翻轉
  • 最後做正常卷積(填充0,步距1)
轉置卷積(Transposed Convolution)前言卷積操作轉置卷積操作Pytorch中的轉置卷積參數Pytorch轉置卷積實驗

下圖展示了轉置卷積中不同s和p的情況:

轉置卷積(Transposed Convolution)前言卷積操作轉置卷積操作Pytorch中的轉置卷積參數Pytorch轉置卷積實驗
轉置卷積(Transposed Convolution)前言卷積操作轉置卷積操作Pytorch中的轉置卷積參數Pytorch轉置卷積實驗
轉置卷積(Transposed Convolution)前言卷積操作轉置卷積操作Pytorch中的轉置卷積參數Pytorch轉置卷積實驗
s=1, p=0, k=3 s=2, p=0, k=3 s=2, p=1, k=3

轉置卷積操作後特征圖的大小可以通過如下公式計算:

H o u t = ( H i n − 1 ) × s t r i d e [ 0 ] − 2 × p a d d i n g [ 0 ] + k e r n e l _ s i z e [ 0 ] W o u t = ( W i n − 1 ) × s t r i d e [ 1 ] − 2 × p a d d i n g [ 1 ] + k e r n e l _ s i z e [ 1 ] H_{out} =(H_{in}-1) \times {\rm stride[0]} - 2 \times {\rm padding[0]}+ {\rm kernel \_ size[0]} \\ W_{out} =(W_{in}-1) \times {\rm stride[1]} - 2 \times {\rm padding[1]}+ {\rm kernel \_ size[1]} Hout​=(Hin​−1)×stride[0]−2×padding[0]+kernel_size[0]Wout​=(Win​−1)×stride[1]−2×padding[1]+kernel_size[1]

其中stride[0]表示高度方向的stride,padding[0]表示高度方向的padding,kernel_size[0]表示高度方向的kernel_size,索引[1]都表示寬度方向上的。通過上面公式可以看出padding越大,輸出的特征矩陣高、寬越小,你可以了解為正向卷積過程中進行了padding然後得到了特征圖,現在使用轉置卷積還原到原來高、寬後要把之前的padding減掉。

Pytorch中的轉置卷積參數

pytorch官方關于轉置卷積

ConvTranspose2d

的文檔:https://pytorch.org/docs/stable/generated/torch.nn.ConvTranspose2d.html

官方原話:

Applies a 2D transposed convolution operator over an input image composed of several input planes.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).

官方對轉置卷積使用到的參數介紹:

轉置卷積(Transposed Convolution)前言卷積操作轉置卷積操作Pytorch中的轉置卷積參數Pytorch轉置卷積實驗

上面講的例子中已經介紹了

in_channels, out_channels, kernel_size, stride, padding

這幾個參數了,在官方提供的方法中還有:

  • output_padding

    :在計算得到的輸出特征圖的高、寬方向各填充幾行或列0(注意,這裡隻是在上下以及左右的一側

    one side

    填充,并不是兩側都填充,有興趣自己做個實驗看下),預設為0不使用。
  • groups

    :當使用到組卷積時才會用到的參數,預設為1即普通卷積。
  • bias

    :是否使用偏執bias,預設為True使用。
  • dilation

    :當使用到空洞卷積(膨脹卷積)時才會使用到的參數,預設為1即普通卷積。

輸出特征圖寬、高計算:

H o u t = ( H i n − 1 ) × s t r i d e [ 0 ] − 2 × p a d d i n g [ 0 ] + d i l a t i o n [ 0 ] × ( k e r n e l _ s i z e [ 0 ] − 1 ) + o u t p u t _ p a d d i n g [ 0 ] + 1 W o u t = ( W i n − 1 ) × s t r i d e [ 1 ] − 2 × p a d d i n g [ 1 ] + d i l a t i o n [ 1 ] × ( k e r n e l _ s i z e [ 1 ] − 1 ) + o u t p u t _ p a d d i n g [ 1 ] + 1 H_{out} =(H_{in}-1) \times {\rm stride[0]} - 2 \times {\rm padding[0]}+ {\rm dilation[0]} \times ({\rm kernel \_ size[0]}-1) + {\rm output\_padding[0]}+1 \\ W_{out} =(W_{in}-1) \times {\rm stride[1]} - 2 \times {\rm padding[1]}+ {\rm dilation[1]} \times ({\rm kernel \_ size[1]}-1) + {\rm output\_padding[1]}+1 Hout​=(Hin​−1)×stride[0]−2×padding[0]+dilation[0]×(kernel_size[0]−1)+output_padding[0]+1Wout​=(Win​−1)×stride[1]−2×padding[1]+dilation[1]×(kernel_size[1]−1)+output_padding[1]+1

Pytorch轉置卷積實驗

下面使用Pytorch架構來模拟s=1, p=0, k=3的轉置卷積操作:

轉置卷積(Transposed Convolution)前言卷積操作轉置卷積操作Pytorch中的轉置卷積參數Pytorch轉置卷積實驗

在代碼中

transposed_conv_official

函數是使用官方的轉置卷積進行計算,

transposed_conv_self

函數是按照上面講的步驟自己對輸入特征圖進行填充并通過卷積得到的結果。

import torch
import torch.nn as nn


def transposed_conv_official():
    feature_map = torch.as_tensor([[1, 0],
                                   [2, 1]], dtype=torch.float32).reshape([1, 1, 2, 2])
    print(feature_map)
    trans_conv = nn.ConvTranspose2d(in_channels=1, out_channels=1,
                                    kernel_size=3, stride=1, bias=False)
    trans_conv.load_state_dict({"weight": torch.as_tensor([[1, 0, 1],
                                                           [0, 1, 1],
                                                           [1, 0, 0]], dtype=torch.float32).reshape([1, 1, 3, 3])})
    print(trans_conv.weight)
    output = trans_conv(feature_map)
    print(output)


def transposed_conv_self():
    feature_map = torch.as_tensor([[0, 0, 0, 0, 0, 0],
                                   [0, 0, 0, 0, 0, 0],
                                   [0, 0, 1, 0, 0, 0],
                                   [0, 0, 2, 1, 0, 0],
                                   [0, 0, 0, 0, 0, 0],
                                   [0, 0, 0, 0, 0, 0]], dtype=torch.float32).reshape([1, 1, 6, 6])
    print(feature_map)
    conv = nn.Conv2d(in_channels=1, out_channels=1,
                     kernel_size=3, stride=1, bias=False)
    conv.load_state_dict({"weight": torch.as_tensor([[0, 0, 1],
                                                     [1, 1, 0],
                                                     [1, 0, 1]], dtype=torch.float32).reshape([1, 1, 3, 3])})
    print(conv.weight)
    output = conv(feature_map)
    print(output)


def main():
    transposed_conv_official()
    print("---------------")
    transposed_conv_self()


if __name__ == '__main__':
    main()

           

終端輸出:

tensor([[[[1., 0.],
          [2., 1.]]]])
Parameter containing:
tensor([[[[1., 0., 1.],
          [0., 1., 1.],
          [1., 0., 0.]]]], requires_grad=True)
tensor([[[[1., 0., 1., 0.],
          [2., 2., 3., 1.],
          [1., 2., 3., 1.],
          [2., 1., 0., 0.]]]], grad_fn=<SlowConvTranspose2DBackward>)
---------------
tensor([[[[0., 0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0., 0.],
          [0., 0., 1., 0., 0., 0.],
          [0., 0., 2., 1., 0., 0.],
          [0., 0., 0., 0., 0., 0.],
          [0., 0., 0., 0., 0., 0.]]]])
Parameter containing:
tensor([[[[0., 0., 1.],
          [1., 1., 0.],
          [1., 0., 1.]]]], requires_grad=True)
tensor([[[[1., 0., 1., 0.],
          [2., 2., 3., 1.],
          [1., 2., 3., 1.],
          [2., 1., 0., 0.]]]], grad_fn=<ThnnConv2DBackward>)

Process finished with exit code 0
           

通過對比能夠發現,官方轉置卷積的結果,和我們自己實作的轉置卷積結果是一樣的。對于其他的情況大家可以自己動手做做實驗。

繼續閱讀