精华内容
下载资源
问答
  • 转置卷积

    2021-04-22 20:23:13
    转置卷积transposed convolution个人备忘总结转置卷积特点实现总结 转置卷积 上采样方式有三种:双线性或最近邻插值、反池化和转置卷积。现在见的比较多的是双线性和转置卷积。在经典的FCN8中用的就是转置卷积(文中...

    转置卷积transposed convolution个人备忘总结

    转置卷积

    上采样方式有三种:双线性或最近邻插值、反池化和转置卷积。现在见的比较多的是双线性和转置卷积。在经典的FCN8中用的就是转置卷积(文中称其为反卷积deconvolution)。

    特点

    转置卷积顾名思义是一种卷积方式,只是卷积前多了个参数转置的过程。

    优点:可学习,理论上模型可以通过学习获取最适合当前数据集的上采样方式。

    缺点:存在棋盘效应, 分割边界为锯齿状。前者可以参考国外大佬的博客,消除办法是将kernel设置为stride的整数倍。后者为个人经验,目前没找到好的解决办法。

    实现

    目前在网上看到的对转置卷积实现方式的解释主要有两种。

    1. https://blog.csdn.net/LoseInVain/article/details/81098502
    2. https://blog.csdn.net/w55100/article/details/106467776

    以前我偏向第一种解释,把图像和参数展成两个数组相乘,看着比较简单。今天打算仔细研究一下,结果想了半天没想明白偶数如4*4的卷积核该怎么展开。

    那还能怎么办呢,叛变呗,第二种解释真香。这里只讨论最基础的情形,即只考虑kernel、stride和padding,不考虑dilation(文献看的少,好像没遇到过上采样时用dilation的)。

    先上两个公式,从理论上看一下:

    下采样(普通卷积)
    L=(H+2paddingkernel)/stride+1 L = (H + 2 * padding - kernel) / stride + 1
    上采样(转置卷积)
    H=(L1)stride+kernel2padding H = (L - 1) * stride + kernel - 2 * padding

    再举个实际的简单例子。

    首先是下采样,为了方便,我们设定图片img为44,kernel为44,stride为2,padding为1。对于下采样,为了简单img和kernel如下:
    在这里插入图片描述
    对img补零,因为padding为1,在img上下左右补一圈0,然后以stride为2滑动,很容易就能得到22的卷积结果:
    在这里插入图片描述
    上采样,我们设定img为2
    2,kernel为4*4,stride为2,padding为1,为了方便,img和kernel如下:
    在这里插入图片描述

    对img补零,重点来了,补零的依据不是padding,而是stride,并且补零是在img里边进行,而不是外围。
    在这里插入图片描述
    卷积前需要先对kernel进行转置,逆时针旋转180°。
    在这里插入图片描述
    在补零后的图像上滑动,滑动间隔固定为1,与stride无关,得到一个6*6的上采样结果。
    在这里插入图片描述
    因为padding为1,我们去掉最外围的一圈,得到最终的上采样结果。
    在这里插入图片描述

    通过拆解转置卷积计算输出尺寸的公式可以清晰看到以上操作的依据
    H=(L1)stride+kernel2padding H = (L - 1) * stride + kernel - 2 * padding
    补零:(L - 1) * stride个
    消融:2 * padding行和列,即padding圈

    pytorch验证

    import torch
    import torch.nn.functional as F
    import numpy as np
    #创建img
    img = np.array([1,2,3,4], np.float32)
    img = np.reshape(img, [1,1,2,2])
    img = torch.from_numpy(img)
    #创建kernel
    w = np.array(range(1,17),np.float32)
    w = np.reshape(w, [1,1,4,4])
    w = torch.from_numpy(w)
    #计算
    output = F.conv_transpose2d(img, w, None, 2, 1)
    print(output)
    

    总结

    转置卷积与普通卷积在具体实现上都是卷积运算,不同的是对于转置卷积:

    1. 普通卷积在图像外部补零,转置卷积在图像内部补零
    2. 普通卷积由padding决定补零个数,转置卷积由stride决定补零个数
    3. 转置卷积由padding决定最后消去的圈数

    PS:由于在实际使用中kernel为随机初始化,所以不需要特意去关心转置的问题。

    展开全文
  • 转置卷积(transposed convolution)是一种上采样技术,操作过程是卷积的反过程,也被称作反卷积(deconvolution),但它的操作结果不是卷积的逆。它也可以通过卷积操作来实现,只是需要将卷积核旋转180度。它主要...

    前言


      原先是将这篇笔记和上一篇笔记合起来写的,但是由于内容很多,于是将卷积与转置卷积分作两篇。转置卷积(transposed convolution)是一种上采样技术,操作过程是卷积的反过程,也被称作反卷积(deconvolution),但它的操作结果不是卷积的逆。它也可以通过卷积操作来实现,只是需要将卷积核旋转180度。它主要应用在图像分割和超分辨率等任务中。笔记主要包括转置卷积操作和Pytorch转置卷积层。本笔记的知识框架主要来源于深度之眼,并依此作了内容的丰富拓展,拓展内容主要源自对torch文档的翻译,对孙玉林等著的PyTorch深度学习入门与实战的参考和自己的粗浅理解,所用数据来源于网络。发现有人在其他平台照搬笔者笔记,不仅不注明出处,有甚者更将其作为收费文章,因此笔者将在文中任意位置插入识别标志。

       笔记是笔者根据自己理解一字一字打上去的,还要到处找合适的图片,有时为了便于理解还要修图,原创不易,转载请注明出处,文中笔者哪怕是引图也注明了出处的

      结果可视化见:深度之眼Pytorch打卡(十):Pytorch数据预处理——数据统一与数据增强(上)
      卷积操作见:深度之眼Pytorch打卡(十四):Pytorch卷积神经网络部件——卷积操作与卷积层、转置卷积操作与转置卷积层(反卷积)(对卷积转置卷积细致动图分析)


    转置卷积操作(transposed convolution)


    • 转置卷积

      转置卷积有时被称作反卷积,结果上它并不是卷积的逆,但操作上的确是卷积的反过程。它是一种上采样技术,可以理解成把输入尺寸放大的技术,被广泛的应用在图像分割,超分辨率等应用中。与传统的上采样技术,如线性插值,双线性插值等方法相比,转置卷积是一种需要训练,可学习的方法。图1所示的是一种著名的图像分割网络架构,即编码器-解码器网络,其编码部分用CNN架构,解码部分便使用的转置卷积1图源

    图像分割编码器-解码器网络

    图1.图像分割的下采样和上采样网络
    • 转置卷积操作

      现假设有一输入尺寸为2x2,转置卷积核大小为2x2,需要上采样到3x3,如图2所示,图改自图源。将输入左上角的值0与内核相乘,得到的2x2的张量放输出的左上角(红框区域),将输入右上角的值1与内核相乘,得到的2x2的张量放输出的右上角(蓝框区域),将输入左下角的值2与内核相乘,得到的2x2的张量放输出的左下角(黄框区域),将输入右下角的值3与内核相乘,得到的2x2的张量放输出的右下角(灰区域),重叠部分的值相加就得到了转置卷积的结果。
    (CSDN意疏原创笔记:https://blog.csdn.net/sinat_35907936/article/details/107833112)转置卷积过程

    图2.输入、内核与输出尺寸

      上述转置卷积过程可以通过图3清晰表达出来,图源。观其过程,输入的每一个神经元都与输出的每4个神经元相连,并且共享一个卷积核,操作上的确是卷积反过来。从图1中解码部分示意图中也可以瞥见一斑。

    转置卷积过程

    图3.转置卷积过程

      所以,转置卷积的输出尺寸公式,就应是卷积输出公式反函数,如式(1)。输出尺寸w1、输入尺寸w,卷积核大小f,缩减大小p和步长spadding变成了,故有padding时应该将输入向中心缩小。s(w-1),可以知道当有stride时,是将输入做膨胀,与dilation类似。两者都是卷积处操作的反过程。

    在这里插入图片描述

      上述的运算过程可以通过卷积运算来完成,将内核旋转180度,然后与输入做卷积,保证输入与内核至少有一个元素相交,不相交部分输入补零。过程如图4所示,图改自图源

    转置卷积变卷积

    图4.转置卷积变卷积

    Pytorch转置卷积层


      Pytorch的转置卷积层有三个,分别是nn.ConvTranspose1d()、nn.ConvTranspose2d()和nn.ConvTranspose3d()。三者的操作过程与参数都是相同的,所以以下也只单独学习最最常用的nn.ConvTranspose2d()

    CLASS torch.nn.ConvTranspose2d(in_channels: int, 
                                  out_channels: int, 
                                  kernel_size: Union[int, Tuple[int, int]], 
                                  stride: Union[int, Tuple[int, int]] = 1, 
                                  padding: Union[int, Tuple[int, int]] = 0, 
                                  output_padding: Union[int, Tuple[int, int]] = 0, 
                                  groups: int = 1, 
                                  bias: bool = True, 
                                  dilation: int = 1, 
                                  padding_mode: str = 'zeros')
    

      容易发现转置卷积层的参数与卷积层的参数很多是一致的,没有差异的参数将不赘述,详见此文。 以下只列出有差异的参数。用1x1kernel时,可以由输入输出之间的关系,来间接反应这些参数的影响,以此来验证式(1)处的分析。

      代码:

    import torch
    import torch.nn as nn
    
    in_tensor = torch.tensor([[1, 2, 3, 4, 5],
                              [1, 2, 3, 4, 5],
                              [1, 2, 3, 4, 5],
                              [1, 2, 3, 4, 5],
                              [1, 2, 3, 4, 5]], dtype=torch.float)
    in_tensor = torch.reshape(in_tensor, [1, 1, 5, 5])              # 转到四维
    print(in_tensor)
    deconv1 = nn.ConvTranspose2d(1, 1, (1, 1), bias=False, stride=1)
    deconv1.weight.data = torch.tensor([[[[1]]]], dtype=torch.float)
    out_tensor = deconv1(in_tensor)
    print(out_tensor)
    
    # CSDN意疏原创笔记:https://blog.csdn.net/sinat_35907936/article/details/107833112
    

      stride:stride=1padding=0kernel=1*1并且权值为1时,由图5方法可得输出应该和输入一模一样。代码验证后发现的确如此。维持其他参数不变,让stride=2时,可以发现输出是在输入的每两个元素间都插入了一个0的结果,此时两个相邻非零元素的间距是2。维持其他参数不变,让stride=3时,可以发现输出是在输入的每两个元素间都插入了两个0的结果,此时两个相邻非零元素的间距是3…。可见转置卷积层的stride,就等价于在input每两个元素之间插入(stride-1)0,即将输入膨胀。

      结果:

    # input
    tensor([[[[1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.]]]])
              
    # stride = 1  padding = 0        
    tensor([[[[1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.]]]], grad_fn=<SlowConvTranspose2DBackward>)
              
    # stride = 2  padding = 0  
    tensor([[[[1., 0., 2., 0., 3., 0., 4., 0., 5.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 2., 0., 3., 0., 4., 0., 5.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 2., 0., 3., 0., 4., 0., 5.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 2., 0., 3., 0., 4., 0., 5.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 2., 0., 3., 0., 4., 0., 5.]]]],
           grad_fn=<SlowConvTranspose2DBackward>)  
    # stride = 3  padding = 0  
    tensor([[[[1., 0., 0., 2., 0., 0., 3., 0., 0., 4., 0., 0., 5.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 0., 2., 0., 0., 3., 0., 0., 4., 0., 0., 5.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 0., 2., 0., 0., 3., 0., 0., 4., 0., 0., 5.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 0., 2., 0., 0., 3., 0., 0., 4., 0., 0., 5.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 0., 2., 0., 0., 3., 0., 0., 4., 0., 0., 5.]]]],
           grad_fn=<SlowConvTranspose2DBackward>) 
    
    # CSDN意疏原创笔记:https://blog.csdn.net/sinat_35907936/article/details/107833112
    

      padding: 与上面同样的输入和卷积核,让stride = 1,修改paddingpadding=1时,输出是输入上下少一行,左右少一列的结果。padding=2时,输出是输入上下少两行行,左右少两行的结果。可见转置卷积层的padding,就等价于在input上,边缘处上下各去掉padding行,左右各去掉padding列的结果。即将输入向中心缩小。

    # input
    tensor([[[[1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.]]]])
              
    # padding = 1  stride = 1       
    tensor([[[[2., 3., 4.],
              [2., 3., 4.],
              [2., 3., 4.]]]], grad_fn=<SlowConvTranspose2DBackward>)
              
    # padding = 2  stride = 1
    tensor([[[[3.]]]], grad_fn=<SlowConvTranspose2DBackward>)
    
    # CSDN意疏原创笔记:https://blog.csdn.net/sinat_35907936/article/details/107833112
    

      output_padding: 默认在输出的最右边拓展output_padding列,在输出的最下边拓展output_padding行。output_padding必须比stride或者dilation小,否则会报错:RuntimeError: output padding must be smaller than either stride or dilation.

    deconv1 = nn.ConvTranspose2d(1, 1, (1, 1), bias=False, stride=2, padding=0, output_padding=1)
    

      结果:

    # input
    tensor([[[[1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.],
              [1., 2., 3., 4., 5.]]]])
              
    # stride=2, padding=0, output_padding=1
    tensor([[[[1., 0., 2., 0., 3., 0., 4., 0., 5., 0.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 2., 0., 3., 0., 4., 0., 5., 0.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 2., 0., 3., 0., 4., 0., 5., 0.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 2., 0., 3., 0., 4., 0., 5., 0.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.],
              [1., 0., 2., 0., 3., 0., 4., 0., 5., 0.],
              [0., 0., 0., 0., 0., 0., 0., 0., 0., 0.]]]],
           grad_fn=<SlowConvTranspose2DBackward>)
    
    # CSDN意疏原创笔记:https://blog.csdn.net/sinat_35907936/article/details/107833112
    

      加上dilation,output_padding后的转置卷积输出尺寸公式如下所示。其中,W指输出的宽,H指输出的高。

    H out =(H in−1)×stride[0]−2×padding[0]+dilation[0]×(kernel_size[0]−1)+output_padding[0]+1
    W out =(W in−1)×stride[1]−2×padding[1]+dilation[1]×(kernel_size[1]−1)+output_padding[1]+1

    • nn.ConvTranspose2d()实现

      转置卷积同样需要转矩阵乘法来实现,如图5所示,其改自国外一篇文章:Up-sampling with Transposed Convolution。与卷积一样,转置卷积实现的关键也在于产生稀疏矩阵(sparse matrix),将稀疏矩阵与输入内积的结果resize就可以得到转置卷积的输出。

    转置卷积转矩阵乘法

    图5.转置卷积转矩阵乘法
      

      稀疏矩阵D(sparse matrix D) 也是由被flatten的转置卷积核叠放而成的,与卷积层那里有相似之处。剔除卷积核中的某些元素,让其和输入尺寸一致,然后再fatten成一维张量。由图4的方法,我们可以知道应该保留转置卷积核的哪些值,该剔除哪些值,如图6所示,其改自国外一篇文章:Up-sampling with Transposed Convolution。由于输出是4X4,故应该叠16层。
    转置卷积稀疏矩阵

    图6.转置卷积稀疏矩阵

      回忆卷积层处的稀疏矩阵C,当转置卷积与卷积的输入输出尺寸是互换的,并且相同卷积核时,转置卷积形成的稀疏矩阵D与卷积形成的稀疏矩阵C是转置的关系,不同卷积核时,如果只看元素位置,不看元素大小,也是转置的关系,这也是转置卷积名称的来源,如图7所示,其改自国外一篇文章:Up-sampling with Transposed Convolution。所以我们可以用产生稀疏矩阵C的方法,来间接产生稀疏矩阵D,以简化产生过程。

    转置卷积稀疏矩阵与卷积稀疏矩阵的关系

    图7.转置卷积稀疏矩阵与卷积稀疏矩阵的关系
    • nn.ConvTranspose2d()应用

      在设计时,一般我们是知晓输入和输出尺寸的,而我们需要推算的应该是stridepadding和卷积核的大小。以上一篇笔记中卷积得到的438x438的特征图作为输入。输出440x440的图像,由于只放大了一点点,故stride=1,在不用空洞,不设dilation,output_padding的情况下,则440=437-2*padding+kernel_size,如果kernel_size=3,则padding=0,若kernel_size=4,则padding=1

      取stride=1padding=0,kernel_size=4这正好是先前卷积过程的反过程。

      代码:transform_inverse()函数定义见此文

    import torch
    import torch.nn as nn
    from PIL import Image
    import matplotlib.pyplot as plt
    import torchvision.transforms as transforms
    from tools.transform_inverse import transform_inverse
    
    pil_img = Image.open('data/lenna.jpg').convert('L')
    img = transforms.ToTensor()(pil_img)
    c = img.size()[0]
    h = img.size()[1]
    w = img.size()[2]
    input_img = torch.reshape(img, [1, c, h, w])  # 转换成4维,[batch_size, c, h, w]
    print(input_img.size())
    
    # 卷积层
    conv1 = nn.Conv2d(1, 2, (3, 3), bias=False)   # 实例化
    conv1.weight.data[0] = torch.tensor([[1, 2, 1],
                                         [0, 0, 0],
                                         [-1, -2, -1]])  # 水平边缘的sobel算子
    conv1.weight.data[1] = torch.tensor([[-1, 0, 1],
                                         [-2, 0, 2],
                                         [-1, 0, 1]])    # 竖直边缘的sobel算子
    # print(conv1.weight.data)
    
    out_img = conv1(input_img)                           # 两张特征图
    print(out_img.size())
    out_img = torch.squeeze(out_img, dim=0)
    out_img_ = out_img
    out_img = out_img[0]+out_img[1]                      # 水平边缘加竖直边缘等于全部边缘
    out_pil_img = transform_inverse(torch.reshape(out_img, [1, out_img.size()[0], out_img.size()[1]]), None)
    plt.figure(0)
    ax = plt.subplot(2, 2, 1)
    ax.set_title('conv input picture')
    ax.imshow(pil_img, cmap='gray')
    ax = plt.subplot(2, 2, 2)
    ax.set_title('conv output picture')
    ax.imshow(out_pil_img, cmap='gray')
    
    # CSDN意疏原创笔记:https://blog.csdn.net/sinat_35907936/article/details/107833112
    # 转置卷积层
    deconv1 = nn.ConvTranspose2d(2, 1, (3, 3), bias=False)
    deconv1.weight.data[0] = torch.tensor([[1, 2, 1],
                                         [0, 0, 0],
                                         [-1, -2, -1]])  # 水平边缘的sobel算子
    deconv1.weight.data[1] = torch.tensor([[-1, 0, 1],
                                         [-2, 0, 2],
                                         [-1, 0, 1]])    # 竖直边缘的sobel算子
    de_out = deconv1(torch.reshape(out_img_, [1, out_img_.size()[0],
                                              out_img_.size()[1], out_img_.size()[2]]))
    de_out = torch.squeeze(de_out, dim=0)
    print(de_out.size())
    de_out_pil_img = transform_inverse(de_out, None)
    ax = plt.subplot(2, 2, 3)
    ax.set_title('deconv input picture')
    ax.imshow(out_pil_img, cmap='gray')
    ax = plt.subplot(2, 2, 4)
    ax.set_title('deconv output picture')
    ax.imshow(de_out_pil_img, cmap='gray')
    plt.show()
    
    
    # CSDN意疏原创笔记:https://blog.csdn.net/sinat_35907936/article/details/107833112
    

      结果:经过上采样,细节更丰富了,但并没有恢复原图,如图8所示。可见转置卷积只可以被看做是卷积的反过程,而不是卷积的逆。
    转置卷积前后对比图


    1. https://towardsdatascience.com/transposed-convolution-demystified-84ca81b4baba ↩︎

    展开全文
  • 一文搞懂反卷积,转置卷积

    万次阅读 多人点赞 2018-07-18 15:06:59
    一文搞懂反卷积,转置卷积 前言 本文翻译自《Up-sampling with Transposed Convolution》,这篇文章对转置卷积和反卷积有着很好的解释,这里将其翻译为中文,以飨国人。 如有谬误,请联系指正。转载请注明出处...

    前言

    本文翻译自《Up-sampling with Transposed Convolution》,这篇文章对转置卷积和反卷积有着很好的解释,这里将其翻译为中文,以飨国人。如有谬误,请联系指正。转载请注明出处。

    \nabla 联系方式:

    e-mail: FesianXu@gmail.com

    QQ: 973926198

    github: https://github.com/FesianXu

    知乎专栏: 计算机视觉/计算机图形理论与应用

    微信公众号
    qrcode


    用转置卷积进行上采样

    对于上采用的需求

    当我们用神经网络生成图片的时候,经常需要将一些低分辨率的图片转换为高分辨率的图片。
    lowtohigh

    对于这种上采样(up-sampling)操作,目前有着一些插值方法进行处理:

    1. 最近邻插值(Nearest neighbor interpolation)
    2. 双线性插值(Bi-Linear interpolation)
    3. 双立方插值(Bi-Cubic interpolation)

    以上的这些方法都是一些插值方法,需要我们在决定网络结构的时候进行挑选。这些方法就像是人工特征工程一样,并没有给神经网络学习的余地,神经网络不能自己学习如何更好地进行插值,这个显然是不够理想的。

    为什么是转置卷积

    转置卷积(Transposed Convolution)常常在一些文献中也称之为反卷积(Deconvolution)和部分跨越卷积(Fractionally-strided Convolution),因为称之为反卷积容易让人以为和数字信号处理中反卷积混起来,造成不必要的误解,因此下文都将称为转置卷积,并且建议各位不要采用反卷积这个称呼。

    如果我们想要我们的网络可以学习到最好地上采样的方法,我们这个时候就可以采用转置卷积。这个方法不会使用预先定义的插值方法,它具有可以学习的参数。理解转置卷积这个概念是很重要的,因为它在若干重要的文献中都有所应用,如:

    1. DCGAN中的生成器将会用随机值转变为一个全尺寸(full-size)的图片,这个时候就需要用到转置卷积。
    2. 在语义分割中,会使用卷积层在编码器中进行特征提取,然后在解码层中进行恢复为原先的尺寸,这样才可以对原来图像的每个像素都进行分类。这个过程同样需要用到转置卷积。

    卷积操作

    让我们回顾下卷积操作是怎么工作的,并且我们将会从一个小例子中直观的感受卷积操作。假设我们有一个4×44 \times 4的矩阵,我们将在这个矩阵上应用3×33 \times 3的卷积核,并且不添加任何填充(padding),步进参数(stride)设置为1,就像下图所示,输出为一个2×22 \times 2的矩阵。
    这里写图片描述

    这个卷积操作在输入矩阵和卷积核中,对每个元素的乘积进行相加。因为我们没有任何填充和使用1为步进,因此我们只能对这个操作进行4次,因此我们的输出矩阵尺寸为2×22 \times 2
    这里写图片描述
    这种卷积操作使得输入值和输出值之间存在有位置上的连接关系,举例来说,输入矩阵左上方的值将会影响到输出矩阵的左上方的值。更具体而言,3×33 \times 3的卷积核是用来连接输入矩阵中的9个值,并且将其转变为输出矩阵的一个值的。一个卷积操作是一个多对一(many-to-one)的映射关系。让我们记住这个,我们接下来将会用得着。

    反过来操作吧

    现在,假设我们想要反过来操作。我们想要将输入矩阵中的一个值映射到输出矩阵的9个值,这将是一个一对多(one-to-many)的映射关系。这个就像是卷积操作的反操作,其核心观点就是用转置卷积。举个例子,我们对一个2×22 \times 2的矩阵进行上采样为4×44 \times 4的矩阵。这个操作将会维护一个1对应9的映射关系。
    这里写图片描述
    因此就结论而言,卷积操作是多对一,而转置卷积操作是一对多,如下图所示,每一个“对”而言,都需要维护一个权值。
    在这里插入图片描述

    但是我们将如何具体操作呢?为了接下来的讨论,我们需要定义一个卷积矩阵(convolution matrix)和相应的转置卷积矩阵(transposed convolution matrix)。

    卷积矩阵

    我们可以将一个卷积操作用一个矩阵表示。这个表示很简单,无非就是将卷积核重新排列到我们可以用普通的矩阵乘法进行矩阵卷积操作。如下图就是原始的卷积核:
    这里写图片描述
    我们对这个3×33 \times 3的卷积核进行重新排列,得到了下面这个4×164 \times 16的卷积矩阵:
    这里写图片描述

    这个便是卷积矩阵了,这个矩阵的每一行都定义了一个卷积操作。下图将会更加直观地告诉你这个重排列是怎么进行的。每一个卷积矩阵的行都是通过重新排列卷积核的元素,并且添加0补充(zero padding)进行的。
    这里写图片描述
    为了将卷积操作表示为卷积矩阵和输入矩阵的向量乘法,我们将输入矩阵4×44 \times 4摊平(flatten)为一个列向量,形状为16×116 \times 1,如下图所示。
    这里写图片描述

    我们可以将这个4×164 \times 16的卷积矩阵和1×161 \times 16的输入列向量进行矩阵乘法,这样我们就得到了输出列向量。
    这里写图片描述

    这个输出的4×14 \times 1的矩阵可以重新塑性为一个2×22 \times 2的矩阵,而这个矩阵正是和我们一开始通过传统的卷积操作得到的一模一样。
    这里写图片描述

    简单来说,这个卷积矩阵除了重新排列卷积核的权重之外就没有啥了,然后卷积操作可以通过表示为卷积矩阵和输入矩阵的列向量形式的矩阵乘积形式进行表达。

    所以各位发现了吗,关键点就在于这个卷积矩阵,你可以从16(4×44 \times 4)到4(2×22 \times 2)因为这个卷积矩阵尺寸正是4×164 \times 16的,然后呢,如果你有一个16×416 \times 4的矩阵,你就可以从4(2×22 \times 2)到16(4×44 \times 4)了,这不就是一个上采样的操作吗?啊哈!让我们继续吧!

    转置卷积矩阵

    我们想要从4(2×22 \times 2)到16(4×44 \times 4),因此我们使用了一个16×416 \times 4的矩阵,但是还有一件事情需要注意,我们是想要维护一个1到9的映射关系。

    假设我们转置这个卷积矩阵C  (4×16)C \ \ (4 \times 16)变为CT  (16×4)C^T \ \ (16 \times 4)。我们可以对CTC_T和列向量(4×1)(4 \times 1)进行矩阵乘法,从而生成一个16×116 \times 1的输出矩阵。这个转置矩阵正是将一个元素映射到了9个元素。
    这里写图片描述

    这个输出可以塑形为(4×4)(4 \times 4)的矩阵:
    这里写图片描述

    我们只是对小矩阵(2×2)(2 \times 2)进行上采样为一个更大尺寸的矩阵(4×4)(4 \times 4)。这个转置卷积矩阵维护了一个1个元素到9个元素的映射关系,因为这个关系正表现在了其转置卷积元素上。

    需要注意的是:这里的转置卷积矩阵的参数,不一定从原始的卷积矩阵中简单转置得到的,转置这个操作只是提供了转置卷积矩阵的形状而已。

    总结

    转置卷积操作构建了和普通的卷积操作一样的连接关系,只不过这个是从反向方向开始连接的。我们可以用它进行上采样。另外,这个转置卷积矩阵的参数是可以学习的,因此我们不需要一些人为预先定义的方法。即使它被称为转置卷积,它并不是意味着我们将一些现存的卷积矩阵简单转置并且使用其转置后的值。

    从本质来说,转置卷积不是一个卷积,但是我们可以将其看成卷积,并且当成卷积这样去用。我们通过在输入矩阵中的元素之间插入0进行补充,从而实现尺寸上采样,然后通过普通的卷积操作就可以产生和转置卷积相同的效果了。你在一些文章中将会发现他们都是这样解释转置卷积的,但是这个因为在卷积操作之前需要通过添加0进行上采样,因此是比较低效率的。

    注意:转置卷积会导致生成图像中出现棋盘效应(checkerboard artifacts),这篇文章《Deconvolution and Checkerboard Artifacts》推荐了一种上采样的操作(也就是插值操作),这个操作接在一个卷积操作后面以减少这种现象。如果你的主要目的是生成尽可能少棋盘效应的图像,那么这篇文章就值得你去阅读。


    补充内容

    评论区有朋友提出了一个问题,我觉得可能有些朋友也会有类似的疑问因此在这里统一讨论下,问题为:

    博主您好,我觉的转置卷积矩阵的参数随着训练过程不断被优化,但是它是在随机初始化的基础上进行优化,还是在原始卷积矩阵的基础上进行优化? – CSDN user https://me.csdn.net/BistuSim

    这个问题其实可以通过观察深度学习框架的实现方式进行,我选用的是pytorch,我们打开torch.nn.ConvTranspose1d的源码,发现有:

    class ConvTranspose1d(_ConvTransposeMixin, _ConvNd):
        def __init__(self, in_channels, out_channels, kernel_size, stride=1,
                     padding=0, output_padding=0, groups=1, bias=True, dilation=1):
            kernel_size = _single(kernel_size)
            stride = _single(stride)
            padding = _single(padding)
            dilation = _single(dilation)
            output_padding = _single(output_padding)
            super(ConvTranspose1d, self).__init__(
                in_channels, out_channels, kernel_size, stride, padding, dilation,
                True, output_padding, groups, bias)
    
        @weak_script_method
        def forward(self, input, output_size=None):
            # type: (Tensor, Optional[List[int]]) -> Tensor
            output_padding = self._output_padding(input, output_size, self.stride, self.padding, self.kernel_size)
            return F.conv_transpose1d(
                input, self.weight, self.bias, self.stride, self.padding,
                output_padding, self.groups, self.dilation)
    

    不难发现其实我们的卷积核参数weights其实是在超类中定义的,我们转到_ConvNd,代码如:

    class _ConvNd(Module):
        __constants__ = ['stride', 'padding', 'dilation', 'groups', 'bias']
        def __init__(self, in_channels, out_channels, kernel_size, stride,
                     padding, dilation, transposed, output_padding, groups, bias):
            super(_ConvNd, self).__init__()
            if in_channels % groups != 0:
                raise ValueError('in_channels must be divisible by groups')
            if out_channels % groups != 0:
                raise ValueError('out_channels must be divisible by groups')
            self.in_channels = in_channels
            self.out_channels = out_channels
            self.kernel_size = kernel_size
            self.stride = stride
            self.padding = padding
            self.dilation = dilation
            self.transposed = transposed
            self.output_padding = output_padding
            self.groups = groups
            if transposed:
                self.weight = Parameter(torch.Tensor(
                    in_channels, out_channels // groups, *kernel_size))
            else:
                self.weight = Parameter(torch.Tensor(
                    out_channels, in_channels // groups, *kernel_size))
            if bias:
                self.bias = Parameter(torch.Tensor(out_channels))
            else:
                self.register_parameter('bias', None)
            self.reset_parameters()
    

    我们可以清楚的发现,其实weights或者是bias的初始化就是一般地初始化一个符合一定尺寸要求的Tensor即可了,我们也可以发现其在forward过程中并没有所真的去根据输入进行权值的所谓“转置”之类的操作。因此我认为只要一般地进行随机初始化即可了。

    而且,我们如果同时去观察torch.nn.Conv2d的类的话,其实也可以发现,其参数都是通过_ConvNd去进行初始化的,因此Conv2dConvTranspose2D的参数初始化除了尺寸的区别,其他应该类似。


    引用

    1. A guide to convolution arithmetic for deep learning.(Vincent Dumoulin, Francesco Visin).[https://arxiv.org/abs/1603.07285]

    2. Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks.(Alec Radford, Luke Metz, Soumith Chintala)
      [https://arxiv.org/pdf/1511.06434v2.pdf]

    3. Fully Convolutional Networks for Semantic Segmentation.(Jonathan Long, Evan Shelhamer, Trevor Darrell)
      [https://people.eecs.berkeley.edu/~jonlong/long_shelhamer_fcn.pdf]

    4. Deconvolution and Checkerboard Artifacts.(Augustus Odena, Vincent Dumoulin, Chris Olah)
      [https://distill.pub/2016/deconv-checkerboard/]

    展开全文
  • 卷积与转置卷积

    千次阅读 2017-08-29 11:50:26
    当正向卷积步长不为1时(常常可能为2),转置卷积步长为: 转置卷积填充由正向卷积的卷积核和填充决定: 转置卷积的核和正向卷积一样:转置卷积的输出公式: 转置卷积又称微步卷积(“微步”的含义指:新的步长为1...

    当正向卷积步长不为1时(常常可能为2),转置卷积步长为: 

    转置卷积填充由正向卷积的卷积核和填充决定:   

    转置卷积的核和正向卷积一样:

    转置卷积的输出公式:     

        转置卷积又称微步卷积(“微步”的含义指:新的步长为1,而之前的步长为2,使得转置卷积的滑窗处理相比较卷积的“小”。),可以视作传统卷积操作的一种“逆向”传递过程;并且,转置卷积受“正向”卷积的参数约束,即步长stride和零填充(zero-padding)。下面给出stride=1和padding=0、stride=1和padding=1、stride=2和padding=0、stride=2和padding=1的卷积和转置卷积例子。













      conv_arithmetic点击打开链接


    展开全文
  • 转置卷积详解

    万次阅读 2020-03-14 11:53:18
    转置卷积详解   前面文章对卷积做了讲解,感觉既然重新整理,就将系列概念整体做个梳理,也算是将自己知道的所有东西拿来献丑把。   转置卷积(Transposed Convolution)是后来的叫法,一开始大家都是称逆卷积/反...
  • 转置卷积、又称反卷积其实是卷积的逆过程。卷积的过程通常会减小特征图的的大小,而转置卷积往往增大特征图的大小 卷积 卷积操作,输入是一个特征图i,参数padding§, stride(s),kernel_size(k),输出是卷积后得到...
  • 抽丝剥茧,带你理解转置卷积(反卷积)

    万次阅读 多人点赞 2019-02-18 14:12:18
    这里写自定义目录标题转置卷积普通卷积(直接卷积)转置卷积形象化的转置卷积欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片...
  • 亚像素卷积和转置卷积

    千次阅读 2019-05-20 15:27:14
    CNN中对特征图进行上采样常用的操作有转置卷积和亚像素卷积 1、转置卷积 转置卷积(Transposed Convolution)又称为反卷积(Deconvolution) 先看下普通的卷积过程: 如下图: 这是一个卷积核大小为3x3,...
  • 文章目录转置卷积Convolution as matrix operationTransposed convolution理解 GAN涉及到转置卷积,这里mark一下忘记了再回来看下。 转置卷积 转置卷积源于与卷积方向相反的愿望,即从卷积的output形状到input形状,...
  • 我们把从低分辨率特征图(小尺寸)到高分辨率特征图(大尺寸)称为上采样,上采样有两种方式:各种插值方法、转置卷积(反卷积)。 二、转置卷积 转置卷积(Transposed Convolution)又称为反卷积(Deconvolution)...
  • 在CNN中,转置卷积是一种上采样(up-sampling)的方法.如果你对转置卷积感到困惑,那么就来读读这篇文章吧. 本文的notebook代码在Github. 上采样的需要 在我们使用神经网络的过程中,我们经常需要上采样(up-sampling)...
  • 文章目录普通卷积转置卷积深度可分离卷积(1) 普通卷积(2) 深度可分离卷积(3)总结分组卷积(Group convolution)(1) 介绍(2) 分组卷积具体的例子 普通卷积 可以直观的理解为一个带颜色小窗户(卷积核)在原始的输入...
  • 2.转置卷积 二、公式推导 1.卷积公式推导 2.转置卷积公式推导 3.代码演示 一、公式 1.卷积 2.转置卷积 在Pytorch中,转置卷积层参数还含有output_padding,用于解决输出大小不唯一(歧义)的问题,...
  • 关于转置卷积(反卷积)的理解

    万次阅读 多人点赞 2018-04-25 23:32:57
    本文地址:... 什么是转置卷积(反卷积)? 转置卷积(Transposed Convolution)又称为反卷积(Deconvolution)。在PyTorch中可以使用torch.nn.ConvTranspose2d()来调用,在Caffe中也有对应的层deco...
  • 转置卷积又被称为反卷积和逆卷积,但是转置卷积才是最正规和主流的叫法,在主流的深度学习框架中,转置卷积的函数名都是conv_transpose 因为转置卷积的运算过程很容易让人误解,举一个例子,一个4*4的输入经过3*3的...
  • 谈谈关于转置卷积的理解 为什么是转置卷积 卷积操作 反过来操作 卷积矩阵 转置卷积矩阵 总结 最近看到反卷积的概念,开始不太明白,然后查了些资料,总算搞明白了,顺便在此对结合自己的理解做一记录。 当我们使用...
  • 卷积的正向运算是转置卷积的反向运算 转置卷积的正向运算时卷积的反向运算 具体解析如下: https://www.cnblogs.com/kk17/p/10094075.html CSDNhttps://blog.csdn.net/qq_16234613/article/details/79387345 ...
  • 深度学习中的卷积与转置卷积1 卷积2 转置卷积(反卷积)3 个人理解 1 卷积 输入平展为 16×1 的矩阵,并将卷积核转换为一个稀疏矩阵C(4×16)。然后,在稀疏矩阵和平展的输入之间使用矩阵乘法。之后,再将所得到的...
  • 转置卷积学习笔记

    2020-04-13 17:58:29
    转置卷积最全博客链接 (最下面) 这一份足够了!!!因为连我这个学渣都看懂了 deconv / transposed conv 转置卷积又叫反卷积、逆卷积。不过转置卷积是目前最为正规和主流的名称,因为这个名称更加贴切的描述了卷积的...
  • 插值方法不需要学习任何参数,而转置卷积就像卷积一样需要学习参数。 tf.nn.conv2d_transpose中设置参数“SAME”,参考【3】,是在输入四周PAD一圈或者两圈0,然后在裁减,输出不唯一;设置参数“VALID”时,直接在...
  • 文章目录卷积、转置卷积、可分离卷积、分组卷积、空洞卷积的理解转置卷积深度可分离卷积分组卷积空洞卷积 卷积、转置卷积、可分离卷积、分组卷积、空洞卷积的理解 这里主要是汇总一些本人觉得比较好的文章。 ##卷积 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 776
精华内容 310
关键字:

转置卷积