反卷积和上采样+卷积的区别?

关注者
275
被浏览
271,578

10 个回答

要知道两者的区别,我觉得最直观的方式就是从他们的实现方式上去分析。首先来看下反卷积是如何实现的,首先看下面反卷积的运算流程图:

https://zhuanlan.zhihu.com/p/65248401

反向过程我们不需要关注只看前向过程,看流程图最下面的一行。反卷积的前向过程就是首先做了一个矩阵乘法(或者也可以用1x1的卷积来实现),把输入 feature map 的通道数从 Cin 变成 Cout * K * K,这时候 feature map 的大小还是保持 Hin * Win 的,那反卷积是如何实现把 feature map变大的呢,其实就是接下来的一步col2im操作,这个相当于是卷积里面的 im2col 的逆过程,把中间结果的每一列从一个 Cout * K * K的向量,reshape 成 (Cout, K, K) 的 tensor,然后根据 Cout 的索引把对应 (k, k) 的 patch 回填累加到输出 feature map 对应 channel 的位置上,至于应该把这个patch回填到某张 feature map 的哪个位置上,这个可以说主要是由步长这个参数决定的,就像带步长的卷积,在做卷积操作的时候,窗口会根据步长来滑动,反卷积回填的时候,也是根据步长来滑动回填窗口。但是具体实现的时候没那么简单了,还有其他参数,具体可以参考 mxnet里面反卷积的实现

分析完反卷积的运算过程,再来看下上采样+卷积的运算过程,上采样比如双线性插值,就是把feature map根据上采样率插值出更大的feature map,feature map之间的上采样是各自独立的,所以channel数不会改变,然后再跟一个卷积提特征,这一步才可以改变channel数。

那既然他们共同的作用都是可以增大 feature map 同时改变 channel 数,那么在实际任务中该如何选择呢。反卷积有个比较大的问题就是,如果参数配置不当,很容易出现输出feature map带有明显棋盘状的现象,为什么呢,就是在col2im,然后回填这一步。

下面这个博客就分析的非常好,值得细读:

里面提供了可动态配置反卷积的kernel size和stride然后可视化输出结果,可以看到当stride为2的时候,kernel是奇数就会出现网格:

https://distill.pub/2016/deconv-checkerboard/
https://distill.pub/2016/deconv-checkerboard/

而偶数kernel就不会:

https://distill.pub/2016/deconv-checkerboard/
https://distill.pub/2016/deconv-checkerboard/

而如果是多层堆叠反卷积的话而参数配置又不当,那么棋盘状的现象就会层层传递:

https://distill.pub/2016/deconv-checkerboard/

所以当使用反卷积的时候参数配置需要特别的小心。

Talk is cheap, show me the code!:)

下面就用简单的几句代码来复现使用反卷积可能会带来的的网格问题:

import mxnet as mx
batch_size = 1
in_channel = 1
height = 5
width = 5
data_shape = (batch_size, in_channel, height, width)
data = mx.nd.ones(data_shape)
out_channel = 1
kernel_size = 3
deconv_weight_shape = (in_channel, out_channel, kernel_size, kernel_size)
deconv_weight = mx.nd.ones(deconv_weight_shape)
stride = 2
up_scale = 2
data_deconv = mx.nd.Deconvolution(data=data, weight=deconv_weight, 
				      target_shape=(height * up_scale, width * up_scale),
				      kernel=(kernel_size, kernel_size),
				      stride=(stride, stride),
				      num_filter=out_channel)
print(data_deconv)
data_upsample = mx.nd.contrib.BilinearResize2D(data=data, scale_height=up_scale, scale_width=up_scale)
conv_weight_shape = (out_channel, in_channel, kernel_size, kernel_size)