SS:转置卷积与膨胀卷积
Posted zzzyzh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SS:转置卷积与膨胀卷积相关的知识,希望对你有一定的参考价值。
文章目录
前言
本文首先回顾了普通的直接卷积操作,然后讲解了在语义分割中十分常见的两种卷积操作:转置卷积(Transposed Convolution)与膨胀卷积(Dilated Convolution)
同时,还给出了相关实例,方便读者理解。具体的语义分割模型的网络结构讲解,可以参考博主主页中的其他blog
1. Convolution
1.1. Introduction
- padding = 0
- strides = 1
- kernal_size = 3
假设输入特征图大小是 4x4 的(假设输入输出都是单通道),通过卷积后得到的特征图大小为 2x2。一般使用卷积的情况中,要么特征图变小(stride > 1),要么保持不变(stride = 1),当然也可以通过四周 padding 让特征图变大但没有意义。关于卷积的详细介绍以及如何使用 Pytorch 实现卷积操作,可以看我的另一篇 blog:CV学习笔记【2】:卷积与Conv2d
1.2. Procedure
普通的卷积过程可以直观的理解为一个带颜色小窗户(卷积核)在原始的输入图像一步一步的挪动,来通过加权计算得到输出特征:
但是实际在计算机中计算的时候,并不是像这样一个位置一个位置的进行滑动计算,因为这样的效率太低了。计算机会将卷积核转换成等效的矩阵,将输入转换为向量。通过输入向量和卷积核矩阵的相乘获得输出向量。输出的向量经过整形便可得到我们的二维输出特征。具体的操作如下图所示。由于我们的 3x3 卷积核要在输入上不同的位置卷积 4 次,所以通过补零的方法将卷积核分别置于一个 4x4 矩阵的四个角落。这样我们的输入可以直接和这四个 4x4 的矩阵进行卷积,而舍去了滑动这一操作步骤:
进一步的,我们将输入拉成长向量,四个 4x4 卷积核也拉成长向量并进行拼接:
- 输入
- 卷积核
我们记向量化的图像为 I I I,向量化的卷积矩阵为 C C C,输出特征向量为 O O O,则有:
I T ∗ C = O T I^T * C = O^T IT∗C=OT
最终计算过程如下图所示:
I 1 × 16 C 16 × 4 = O 1 × 4 I^1 \\times 16 C^16 \\times 4 = O^1 \\times 4 I1×16C16×4=O1×4
2. Transposed Convolution
2.1. Introduction
转置卷积(Transposed Convolution) 在语义分割或者对抗神经网络(GAN)中比较常见,其主要作用就是做上采样(UpSampling)。在有些地方转置卷积又被称作 fractionally-strided convolution 或者 deconvolution,但 deconvolution 具有误导性,不建议使用。对于转置卷积需要注意的是:
- 转置卷积不是卷积的逆运算
- 转置卷积只是将特征层的大小还原回卷积之前的大小,但是还原后的特征层中的数值与原本的数值并不相同,所以不能认为是“逆运算”
- 转置卷积也是卷积
2.2. Procedure
转置卷积的运算步骤可以归为以下几步:
- 在输入特征图元素间填充 s-1 行、列 0(其中 s 表示转置卷积的步距)
- s = 1、s - 1 = 0,即不需要在元素间填充行和列
- s = 2、s - 1 = 1,即需要在元素间填充 1 行和 1 列 0
- s = 1、s - 1 = 0,即不需要在元素间填充行和列
- 在输入特征图四周填充 k-p-1 行、列 0(其中 k 表示转置卷积的 kernel_size 大小,p 为转置卷积的 padding,注意这里的 padding 和卷积操作中有些不同)
- k = 3、p = 0、k - p - 1 = 2,即需要在特征图四周填充 2 行和 2 列 0
- k = 3、p = 1、k - p - 1 = 1,即需要在特征图四周填充 1 行和 1 列 0(在第一步的基础上)
- k = 3、p = 0、k - p - 1 = 2,即需要在特征图四周填充 2 行和 2 列 0
- 将卷积核参数上下、左右翻转
- 做正常卷积运算(填充 0,步距 1)
假设输入的特征图大小为 2x2(假设输入输出都为单通道),通过转置卷积后得到 4x4 大小的特征图。这里使用的转置卷积核大小为 k = 3,stride = 1,padding = 0 的情况(忽略偏执bias):
- 首先在元素间填充 s-1 = 0 行、列 0(等于 0 不用填充)
- 然后在特征图四周填充 k-p-1 = 2 行、列 0
- 接着对卷积核参数进行上下、左右翻转
- 最后做正常卷积(填充 0,步距 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 ] + 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 stride[0] - 2 \\times padding[0] + kernel_size[0] \\\\ W_out = (W_in - 1) \\times stride[1] - 2 \\times padding[1] + kernel_size[1] Hout=(Hin−1)×stride[0]−2×padding[0]+kernelsize[0]Wout=(Win−1)×stride[1]−2×padding[1]+kernelsize[1]
- stride[0] 表示高度方向的 stride,padding[0] 表示高度方向的 padding,kernel_size[0] 表示高度方向的 kernel_size
- stride[1] 表示高度方向的 stride,padding[1] 表示高度方向的 padding,kernel_size[1] 表示高度方向的 kernel_size
通过上面公式可以看出 padding 越大,输出的特征矩阵高、宽越小,你可以理解为正向卷积过程中进行了 padding 然后得到了特征图,现在使用转置卷积还原到原来高、宽后要把之前的 padding 减掉
2.3. torch.nn.ConvTranspose2d
torch.nn.ConvTranspose2d 参数如下:
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 stride[0] - 2 \\times padding[0] + dilation[0] \\times (kernel_size[0] - 1) + output_padding[0] + 1 \\\\ W_out = (W_in - 1) \\times stride[1] - 2 \\times padding[1] + dilation[1] \\times (kernel_size[1] - 1) + output_padding[1] + 1 Hout=(Hin−1)×stride[0]−2×padding[0]+dilation[0]×(kernelsize[0]−1)+outputpadding[0]+1Wout=(Win−1)×stride[1]−2×padding[1]+dilation[1]×(kernelsize[1]−1)+outputpadding[1]+1
2.4. Visualization
一般的卷积操作(我们这里只考虑最简单的无 padding,stride = 1 的情况),都将输入的数据越卷越小。根据卷积核大小的不同,和步长的不同,输出的尺寸变化也很大。
在数学上,转置卷积的操作非常简单,把正常卷积的操作反过来即可:
O T ∗ C T = I T O^T * C^T = I^T OT∗CT=IT
这里需要注意的是这两个操作并不是可逆的,对于同一个卷积核,经过转置卷积操作之后并不能恢复到原始的数值,保留的只有原始的形状
结果张量:
将输入还原为一个 2x2 的张量:
在正常卷积的操作过程中,直接卷积向量化的时候是将卷积核补零然后拉成列向量,得到了一个新的转置卷积矩阵,现在将这个过程反过来,把 16 个列向量再转换成卷积核:
推理过程如下:
- 以第一个等效矩阵为例:
- 以第二个等效矩阵为例:
- 以第三个等效矩阵为例:
- 以第四个等效矩阵为例:
- 同理可以知道剩下的等效矩阵与卷积核卷积的结果
- 通过比较可以发现,使用的绿色卷积核,与一开始直接卷积的卷积核的关系为:原直接卷积卷积核上下、左右翻转后得到新卷积核
2.5. Experiment
下面使用 Pytorch 框架来模拟 s = 1,p = 0,k = 3 的转置卷积操作
在代码中 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, 以上是关于SS:转置卷积与膨胀卷积的主要内容,如果未能解决你的问题,请参考以下文章