(二十九)通俗易懂理解——转置(反)卷积+FCN+UNet

首先对为何转置卷积能够起到上采样进行说明。看第一部分应该以这样的角度去理解,不然可能也会看的云里雾里。

用转置卷积进行上采样

对于上采用的需求

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

对于这种上采样(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×4的矩阵,我们将在这个矩阵上应用3×3的卷积核,并且不添加任何填充(padding),步进参数(stride)设置为1,就像下图所示,输出为一个2×2的矩阵。

这个卷积操作在输入矩阵和卷积核中,对每个元素的乘积进行相加。因为我们没有任何填充和使用1为步进,因此我们只能对这个操作进行4次,因此我们的输出矩阵尺寸为2×2。

这种卷积操作使得输入值和输出值之间存在有位置上的连接关系,举例来说,输入矩阵左上方的值将会影响到输出矩阵的左上方的值。更具体而言,3×3的卷积核是用来连接输入矩阵中的9个值,并且将其转变为输出矩阵的一个值的。一个卷积操作是一个多对一(many-to-one)的映射关系。让我们记住这个,我们接下来将会用得着。

反过来操作吧

现在,假设我们想要反过来操作。我们想要将输入矩阵中的一个值映射到输出矩阵的9个值,这将是一个一对多(one-to-many)的映射关系。这个就像是卷积操作的反操作,其核心观点就是用转置卷积。举个例子,我们对一个2×2的矩阵进行上采样为4×4的矩阵。这个操作将会维护一个1对应9的映射关系。

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

卷积矩阵

我们可以将一个卷积操作用一个矩阵表示。这个表示很简单,无非就是将卷积核重新排列到我们可以用普通的矩阵乘法进行矩阵卷积操作。如下图就是原始的卷积核:

我们对这个3×3的卷积核进行重新排列,得到了下面这个4×16的卷积矩阵:

这个便是卷积矩阵了,这个矩阵的每一行都定义了一个卷积操作。下图将会更加直观地告诉你这个重排列是怎么进行的。每一个卷积矩阵的行都是通过重新排列卷积核的元素,并且添加0补充(zero padding)进行的。

为了将卷积操作表示为卷积矩阵和输入矩阵的向量乘法,我们将输入矩阵4×4摊平(flatten)为一个列向量,形状为16×1,如下图所示。

我们可以将这个4×16的卷积矩阵和1×16的输入列向量进行矩阵乘法,这样我们就得到了输出列向量。

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

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

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

其实以上只是说明了我们平常所做的卷积,实际上可以认为是对图片进行相应的矩阵相乘,总能找到一个矩阵对图片矩阵做变换得到与卷积一样的效果。

转置卷积矩阵

这个输出可以塑形为(4×4)的矩阵:

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

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

实际上,转置卷积如同以下的操作:

不同的框架下,参数设置可能有所不同,这部分我还没有仔细去研究究竟是如何实现的。大体上可以这么看,当我们想从2*2的特征图映射到32*32的特征图时,我们只要填入相应的参数(包括filter,padding,stride),这些参数与特征图32*32直接卷积成2*2的特征图参数一致。详细的可能需要以后看到了再继续补充。

FCN的原理

FCN将传统CNN中的全连接层转化成一个个的卷积层。如下图所示,在传统的CNN结构中,前5层是卷积层,第6层和第7层分别是一个长度为4096的一维向量,第8层是长度为1000的一维向量,分别对应1000个类别的概率。FCN将这3层表示为卷积层,卷积核的大小(通道数,宽,高)分别为(4096,1,1)、(4096,1,1)、(1000,1,1)。所有的层都是卷积层,故称为全卷积网络。

可以发现,经过多次卷积(还有pooling)以后,得到的图像越来越小,分辨率越来越低(粗略的图像),那么FCN是如何得到图像中每一个像素的类别的呢?为了从这个分辨率低的粗略图像恢复到原图的分辨率,FCN使用了上采样。例如经过5次卷积(和pooling)以后,图像的分辨率依次缩小了2,4,8,16,32倍。对于最后一层的输出图像,需要进行32倍的上采样,以得到原图一样的大小。

这个上采样是通过反卷积(deconvolution)实现的。对第5层的输出(32倍放大)反卷积到原图大小,得到的结果还是不够精确,一些细节无法恢复。于是Jonathan将第4层的输出和第3层的输出也依次反卷积,分别需要16倍和8倍上采样,结果就精细一些了。下图是这个卷积和反卷积上采样的过程:

需要说明的是,所谓融合其实就比如分别将pool4和pool5上采样后得到的特征图进行相加。

下图是32倍,16倍和8倍上采样得到的结果的对比,可以看到它们得到的结果越来越精确:

FCN的优点和不足

与传统用CNN进行图像分割的方法相比,FCN有两大明显的优点:一是可以接受任意大小的输入图像,而不用要求所有的训练图像和测试图像具有同样的尺寸。二是更加高效,因为避免了由于使用像素块而带来的重复存储和计算卷积的问题。

同时FCN的缺点也比较明显:一是得到的结果还是不够精细。进行8倍上采样虽然比32倍的效果好了很多,但是上采样的结果还是比较模糊和平滑,对图像中的细节不敏感。二是对各个像素进行分类,没有充分考虑像素与像素之间的关系,忽略了在通常的基于像素分类的分割方法中使用的空间规整(spatial regularization)步骤,缺乏空间一致性。

UNet网络结构

UNet不像FCN直接上采样到想要的特征图,而是通过一次又一次小规模的上采样得到的,并且在上采样的过程中,与下采样过程中的特征图进行融合(拼接)。如下图,仔细查看右下角的文字说明,就基本上能看懂此图,也就基本上懂了UNet的框架。

参考文章1:blog.csdn.net/LoseInVai

参考文章2:blog.csdn.net/taigw/art