概述

在解决传统目标检测问题时,我们可以用卷积神经网络对锚框预测偏移量的方式实现BoundingBox级别的目标检测,但在可靠性要求更高的场景(如自动驾驶),我们希望对网络能对给定图像的任一像素赋予含义,这也是全卷积网络可以解决一个问题——语义分割。

传统CNN结构面对语义分割任务,遇到的第一个问题就是:Conv Block实现的全部是down-sampling方法,最后使用全连接层给出图片的one-hot分类信息;语义分割要求输出尺寸尽量接近输入以实现最佳效果。

Long等人提出的全卷积网络实现了端到端、像素到像素的语义分割:全卷积网络(Fully Convolutional Network)之所以全(Full),是因为最基础的FCN设计真的只有卷积和他的衍生操作,这其中就包括著名的转置卷积。

While a general deep net computes a general nonlinear function, a net with only layers of this form computes a nonlinear filter, which we call a deep filter or fully convolutional network. An FCN naturally operates on an input of any size, and produces an output of corresponding (possibly resampled) spatial dimensions.

转置卷积 Transposed Conv

初次学习卷积神经网络中卷积操作时,大部分教材采用滑动窗口的互相关运算讲解前向传播过程。在学习转置卷积之前,我们不妨考虑如何通过构造合适的权重矩阵,用矩阵相乘实现卷积的前向传播。

P.S. 下面利用构造的稀疏矩阵实现了正向卷积,事实上,im2col可以更有效地(相比这种方法)将正向卷积问题转换为通用矩阵相乘问题(GEMM),这也是Caffe实现卷积的方法。

K = np.arange(1, 10).reshape((1,1,3,3))
conv = torch.nn.Conv2d(in_channels=1, out_channels=1, kernel_size=3)
custom_K = torch.Tensor(K)
conv.weight = torch.nn.Parameter(custom_K)
conv.bias = torch.nn.Parameter(torch.Tensor(np.zeros(1)))
print(conv.weight)


X = np.arange(1, 17).reshape((1,1,4,4))
X_T = torch.Tensor(X)

print(conv(X_T))
# 传统Conv采用互相关运算

W, k = np.zeros((4, 16)), np.zeros(11)   #  4 -- 输出的2x2  16 -- 输入flatten之后的长度
k[:3], k[4:7], k[8:] = K[0, 0, 0, :], K[0, 0, 1, :], K[0, 0, 2, :]
W[0, 0:11], W[1, 1:12], W[2, 4:15], W[3, 5:16] = k, k, k, k

# W的构造方法参考托普利兹矩阵

np.dot(W, X.reshape(16)).reshape((1, 1, 2, 2)), W

# 转换为矩阵乘法后与互相关结果相同
# 结果:
# Parameter containing:
# tensor([[[[1., 2., 3.],
#           [4., 5., 6.],
#           [7., 8., 9.]]]], requires_grad=True)
# tensor([[[[348., 393.],
#           [528., 573.]]]], grad_fn=<MkldnnConvolutionBackward>)
# (array([[[[348., 393.],
#           [528., 573.]]]]),
#  array([[1., 2., 3., 0., 4., 5., 6., 0., 7., 8., 9., 0., 0., 0., 0., 0.],
#         [0., 1., 2., 3., 0., 4., 5., 6., 0., 7., 8., 9., 0., 0., 0., 0.],
#         [0., 0., 0., 0., 1., 2., 3., 0., 4., 5., 6., 0., 7., 8., 9., 0.],
#         [0., 0., 0., 0., 0., 1., 2., 3., 0., 4., 5., 6., 0., 7., 8., 9.]]))

由此我们引出转置卷积(Transposed Convulution)的非形式化定义:

如果卷积的前向计算看做$ y = W \cdot x $,其中W是上述构造的权重矩阵,那反向传播函数就可由矩阵求导得到为$ y = W^T \cdot x $。转置卷积正是交换了这两项操作。

Convolution

Deconvolution

举个简单的例子
比如 input= [3,3],Reshape之后,为A=[1,9]
B(可以理解为滤波器)=[9,4] (Toeplitz matrix)
那么A*B=C=[1,4]。Reshape C=[2,2]
所以,通过B卷积,我们从shape=[3,3]变成了shape=[2,2]

反过来:
输入A=[2,2],reshape之后为[1,4]
B的转置为[4,9]
那么A*B=C=[1,9],reshape为[3,3]
所以,通过B的转置 - "反卷积",我们从shape=[2,2]得到了shape=[3,3]

需要注意,转置卷积(Transposed Convolution)并不等同于反卷积,数学意义上的反卷积(DeConvolution)可以通过feature_map还原输入,但神经网络中的转置卷积显然不能还原,因为在正向卷积的过程中丢失了感受野内部像素的相对位置、大小信息(在互相关运算中丢失)。

同时,转置卷积也不能算是卷积的逆过程,而是一种特殊的正向卷积,即Padding后旋转卷积核的正向卷积。

思维转换——全连接层看作卷积层

前面提到了,全连接层是神经网络推理过程中空间信息丢失的罪魁祸首,更何况全连接层要求固定大小的输入输出。如果将全连接层换为$ 1 \times 1 $ 的卷积层,输出就完全可以携带空间信息了。

$ 1 \times 1 $ 卷积,顾名思义就是卷积核大小仅为$ 1 \times 1 $ 的卷积层,在实际运用中是很好的降维手段,且与全连接层输出固定长度的向量不同,$ 1 \times 1 $ 卷积层仍有卷积层的性质:输出大小与输入相关。

很多文章中提到1x1卷积层和全连接层的等价性,这里我给出我个人阅读文献后的理解:

  • 1x1卷积在输入尺寸为1x1时,与全连接层的数学原理没有区别:拿我们入门的MLP分类为例,假设最后一层隐藏层大小Flatten之后为1x1x512,经过(1x1x512)x10的1x1卷积后输出1x1x10,10即为Num of classes。
  • 当输入为HxWxC时,全连接层等价于有n个HxWxC卷积核的卷积层。

由此看出,全连接层与1x1卷积层最大区别在与输入尺寸的可变性,全连接要求固定的输入尺寸,而1x1卷积层则有卷积层的性质:支持任意宽高的输入,且1x1卷积层不改变输入的宽高。

LeCun

FCN就巧妙利用了这一结构,将分类网络中的全连接层换做$ 1 \times 1 $ 卷积层,结合某些up-sampling方法的特性,可以在最后得到与输入相同大小的heatmap。

但还有个问题:由于计算量的限制,分类网络的末端feature_map往往都非常小了,得出的heatmap很粗糙达不到精度要求,这时候就要上一些奇淫技巧了。

Shift-and-stitch

Shift-and-stitch是从coarse的输出中得到dense prediction的方法之一,这个方法的名字十分直观:

假设输出相对输入的下采样因子为$f$,那么输出的map大小自然为输入的$ \frac{1}{f}$,考虑input_map做向垂直两个方向的$f^2$次平移$(\Delta x, \Delta y)$,其中$ \Delta x, \Delta y \in \{0, 1, ..., f - 1\}$。对这些平移后得到结果分别做前向传播,可以得到$f^2$个output,此时我们可以按特定的规则将这些output交织(stitch)成dense prediction。交织的规则参见:https://zhuanlan.zhihu.com/p/56035377

We find learning through upsampling, as described in the next section, to be more effective and efficient, especially when combined with the skip layer fusion described later on.

合着讲这个就是让读者知道有这么个方法咯...

Backwards Strided Convolution

论文的这一部分提到了插值也是一种上采样方法,并简要提及了图像处理中常用的resize方法:双线性插值。随后话锋一转,提到了如果下采样因子$f$是整数的话,很自然地能想到用转置卷积的方法做upsampling。

需要注意的是,作者提到了转置卷积的参数显然也是可以通过BP学习的,但后续实验中作者固定了转置卷积层的参数。

Patchwise training v.s. Fully convolutional training

Fully convolutional training很好理解,就是将分类网络中的全连接层换成卷积层。但这种方式有一个问题:对图片中每个像素的预测都接受了整个图片作为输出,而实际上就经验而言,预测一个像素的类别并不需要这么多信息。Patchwise Training则是在前馈过程中选择图片周围的小patch,可能起到帮助收敛、平衡类别的效果。

Whole image fully convolutional training is identical to patchwise training where each batch consists of all the receptive fields of the units below the loss for an image (or collection of images). 一句提及了两者的相关性。

当然还是本文的传统艺能:

We find that sampling does not have a significant effect on convergence rate compared to whole image training, but takes significantly more time due to the larger number of images that need to be considered per batch.

4.3部分提到,sampling对收敛没啥用,但还额外花了不少时间,所以之后的实验都是Fully Convolutional Training了。

总结与感受体会

全篇看下来,文章的确提出了一种优秀的网络结构,作为15年提出的模型如今还是能在语义分割领域打出一片天,不愧是计算机视觉经典之作。当然后续DeepLab也给出了语义分割的优秀解决方案,值得一读。

Last modification:June 7th, 2020 at 04:56 pm
If you think my article is useful to you, please feel free to appreciate