热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

PyTorch学习笔记(10)——上采样和PixelShuffle

去年曾经使用过FCN(全卷积神经网络)及其派生Unet,再加上在爱奇艺的时候做过一些超分辨率重建的内容,其中用到了毕业于帝国理工的华人博士

去年曾经使用过FCN(全卷积神经网络)及其派生Unet,再加上在爱奇艺的时候做过一些超分辨率重建的内容,其中用到了毕业于帝国理工的华人博士Shi Wenzhe(在Twitter任职)发表的PixelShuffle《Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network
》的论文。PyTorch 0.4.1将这些上采样的方式定义为Vision Layers,现在对这4种在PyTorch中的上采样方法进行介绍。


0. 什么是上采样?

上采样,在深度学习框架中,可以简单的理解为**任何可以让你的图像变成更高分辨率的技术。**最简单的方式是重采样和插值:将输入图片input image进行rescale到一个想要的尺寸,而且计算每个点的像素点,使用如双线性插值bilinear等插值方法对其余点进行插值。

Unpooling是在CNN中常用的来表示max pooling的逆操作。这是从2013年纽约大学Matthew D. Zeiler和Rob Fergus发表的《Visualizing and Understanding Convolutional Networks》中引用的:因为max pooling不可逆,因此使用近似的方式来反转得到max pooling操作之前的原始情况:

记住max pooling做的时候的size,比如下图的一个4x4的矩阵,max pooling的size为2x2,stride为2,反卷积操作需要记住最大值的位置,将其余位置至为0就行。

Deconvolution(反卷积)在CNN中常用于表示一种反向卷积 ,但它并不是一个完全符合数学规定的反卷积操作。

Unpooling不同,使用反卷积来对图像进行上采样是可以习得的。通常用来对卷积层的结果进行上采样,使其回到原始图片的分辨率。
反卷积也被称为分数步长卷积(convolution with fractional strides)或者转置卷积(transpose convolution)或者后向卷积backwards strided convolution。
真正的反卷积如wikipedia里面所说,但是不会有人在实际的CNN结构中使用它。

1. Vision Layer

在PyTorch中,上采样的层被封装在torch.nn中的Vision Layers里面,一共有4种:

  • ① PixelShuffle
  • ② Upsample
  • ③ UpsamplingNearest2d
  • ④ UpsamplingBilinear2d

下面,将对其分别进行说明

1.1 PixelShuffle

正常情况下,卷积操作会使feature map的高和宽变小。

但当我们的stride&#61; 1 r <1 \frac{1}{r} <1 r1​<1 时&#xff0c;可以让卷积后的feature map的高和宽变大——即分辨率增大&#xff0c;这个新的操作叫做sub-pixel convolution&#xff0c;具体原理可以看PixelShuffle《Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network
》的论文。

pixelshuffle算法的实现流程如上图&#xff0c;其实现的功能是&#xff1a;将一个H × W的低分辨率输入图像&#xff08;Low Resolution&#xff09;&#xff0c;通过Sub-pixel操作将其变为rH x rW的高分辨率图像&#xff08;High Resolution&#xff09;。

但是其实现过程不是直接通过插值等方式产生这个高分辨率图像&#xff0c;而是通过卷积先得到 r 2 r^2 r2个通道的特征图&#xff08;特征图大小和输入低分辨率图像一致&#xff09;&#xff0c;然后通过周期筛选&#xff08;periodic shuffing&#xff09;的方法得到这个高分辨率的图像&#xff0c;其中 r r r为上采样因子&#xff08;upscaling factor&#xff09;&#xff0c;也就是图像的扩大倍率。

定义

该类定义如下&#xff1a;

class torch.nn.PixleShuffle(upscale_factor)

这里的upscale_factor就是放大的倍数&#xff0c;数据类型为int
以四维输入(N,C,H,W)为例&#xff0c;Pixelshuffle会将为(∗, r 2 C r^2C r2C,H,W)的Tensor给reshape成(∗,C,rH,rW)的Tensor。形式化地说&#xff0c;它的输入输出的shape如下&#xff1a;

  • 输入: (N,C x upscale_factor 2 ^2 2,H,W)
  • 输出: (N,C,H x upscale_factor,W x upscale_factor)

例子

>>> ps &#61; nn.PixelShuffle(3) >>> input &#61; torch.tensor(1, 9, 4, 4) >>> output &#61; ps(input) >>> print(output.size()) torch.Size([1, 1, 12, 12])

怎么样&#xff0c;是不是看起来挺简单的&#xff1f;我将在最后完整的介绍一下1&#xff09;转置卷积 2&#xff09;sub-pixel 卷积
3&#xff09;反卷积以及pixelshuffle这几个知识点。

1.2 Upsample&#xff08;新版本中推荐使用torch.nn.functional.interpolate&#xff09;

对给定多通道的1维&#xff08;temporal&#xff09;、2维&#xff08;spatial&#xff09;、3维&#xff08;volumetric&#xff09;数据进行上采样。

对volumetric输入&#xff08;3维——点云数据&#xff09;&#xff0c;输入数据Tensor格式为5维&#xff1a;minibatch x channels x depth x height x width
对spatial输入&#xff08;2维——jpg、png等数据&#xff09;&#xff0c;输入数据Tensor格式为4维&#xff1a;minibatch x channels x height x width
对temporal输入&#xff08;1维——向量数据&#xff09;&#xff0c;输入数据Tensor格式为3维&#xff1a;minibatch x channels x width

此算法支持最近邻&#xff0c;线性插值&#xff0c;双线性插值&#xff0c;三次线性插值对3维、4维、5维的输入Tensor分别进行上采样&#xff08;Upsample&#xff09;。

定义

该类定义如下&#xff1a;

class torch.nn.Upsample(size&#61;None, scale_factor&#61;None, mode&#61;&#39;nearest&#39;, align_corners&#61;None)

其中&#xff1a;

  • size 是要输出的尺寸&#xff0c;数据类型为tuple&#xff1a; ([optional D_out], [optional H_out], W_out)
  • scale_factor 在高度、宽度和深度上面的放大倍数。数据类型既可以是int——表明高度、宽度、深度都扩大同一倍数&#xff1b;亦或是tuple——指定高度、宽度、深度的扩大倍数。
  • mode 上采样的方法&#xff0c;包括最近邻&#xff08;nearest&#xff09;&#xff0c;线性插值&#xff08;linear&#xff09;&#xff0c;双线性插值&#xff08;bilinear&#xff09;&#xff0c;三次线性插值&#xff08;trilinear&#xff09;&#xff0c;默认是最近邻&#xff08;nearest&#xff09;。
  • align_corners 如果设为True&#xff0c;输入图像和输出图像角点的像素将会被对齐&#xff08;aligned&#xff09;&#xff0c;这只在mode &#61; linear, bilinear, or trilinear才有效&#xff0c;默认为False。

例子

>>> input &#61; torch.arange(1, 5).view(1, 1, 2, 2).float() >>> input tensor([[[[ 1., 2.], [ 3., 4.]]]]) >>> m &#61; nn.Upsample(scale_factor&#61;2, mode&#61;&#39;nearest&#39;) >>> m(input) tensor([[[[ 1., 1., 2., 2.], [ 1., 1., 2., 2.], [ 3., 3., 4., 4.], [ 3., 3., 4., 4.]]]]) >>> m &#61; nn.Upsample(scale_factor&#61;2, mode&#61;&#39;bilinear&#39;) # align_corners&#61;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]]]]) >>> m &#61; nn.Upsample(scale_factor&#61;2, mode&#61;&#39;bilinear&#39;, align_corners&#61;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]]]]) >>> # Try scaling the same data in a larger tensor >>> >>> input_3x3 &#61; torch.zeros(3, 3).view(1, 1, 3, 3) >>> input_3x3[:, :, :2, :2].copy_(input) tensor([[[[ 1., 2.], [ 3., 4.]]]]) >>> input_3x3 tensor([[[[ 1., 2., 0.], [ 3., 4., 0.], [ 0., 0., 0.]]]]) >>> m &#61; nn.Upsample(scale_factor&#61;2, mode&#61;&#39;bilinear&#39;) # align_corners&#61;False >>> # Notice that values in top left corner are the same with the small input (except at boundary) >>> m(input_3x3) tensor([[[[ 1.0000, 1.2500, 1.7500, 1.5000, 0.5000, 0.0000], [ 1.5000, 1.7500, 2.2500, 1.8750, 0.6250, 0.0000], [ 2.5000, 2.7500, 3.2500, 2.6250, 0.8750, 0.0000], [ 2.2500, 2.4375, 2.8125, 2.2500, 0.7500, 0.0000], [ 0.7500, 0.8125, 0.9375, 0.7500, 0.2500, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]]]]) >>> m &#61; nn.Upsample(scale_factor&#61;2, mode&#61;&#39;bilinear&#39;, align_corners&#61;True) >>> # Notice that values in top left corner are now changed >>> m(input_3x3) tensor([[[[ 1.0000, 1.4000, 1.8000, 1.6000, 0.8000, 0.0000], [ 1.8000, 2.2000, 2.6000, 2.2400, 1.1200, 0.0000], [ 2.6000, 3.0000, 3.4000, 2.8800, 1.4400, 0.0000], [ 2.4000, 2.7200, 3.0400, 2.5600, 1.2800, 0.0000], [ 1.2000, 1.3600, 1.5200, 1.2800, 0.6400, 0.0000], [ 0.0000, 0.0000, 0.0000, 0.0000, 0.0000, 0.0000]]]])

1.3 UpsamplingNearest2d

本质上其实就是对jpg、png等格式图像数据的Upsample(mode&#61;&#39;nearest&#39;)

定义

class torch.nn.UpsamplingNearest2d(size&#61;None, scale_factor&#61;None)

输入输出&#xff1a;

例子

>>> input &#61; torch.arange(1, 5).view(1, 1, 2, 2) >>> input tensor([[[[ 1., 2.], [ 3., 4.]]]]) >>> m &#61; nn.UpsamplingNearest2d(scale_factor&#61;2) >>> m(input) tensor([[[[ 1., 1., 2., 2.], [ 1., 1., 2., 2.], [ 3., 3., 4., 4.], [ 3., 3., 4., 4.]]]])

1.4 UpsamplingBilinear2d

跟1.3类似&#xff0c;本质上其实就是对jpg、png等格式图像数据的Upsample(mode&#61;&#39;bilinear&#39;)

定义

class torch.nn.UpsamplingBilinear2d(size&#61;None, scale_factor&#61;None)

输入输出&#xff1a;

例子

>>> input &#61; torch.arange(1, 5).view(1, 1, 2, 2) >>> input tensor([[[[ 1., 2.], [ 3., 4.]]]]) >>> m &#61; nn.UpsamplingBilinear2d(scale_factor&#61;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]]]])

2. 知识回顾


本段主要转自《一边Upsample一边Convolve&#xff1a;Efficient Sub-pixel-convolutional-layers详解


2.1 Transposed convolution(转置卷积)

下面以一维向量进行卷积为例进行说明&#xff08;stride&#61;2&#xff09;&#xff0c;x为输入y为输出&#xff0c;通过1维卷积核/滤波器f来实现这个过程&#xff0c;x的size为8&#xff0c;f为[1, 2, 3, 4]&#xff0c;y为5&#xff0c;x中灰色的方块表示用0进行padding。在f权重中的灰色方块代表f中某些值与x中的0进行了相乘。下图就是1维卷积的过程&#xff0c;从x到y。

容易地&#xff0c;可以发现1维卷积的方式很直观&#xff0c;那么什么是转置卷积呢&#xff1f;故名思意&#xff0c;就是将卷积倒过来&#xff1a;

如上图所示&#xff0c;1维卷积核/滤波器被转过来了&#xff0c;这里进行一下额外的说明&#xff1a;
假设x &#61; [ x 1 x_1 x1​, x 2 x_2 x2​, …, x 5 x_5 x5​]&#xff0c;y &#61; [ y 1 y_1 y1​, y 2 y_2 y2​, …, y 12 y_{12} y12​]&#xff0c;则最上面的白色块体对应的是 y 3 y_3 y3​。那么&#xff1a;
y 3 y_3 y3​ &#61; 3 x 1 &#43; x 2 3x_1 &#43; x_2 3x1​&#43;x2​

2.2 Sub-pixel convolution

还是以一维卷积为例&#xff0c;输入为x &#61; [ x 1 x_1 x1​, x 2 x_2 x2​, …, x 5 x_5 x5​]&#xff0c;输出为y &#61; [ y 1 y_1 y1​, y 2 y_2 y2​, …, y 12 y_{12} y12​]。sub-pixel convolution&#xff08;stride&#61;1/2&#xff09;如图&#xff1a;

1.1 PixelShuffle中说过&#xff0c;sub-pixel convolution的步长是介于0到1之间的&#xff0c;但是这个操作是如何实现的呢&#xff1f;简而言之&#xff0c;分为两步&#xff1a;

  • ① 将stride设为1
  • ② 将输入数据dilation(以stride&#61;1/2为例&#xff0c;sub-pixel是将输入x的元素之间插入一些元素0&#xff0c;并在前后补上一些元素0)&#xff0c;或者说根据分数索引&#xff08;fractional indices&#xff09;重新创建数据的排列形式。

2.3 Deconvolution

这里以2维卷积来进行演示&#xff0c;输入一个4 x 4的单通道图像&#xff0c;卷积核取1个4 x 4的&#xff0c;假设这里取上采样比例为2&#xff0c;那么我们的目标就是恢复成一个8 x 8的单通道图像。

如上图&#xff0c;我们首先通过fractional indices从原input中创建一个sub-pixel图像&#xff0c;其中白色的像素点就是原input中的像素&#xff08;在LR sapce中&#xff09;&#xff0c;灰色像素点则是通过zero padding而来的。

用一个4 x 4的卷积核来和刚才生成的sub-pixel图像进行stride&#61;1的卷积&#xff0c;首先发现卷积核和sub-pixel图像中非零的像素进行了第一次有效卷积&#xff08;图中紫色像素代表被激活的权重&#xff09;&#xff0c;然后我们将sub-pixels整体向右移动一格&#xff0c;让卷积核再进行一次卷积操作&#xff0c;会发现卷积核中蓝色像素的权重被激活&#xff0c;同理绿色红色&#xff08;注意这里是中间的那个8×8的sub-pixel图像中的白色像素点进行移动&#xff0c;而每次卷积的方式都相同&#xff09;。

最后我们输出得到8 x 8的高分辨率图像&#xff08;HR图像&#xff09;&#xff0c;HR图像和sub-pixel图像的大小是一致的&#xff0c;我们将其涂上颜色&#xff0c;颜色代表卷积核中权重和sub-pixel图像中哪个像素点进行了卷积&#xff08;也就是哪个权重对对应的像素进行了贡献&#xff09;。

Deconvlution的动态过程可见我之前翻译过的一篇文章《CNN概念之上采样&#xff0c;反卷积&#xff0c;Unpooling概念解释》

显然&#xff0c;我们可以看出&#xff0c;紫、蓝、绿、红四部分是相互独立的&#xff0c;那么&#xff0c;可以将这个4 x 4的卷积核分成4个2 x 2的卷积核如下&#xff1a;


注意&#xff0c;这个操作是可逆的。因为每个卷积权重在操作过程中都是独立的。

因此&#xff0c;我们可以直接对原始图像&#xff08;未经过sub-pixel处理&#xff09;直接进行2 x 2的卷积&#xff0c;并对输出进行周期筛选&#xff08;periodic shuffling&#xff09;来得到同样的8 x 8的高分辨率图像。


3. 说明

在新版本PyTorch中&#xff0c;这些插值Vision Layer都不推荐使用了&#xff0c;官方的说法是将其放在了torch.nn.functional.interpolate中&#xff0c;用此方法可以更个性化的定制用户的上采样或者下采样的需求。

4. 参考资料

[1] 一边Upsample一边Convolve&#xff1a;Efficient Sub-pixel-convolutional-layers详解
[2] 双线性插值(Bilinear Interpolation)
[3] torch.nn.functional.interpolate说明
[4] PyTorch 0.4.1——Vision layers


推荐阅读
author-avatar
范大少微博劳
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有