我们可以通过卷积和池化等技术可以将图像进行降维,因此,一些研究人员也想办法恢复原分辨率大小的图像,特别是在语义分割领域应用很成熟。通过对一些资料的学习,简单的整理下三种恢复方法,并进行对比。
1、上采样(Upsampling)[没有学习过程]
在FCN、U-net等网络结构中,涉及到了上采样。上采样概念:上采样指的是任何可以让图像变成更高分辨率的技术。最简单的方式是重采样和插值:将输入图片进行rescale到一个想要的尺寸,而且计算每个点的像素点,使用如双线性插值bilinear等插值方法对其余点进行插值来完成上采样过程。

pytorch torch.nn 实现上采样
在PyTorch中,上采样的层被封装在torch.nn
中的Vision Layers
里面,一共有4种:
- ① PixelShuffle
- ② Upsample
- ③ UpsamplingNearest2d
- ④ UpsamplingBilinear2d
0)PixelShuffle
感性认识
但当我们的stride = (1/r) < 1时,可以让卷积后的feature map变大——即分辨率变大,这个新的操作叫做sub-pixel convolution,具体原理可以看PixelShuffle:Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network这篇paper。
pixelshuffle算法的实现流程如上图,其实现的功能是:将一个H × W的低分辨率输入图像(Low Resolution),通过Sub-pixel操作将其变为rH x rW的高分辨率图像(High Resolution)。
但是其实现过程不是直接通过插值等方式产生这个高分辨率图像,而是通过卷积先得到r2个通道的特征图(特征图大小和输入低分辨率图像一致),然后通过周期筛选(periodic shuffing)的方法得到这个高分辨率的图像,其中r为上采样因子(upscaling factor),也就是图像的扩大倍率。
定义
该类定义如下:
class torch.nn.PixleShuffle(upscale_factor)
这里的upscale_factor
就是放大的倍数,数据类型为int
。
输入输出的shape
具体一点来说,Pixelshuffle
会将shape为(∗, r2C , H , W ) 的Tensor
给reshape成( ∗ , C , rH , rW ) 的Tensor
。形式化地说,它的输入输出的shape如下:
其中N代表batch size。
例子
下面举个例子
ps = nn.PixelShuffle(3)# 缩放到三倍,r == 3 input = torch.tensor(1, 9, 4, 4)## r^2 C == 9,所以C == 1 output = ps(input) print(output.size()) # 输出为: # torch.Size([1, 1, 12, 12])
1)Upsample(新版本中推荐使用torch.nn.functional.interpolate
)
CLASS torch.nn.Upsample(size=None, scale_factor=None, mode='nearest', align_corners=None)
上采样一个给定的多通道的 1D (temporal,如向量数据), 2D (spatial,如jpg、png等图像数据) or 3D (volumetric,如点云数据)数据
对volumetric输入(3维——点云数据),输入数据Tensor格式为5维:minibatch x channels x depth x height x width
对spatial输入(2维——jpg、png等数据),输入数据Tensor格式为4维:minibatch x channels x height x width
对temporal输入(1维——向量数据),输入数据Tensor格式为3维:minibatch x channels x width
此算法支持最近邻'nearest',线性插值'linear',双线性插值'bilinear',双三次'bicubic'和三线性插值'trilinear'对3维、4维、5维的输入Tensor分别进行上采样(Upsample)。
你可以给定scale_factor来指定输出为输入的scale_factor倍或直接使用参数size指定目标输出的大小(但是不能同时制定两个)
参数:
-
size (int or Tuple[int] or Tuple[int, int] or Tuple[int, int, int], optional) – 根据不同的输入类型制定的输出大小
-
scale_factor (float or Tuple[float] or Tuple[float, float] or Tuple[float, float, float], optional) – 指定输出为输入的多少倍数。如果输入为tuple,其也要制定为tuple类型
-
mode (str, optional) – 可使用的上采样算法,有
'nearest'
,'linear'
,'bilinear'
,'bicubic'
and'trilinear'
.默认使用
'nearest'
-
align_corners (bool, optional) – 如果为True,输入的角像素将与输出张量对齐,因此将保存下来这些像素的值。仅当使用的算法为
'linear'
,'bilinear'
or'trilinear'时可以使用。
默认设置为
False
输入输出形状:
注意:
当align_corners = True时,线性插值模式(线性、双线性、双三线性和三线性)不按比例对齐输出和输入像素,因此输出值可以依赖于输入的大小。这是0.3.1版本之前这些模式的默认行为。从那时起,默认行为是align_corners = False,如下图:
上面的图是source pixel为4*4上采样为target pixel为8*8的两种情况,这就是对齐和不对齐的差别,会对齐左上角元素,即设置为align_corners = True时输入的左上角元素是一定等于输出的左上角元素。但是有时align_corners = False时左上角元素也会相等,官网上给的例子就不太能说明两者的不同(也没有试出不同的例子,大家理解这个概念就行了)
如果您想下采样/常规调整大小,您应该使用interpolate()方法,这里的上采样方法已经不推荐使用了。
举例:
import torch from torch import nn input = torch.arange(1, 5, dtype=torch.float32).view(1, 1, 2, 2) input
返回:
tensor([[[[1., 2.], [3., 4.]]]])
'nearest'
m = nn.Upsample(scale_factor=2, mode='nearest') m(input)
返回:
tensor([[[[1., 1., 2., 2.], [1., 1., 2., 2.], [3., 3., 4., 4.], [3., 3., 4., 4.]]]])
'bilinear' align_corners=False
m = nn.Upsample(scale_factor=2, mode='bilinear',align_corners=False) m(input)
返回:
tensor([[[[1.0000, 1.2500, 1.7500, 2.0000], [1.5000, 1.7500, 2.2500, 2.5000], [2.5000, 2.7500, 3.2500, 3.5000], [3.0000, 3.2500, 3.7500, 4.0000]]]])
'bilinear' align_corners=True
m = nn.Upsample(scale_factor=2, mode='bilinear',align_corners=True) m(input)
返回:
tensor([[[[1.0000, 1.3333, 1.6667, 2.0000], [1.6667, 2.0000, 2.3333, 2.6667], [2.3333, 2.6667, 3.0000, 3.3333], [3.0000, 3.3333, 3.6667, 4.0000]]]])
m = nn.Upsample(size=(3,5), mode='bilinear',align_corners=True) m(input)
返回:
tensor([[[[1.0000, 1.2500, 1.5000, 1.7500, 2.0000], [2.0000, 2.2500, 2.5000, 2.7500, 3.0000], [3.0000, 3.2500, 3.5000, 3.7500, 4.0000]]]])
如果你使用的数据都是JPG等图像数据,那么你就能够直接使用下面的用于2D数据的方法:
2)UpsamplingNearest2d
本质上其实就是对jpg、png等格式图像数据的Upsample(mode='nearest')
CLASS torch.nn.UpsamplingNearest2d(size=None, scale_factor=None)
形状:
举例:
m = nn.UpsamplingNearest2d(scale_factor=2) m(input)
input即上面例子的input,返回:
tensor([[[[1., 1., 2., 2.], [1., 1., 2., 2.], [3., 3., 4., 4.], [3., 3., 4., 4.]]]])
m = nn.UpsamplingNearest2d(size=(3,5)) m(input)
返回:
tensor([[[[1., 1., 1., 2., 2.], [1., 1., 1., 2., 2.], [3., 3., 3., 4., 4.]]]])
3)UpsamplingBilinear2d
本质上其实就是对jpg、png等格式图像数据的Upsample(mode='bilinear')
CLASS torch.nn.UpsamplingBilinear2d(size=None, scale_factor=None)
专门用于2D数据的双线性插值算法,参数等跟上面的差不多,省略
形状:
注意:最好还是使用nn.functional.interpolate(..., mode='bilinear', align_corners=True)
举例:
m = nn.UpsamplingBilinear2d(scale_factor=2) m(input)
返回:
tensor([[[[1.0000, 1.3333, 1.6667, 2.0000], [1.6667, 2.0000, 2.3333, 2.6667], [2.3333, 2.6667, 3.0000, 3.3333], [3.0000, 3.3333, 3.6667, 4.0000]]]])
m = nn.UpsamplingBilinear2d(size=(3,5)) m(input)
返回:
tensor([[[[1.0000, 1.2500, 1.5000, 1.7500, 2.0000], [2.0000, 2.2500, 2.5000, 2.7500, 3.0000], [3.0000, 3.2500, 3.5000, 3.7500, 4.0000]]]])
2、上池化(Unpooling)[没有学习过程]
Unpooling是在CNN中常用的来表示max pooling的逆操作。这是从2013年纽约大学Matthew D. Zeiler和Rob Fergus发表的《Visualizing and Understanding Convolutional Networks》中引用的:因为max pooling不可逆,因此使用近似的方式来反转得到max pooling操作之前的原始情况:


对比上面两个示意图,可以发现区别:
- 两者的区别在于UnSampling阶段没有使用MaxPooling时的位置信息,而是直接将内容复制来扩充Feature Map。第一幅图中右边4*4矩阵,用了四种颜色的正方形框分割为四个区域,每一个区域内的内容是直接复制上采样前的对应信息。
- UnPooling的过程,特点是在Maxpooling的时候保留最大值的位置信息,之后在unPooling阶段使用该信息扩充Feature Map,除最大值位置以外,其余补0。从图中即可看到两者结果的不同。
pytorch实现上采样,参考代码:segnet_pytorch:
# Stage 5 x51 = F.relu(self.bn51(self.conv51(x4p))) x52 = F.relu(self.bn52(self.conv52(x51))) x53 = F.relu(self.bn53(self.conv53(x52))) #这个id5记录的是池化操作时最大值的index,其要设置参数return_indices为True x5p, id5 = F.max_pool2d(x53,kernel_size=2, stride=2,return_indices=True) # Stage 5d #这个是进行最大值上采样的函数,其是根据id5来把值放到什么位置,其它位置没有值的地方 #补0 x5d = F.max_unpool2d(x5p, id5, kernel_size=2, stride=2) x53d = F.relu(self.bn53d(self.conv53d(x5d))) x52d = F.relu(self.bn52d(self.conv52d(x53d))) x51d = F.relu(self.bn51d(self.conv51d(x52d)))
测试:
#测试上采样 m=nn.MaxPool2d((3,3),stride=(1,1),return_indices=True) upm=nn.MaxUnpool2d((3,3),stride=(1,1)) data4=torch.randn(1,1,3,3) output5,indices=m(data4) output6=upm(output5,indices) print('\ndata4:',data4, '\nmaxPool2d',output5, '\nindices:',indices, '\noutput6:',output6)
其输出为:
data4: tensor([[[[ 2.3151, -1.0391, 0.1074], [ 1.9360, 0.2524, 2.3735], [-0.1151, 0.4684, -1.8800]]]]) maxPool2d tensor([[[[2.3735]]]]) indices: tensor([[[[5]]]]) output6: tensor([[[[0.0000, 0.0000, 0.0000], [0.0000, 0.0000, 2.3735], [0.0000, 0.0000, 0.0000]]]])
3、反卷积(Deconvolution)或称为fractionally-strided convolutions[具有学习过程]
在介绍反卷积之前,我们需要深入了解一下卷积,一个简单的卷积层运算,卷积参数为示意图如下:
对于上述卷积运算,我们把上图所示的3×3卷积核展成一个如下所示的[4,16]的稀疏矩阵C,如下:
我们再把4×4的输入特征展成[16,1]的矩阵 ,那么
则是一个[4,1]的输出特征矩阵,把它重新排列2×2的输出特征就得到最终的结果,从上述分析可以看出卷积层的计算其实是可以转化成矩阵相乘的。
ps. 值得注意的是,在一些深度学习网络的开源框架中并不是通过这种这个转换方法来计算卷积的,因为这个转换会存在很多无用的0乘操作。
通过上述的分析,我们已经知道卷积层的前向操作可以表示为和矩阵相乘,很容易得到卷积层的反向传播就是和
的转置相乘。
我们已经说过反卷积又被称为Transposed(转置) Convolution,我们可以看出其实卷积层的前向传播过程就是反卷积层的反向传播过程,卷积层的反向传播过程就是反卷积层的前向传播过程。因为卷积层的前向反向计算分别为乘 C和 ,而反卷积层的前向反向计算分别为乘
和
,所以它们的前向传播和反向传播刚好交换过来。
下图表示一个和上图卷积计算对应的反卷积操作,其中他们的输入输出关系正好相反。如果不考虑通道以卷积运算的反向运算来计算反卷积运算的话,还可以通过离散卷积的方法来求反卷积。
当给一个特征图a, 以及给定的卷积核设置,我们分为三步进行逆卷积操作:
第一步:对输入的特征图a进行一些变换,得到新的特征图a’
第二步:求新的卷积核设置,得到新的卷积核设置,后面都会用右上角加撇点的方式区分
第三步:用新的卷积核在新的特征图上做常规的卷积,得到的结果就是逆卷积的结果,就是我们要求的结果。
pytorch中的实现:
torch.nn.
ConvTranspose2d
torch.nn.
ConvTranspose2d
(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1, padding_mode='zeros')
The parameters kernel_size
, stride
, padding
, output_padding
can either be:
-
a single
int
– in which case the same value is used for the height and width dimensions 高宽两个方向参数相同 -
a
tuple
of two ints – in which case, the first int is used for the height dimension, and the second int for the width dimension分别为高宽两个方向指定参数
Shape:
- Input: (N,Cin,Hin,Win)
- Output: (N,Cout,Hout,Wout) where
Hout=(Hin−1)×stride[0]−2×padding[0]+dilation[0]×(kernel_size[0]−1)+output_padding[0]+1
Hout=(Hin−1)×stride[0]−2×padding[0]+dilation[0]×(kernel_size[0]−1)+output_padding[0]+1
参数的含义如下:
- in_channels(int) – 输入信号的通道数
- out_channels(int) – 卷积产生的通道数
- kerner_size(int or tuple) - 卷积核的大小
- stride(int or tuple,optional) - 卷积步长,即要将输入扩大的倍数。
- padding(int or tuple, optional) - 输入的每一条边补充0的层数,高宽都增加2*padding
- output_padding(int or tuple, optional) - 输出边补充0的层数,高宽都增加padding
- groups(int, optional) – 从输入通道到输出通道的阻塞连接数
- bias(bool, optional) - 如果bias=True,添加偏置
- dilation(int or tuple, optional) – 卷积核元素之间的间距
对于每一条边输入输出的尺寸的公式如下:
4、一些反卷积的论文截图
上图为反卷积和全卷积网络为核心的语义分割网络。
图(a)是输入层;图b、d、f、h、j是不同featrue map大小的反卷积的结果;图c、e、g、i是不同featrue map大小的UnPooling结果。