非常详细的反卷积(transposed convolution)的理解

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了非常详细的反卷积(transposed convolution)的理解相关的知识,希望对你有一定的参考价值。

参考技术A

参考链接

其实了解卷积之后可以直接看第4,5,6部分的。

上采样是把低分辨率输入转成高分辨率输出。

有以下几种上采样的方法:

如何用合适的方法进行上采样? 参考文章
反卷积不是一个预先定义好的插值方法,它有一些可学习参数(learnable parameters)。
反卷积的应用场合如下:

下面两个图带我们复习一下普通的卷积操作:先乘再加。

这种卷积操作的亮点是输出值和输入值之间存在位置上的关联性(positional connectivity)。举个例子,输入矩阵的左上角的元素会影响输出矩阵的左上角的元素。更具体地说,3*3的filter/kernel用于将输入矩阵中的9个元素关联于输出矩阵中的1个值。

如果我们需要将矩阵里的1个值和另一个矩阵里的9个值去关联起来,我们该怎么做呢?这是一个 1对多 的关系。这是卷积操作的逆向过程,因此被称为反卷积(Transposed convolution)

举个例子,我们希望将一个2 * 2的矩阵上采样成一个4 * 4的矩阵。而且需要保持1对9的对应关系,具体对应关系见下图。

但是如何执行这样的操作呢?我们先定义卷积矩阵(convolution matrix)和反卷积矩阵(transposed convolution matrix)。

我们希望可以定义这样一个矩阵。:输入矩阵和我们所定义的矩阵进行矩阵相乘就可以完成卷积计算。这个矩阵称为卷积矩阵,它是对卷积核kernel进行重新排列之后获得的。
举个例子:3*3的卷积核参数如下:

我们对这个3 3卷积核重新排列得到了一个4 16的矩阵:

上面就是一个卷积矩阵( convolution matrix)。每一行定义了一个卷积操作。卷积矩阵的每一行其实是重新排列的卷积核加了一些不同位置零填充(zero padding)。

这个卷积矩阵怎么用呢?首先我们需要去把输入矩阵(4 4)扁平化为一个列向量(16 1),如下图所示:

这样我们对这个4 16的卷积矩阵和一个16 1的输入矩阵进行矩阵乘法,进而把输出的4 1的矩阵reshape为2 2的矩阵,就能得到和上述直接进行卷积一样的结果了。

从上面的过程中,我们可以进行如下思考:
如果我们有一个4 * 16的卷积矩阵,我们就可以把16 * 1的输入矩阵变为4 * 1的输出矩阵。
那么如果我们有一个4 * 1的输入和一个16 * 4的矩阵,我们是不是可以通过矩阵乘法得到一个16 * 1的输出呢?

我们希望从4(2 * 2)到16(4 * 4)。因此我们需要用到16 * 4的矩阵。但是我们希望同时保持 1对9 的对应关系。

假设我们把卷积矩阵C(4 * 16)转置为C^T(16 * 4),把C^T(16 * 4)和一个列向量(4 * 1)相乘即可产生一个输出矩阵,而且这个反卷积矩阵C^T将输入向量中的每一个元素都跟输出向量中的9个元素相关联。

这里1对9的对应关系我自己可以用线性代数的知识去理解:反卷积矩阵中的第一列都需要且只需要跟输入向量中的第一个元素进行乘法和加法运算;反卷积矩阵中的第一列有9个非零元素;反卷积矩阵的第一列的不同元素和输入向量中的第一个元素进行乘法加法,都会影响输出向量中不同位置的元素。

以下是示意图:

输出可以reshape到4*4:

注意:实际的反卷积矩阵的权重值并不是从原始的卷积矩阵中的阿赖的。重要的是”the weight layout is transposed from that of the convolution matrix“

尽管叫做转置卷积/反卷积,但是这并不意味着我们去取已有的卷积矩阵来转置。叫做转置卷积的原因是:相比于标准的卷积矩阵(多对一),反卷积矩阵”转置“了输入和输出的对应关系(一对多)。

此外,反卷积矩阵的值是可学习的,我们并不需要一个预定义的插值方法。

其实反卷积并不是卷积,但是我们可以用卷积操作来模拟反卷积。我们在输入矩阵的值之间加零以上采样输入矩阵。加零的目的是使得直接进行卷积核进行反卷积产生一样的效果。

(一些文章是用这个方式去解释反卷积的。)但是这种方法效率更低,因为它在进行卷积前对输入进行了上采样加零。

注意 :在生成的图片(generated images)中,反卷积是 checkerboard artifacts 的产生原因。
这篇文章 介绍了上采样结合卷积操作的一种方法去减少 上述问题的出现。
如果你的主要目标是产生没有checkerboard artifacts的图片,建议阅读以上链接中的文章。

转置卷积/反卷积

转置卷积又称反卷积,逆卷积。在主流的深度学习框架之中,如Tensorflow,Pytorch,Kreas中的函数名都是conv_transpose

将一个4*4的输入通过3*3的卷积核核进行普通卷积后(无padding,stride=1),将得到2*2的输出。而转置卷积将一个2*2的输入通过同样的3*3的卷积核,将得到一个4*4的输出。这看起来像是普通卷积的逆过程。事实上,这两者没有任何关系,操作过程也是不可逆的。

普通卷积(直接卷积)

但在实际计算中,并不是通过卷积核在输入上进行滑动计算,效率太低,而是将卷积核转换为等效矩阵,将输入转化为向量,通过输入向量核卷积核矩阵的相乘获得输出向量。输出的向量经过整形便可得到我们的二维输出特征。

具体操作如下图所示,由于一个3*3的卷积核要在输入上不同位置卷积卷积4次,所以通过补0的方式,将卷积核分别置于一个4*4矩阵的四个角落,这样我们的输入可以直接和这四个4*4的矩阵进行卷积,而舍去了滑动操作。

进一步我们将输入拉成长向量,四个4*4的卷积核也进行拼接,如下图

我们记向量化的图像为  , 向量化的卷积矩阵为  ,输出特征向量为 

 

 

我们将一个1*16的行向量乘以一个16*4的矩阵,得到一个1*4的行向量,那么反过来一个1*4的向量乘以一个4*16的矩阵不就是能得到一个1*16的行向量,这既是转置卷积的思想。

转置卷积

一般卷积操作(这里只考虑最简单的无padding,stride=1的情况),都将输入的数据越卷越小,根据卷积核大小的不同,和步长的不同,输出尺寸变化也很大。但是有时候,我们需要输入一个小的特征,输出更大的尺寸的特征。比如,图像语义分割中,往往要求最终的输出的特征尺寸和原始的输入尺寸相同,但是在网络卷积核池化的过程中特征图的尺寸逐渐变小,这里转置卷积便能派上用场。在数学上,转置卷积的操作非常简单,把正常的卷积操作反过来即可。

这里需要注意的是,这两个操作并不是可逆的,对于用一个卷积核,经过转置卷积操作后并不能恢复到原始的数值,只是保留了原始的形状

形象化的转置卷积

可视化转置卷积,以上式的第一列为例

这里将输入还原为一个2*2的张量,新的卷积核由于左上角有非零值,可以计算得到右侧结果

对每一个列向量都可以做这样的变换

结合整体,仿佛是有一个更大的卷积核在2*2的大小的输入上滑动,但是输入太小,每一次卷积只能对应卷积核的一部分

直接卷积是用一个小窗户看大世界,而转置卷积是用一个大窗户的一部分去看小世界。

这里需要注意。我们定义的卷积是左上角为a,右下角为i,但是在可视化卷积的过程中需要将卷积核旋转180度 后再进行卷积。由于输入图像太小,我们按照卷积核的尺寸来进行补0操作,补0数量为0即3-1,这样就将一个转置卷积转换为对应的直接卷积

总结一下转置卷积转换为直接卷积的步骤(这里只考虑stride=1 padding=0的情况)

设卷积核大小为k*k,输入为方形矩阵

(1)对输入进行四边补0,单边补0的数量为k-1

(2)将卷积核旋转180度,再新的输入上进行直接卷积

# -*- coding: utf-8 -*-
# @Author  : qiaohezhe
# @github : https://github.com/fengduqianhe
# @Date    :  2020/1/23 12:41
# version: Python 3.7.8
# @File : tensorflow_example6.py
# @Software: PyCharm
#转置卷积的验证例子


import tensorflow as tf

x = tf.reshape(tf.constant([[1,2],
                            [4,5]],dtype=tf.float32), [1, 2, 2, 1])
kernel = tf.reshape(tf.constant([[1,2,3],
                                 [4,5,6],
                                 [7,8,9]],dtype=tf.float32), [3, 3, 1, 1])
transpose_conv = tf.nn.conv2d_transpose(x, kernel, output_shape=[1, 4, 4, 1], strides=[1,1,1,1], padding='VALID')
sess = tf.Session()
print(sess.run(x))
print(sess.run(kernel))
print(sess.run(transpose_conv))


x2 = tf.reshape(tf.constant([[0, 0, 0, 0, 0, 0],
                             [0, 0, 0, 0, 0, 0],
                             [0, 0, 1, 2, 0, 0],
                             [0, 0, 4, 5, 0, 0],
                             [0, 0, 0, 0, 0, 0],
                             [0, 0, 0, 0, 0, 0]],dtype=tf.float32), [1, 6, 6, 1])
kernel2  = tf.reshape(tf.constant([[9,8,7],
                                   [6,5,4],
                                   [3,2,1]],dtype=tf.float32), [3, 3, 1, 1])
conv = tf.nn.conv2d(x2,kernel2,strides=[1,1,1,1],padding='VALID')

print(sess.run(x2))
print(sess.run(kernel2))
print(sess.run(conv))

引用学习:

CSDN-专业IT技术社区-登录​blog.csdn.net/tsyccnh/article/details/87357447

以上是关于非常详细的反卷积(transposed convolution)的理解的主要内容,如果未能解决你的问题,请参考以下文章

转置卷积 Transpose Convolution 动手学深度学习v2 pytorch

转置卷积 Transpose Convolution 动手学深度学习v2 pytorch

第十四节,TensorFlow中的反卷积,反池化操作以及gradients的使用

何时以及如何为离散卷积进行零填充?

转置卷积/反卷积

转置卷积/反卷积