PyTorch学习笔记 ——上采样和PixelShuffle

去年曾经使用过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

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

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

torch

1. Vision Layer

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

① PixelShuffle
② Upsample
③ UpsamplingNearest2d
④ UpsamplingBilinear2d

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

1.1 PixelShuffle

正常情况下,卷积操作会使feature map的高和宽变小。
但当我们的 s t r i d e = 1 r ​ < 1 stride=\frac{1}{r} ​<1 stride=r1<1 时,可以让卷积后的feature map的高和宽变大——即分辨率增大,这个新的操作叫做sub-pixel convolution,具体原理可以看PixelShuffle《Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network 》的论文。

在这里插入图片描述

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

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

定义

该类定义如下:

class torch.nn.PixleShuffle(upscale_factor)

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

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

例子

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

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

1.2 Upsample

1.2 Upsample(新版本中推荐使用torch.nn.functional.interpolate)

对给定多通道的1维(temporal)、2维(spatial)、3维(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

此算法支持最近邻,线性插值,双线性插值,三次线性插值对3维、4维、5维的输入Tensor分别进行上采样(Upsample)。
定义

该类定义如下:

class torch.nn.Upsample(size=None, scale_factor=None, mode='nearest', align_corners=None)

其中:

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

例子

>>> input = torch.arange(1, 5).view(1, 1, 2, 2).float()
>>> input
tensor([[[[ 1.,  2.],
          [ 3.,  4.]]]])

>>> 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.]]]])

>>> 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]]]])

>>> 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]]]])

>>> # Try scaling the same data in a larger tensor
>>>
>>> input_3x3 = 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 = nn.Upsample(scale_factor=2, mode='bilinear')  # align_corners=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 = nn.Upsample(scale_factor=2, mode='bilinear', align_corners=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=‘nearest’)。
定义

 class torch.nn.UpsamplingNearest2d(size=None, scale_factor=None)

在这里插入图片描述
例子

>>> input = torch.arange(1, 5).view(1, 1, 2, 2)
>>> input
tensor([[[[ 1.,  2.],
          [ 3.,  4.]]]])

>>> m = nn.UpsamplingNearest2d(scale_factor=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类似,本质上其实就是对jpg、png等格式图像数据的Upsample(mode=‘bilinear’)。
定义

class torch.nn.UpsamplingBilinear2d(size=None, scale_factor=None)
在这里插入图片描述

>>> input = torch.arange(1, 5).view(1, 1, 2, 2)
>>> input
tensor([[[[ 1.,  2.],
          [ 3.,  4.]]]])

>>> 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]]]])

1.5 Sub-piexl convolution

这篇文章介Is the deconvolution layer the same as a convolutional layer论文中提出的一种结合上升采样upsample和卷积操作的的一种方法,称之为Sub-piexl convolution。

这种方法作用于低分率图像得然后通过其操作得到高分辨率图像,其实说白了就是上升采样,我们之前做过的maxpooling、average-pooling和transposed-convolution都有类似的效果,也就是将一个尺寸较小的图像通过算法使之变为尺寸较大的图像,如下图。

在这里插入图片描述

相比于其他的上升采样操作,本文介绍的这个算法在相同运算速度下,不仅具有比maxpooling和averagepooling有对位置的记忆功能,而且相比Transposed convolution具有精度更高的优点。

思想描述

文章将这个算法描述为:LR network,即低分辨卷积网络。

文章拿来与之作对比的是HR network,高分辨卷积网络,一般HR network是现将低分辨力的图像进行二次插值变换后然后对变换后的图像再进行卷积网络。像HR network是先将图像进行upsample后才进行卷积,而文中的这个算法操作则是在upsample的过程中对图像就进行了卷积

在这里插入图片描述
这个算法的实现流程如上图,举个例子,实现的功能就是将一个1×1的image通过Sub-pixel操作将其变为 r 2 r^2 r2的高分辨率图像,但是这个实现过程不是直接产生这个高分辨率图像而是先得到 r 2 r^2 r2个通道特征图然后通过周期筛选(periodic shuffing)得到这个高分辨率的图像,其中r为upscaling factor,也就是图像扩大倍率。

知识铺垫

在说明这个具体的算法流程之前,我们先对几个知识回顾一下:

Transposed convolution and sub-pixel convolution layers(转置卷积与子像素卷积层)

在这里插入图片描述

上图是一个简单的1D的卷积网络,x为输入y为输出,通过滤波器f来实现这个过程,x的size为8,f为4,y为5,x中灰色的方块表示用0进行padding。在f权重中的灰色方块代表f中某些值与x中的0进行了相乘。这个图就是1D卷积的过程,从x到y。

在这里插入图片描述

上图左面是一个转置卷积(stride为2),右面是sub-pixel convolution(stride为1/2),作用在1D上。

通过观察可以很容易的看到左边x为5,y为8,f为4,y中灰色部分代表被截去的部分,而右边通过对x进行部分 取值达到 过滤效果。通过比较两个卷积方法可以发现:如果把右边的fliter进行reverse(倒转),会发现两边得到的y值相同(自己可以试着加一下)。也就是说在参数(filter中权重值)可以学习的情况下,右边的操作和左边的操作完全相同。

Deconvolution layer vs Convolution in LR

接下来我们展示的是:在LR space中用一个简单的卷积参数为 ( o ∗ r ∗ r , i , k , k ) (o* r* r, i,k,k) (orr,i,k,k)-(输出通道,输入通道,kernel width,kernel height)的卷积操作。

和deconvolution,其中卷积参数为( ( o , i , k ∗ r , k ∗ r ) , k 为 正 数 (o, i ,k * r,k * r),k为正数 (o,i,kr,kr)k)的作用是相同的。

我们用2D来进行演示,input为(1,4,4),kernel为(1,1,4,4),假设我们upsample的比率为2,我们要得到(1,8,8)的输出。

在这里插入图片描述
如上图,我们首先通过fractional indices从原input中创建一个sub-pixel图像,其中白色的像素就是原input中的像素(在LR sapce中),灰色是通过zero padding而来的。

在这里插入图片描述
用一个(1,1,4,4)的卷积核来和刚才生成的sub-pixel图像进行卷积,首先发现卷积核和sub-pixel图像中非零的像素进行了第一次有效卷积(图中紫色像素代表被激活的权重),然后我们将sub-pixels整体向右移动一格,让卷积核在进行一次卷积,会发现卷积核中蓝色的像素权重被激活,同理绿色和红色(注意这里是中间的那个8×8的sub-pixel图像中的白色像素点进行移动,而每次卷积的方式都相同)。

在这里插入图片描述
最后我们输出得到HR图像,HR图像和sub-pixel图像的大小是一致的,我们将其涂上颜色,颜色代表这个卷积核中权重和sub-pixel图像中哪个像素点进行了卷积(也就是哪个权重对对应的像素进行了贡献)。

在这里插入图片描述
需要注意的是,这是一个(1,1,4,4)的卷积核,上面每个权重皆为独立地被激活,也就是说我们可以轻易地将其分成(4,1,2,2)的卷积核(如上图)。这个操作是可逆的因为每个卷及权重在操作过程中都是独立的

在这里插入图片描述

这样,这里不适用(1,1,4,4)的卷积核,而是使用(4,1,2,2)的卷积核,,对sub-pixel图像直接在LR space中进行卷及操作(如上图)。再将得到的(4,4,4)的输出进行周期筛选(periodic shuffling)将其reshape为HR image。

在这里插入图片描述

这个图片展示了将左面图像上方的(9,32,3,3)卷积层转化为底部的(1,32,9,9)卷积层,转化过程正如右方所示。也就是说,上方的卷积层对一个32通道的图像进行卷积然后周期筛选后得到的图像和使用下方的卷积层进行反卷积(deconvolution)得到的结果相同。

这个卷积操作是很灵活的,看一下之前的1D的卷积操作,可以将滤波器f=(1,2,3,4)更换为f1=(2,4)以及f2=(1,3),然后产生y1 = f1 * x和y2 = f2 * 2其中代表卷积操作。通过对y1和y2进行组合得到最终的结果y。还记得上面谈说过:
“在LR space中用一个简单的卷积参数为(o r* r,i,k,k)-(输出通道,输入通道,kernel width,kernel height)的卷积操作,和deconvolution,其中卷积参数为((o,i,k * r,k * r),k为正数)的作用是相同的。”
这里的f我们一般都以(k* r)来表示,但其实这个f可以为任意值,加入f=(1,2,3),我们就可以分成f=(2),f=(1,3),同样,得到结果组合起来即可。

核心思想以及结论

在这里插入图片描述现在回到最开始说的那个操作,上图是这个算法的整个过程。通过上面的一系列论述,我们可以得到deconvolition layer的操作和在LR中输出 r d r^d rd的convolution操作得到的结果是一样的(d代表维度)。也就是说,得到 r 2 r^2 r2个通道的这个卷积过程和在它之前得到 r 2 r^2 r2个特征图的操作是一样的。

在上面的Hidden convolutional layers中,看倒数第二个layer,有n个feature maps。我们现在可以知道,在LR space中,通过一个放大倍率为2的upsampling去学习表示n feature maps和在HR space中,通过一个n/4个feature maps去表示的效果是一样的。想象在相同运行速度下,一个n=32(LR space),一个n/4=8个feature maps(HR space),在LR 下的network比在HR network下的表示能力是很强大的。

举个实际的例子,LR network(32 x 32 x 3 x 3 x W/2 x H/2)的复杂度和HR中的(8 x 8 x 6 x 6 x W x H)的复杂度是一样的(W和H代表image的宽和高)。特征图中存留的信息也是一样的:LR(l x 32 x w/2 x h/2)和HR(l x 8 x w x h),其中l代表layer的数量。接受域来说,在原始input LR space中都是一样的,但是LR network的参数(l x 32 x 32 x 3 x 3)比HR network(l x 8 x 8 x 6 x 6)的多,也就是说,LR中,network的卷积比HR中先对input进行upsample的卷积有很强的表示学习能力。

所以有时候先对原始input进行upsample然后再进行卷积并不好,至少在表示学习能力并没有sub-pixel convolution效果好。

说明

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

参考资料:

https://arxiv.org/pdf/1603.07285v1.pdf
https://arxiv.org/abs/1609.07009
https://oldpan.me/archives/upsample-convolve-efficient-sub-pixel-convolutional-layers

### Pixel Shuffle in Deep Learning Image Super-Resolution Implementation and Explanation In the context of deep learning-based image super-resolution (SR), pixel shuffle plays a crucial role as an up-sampling method that efficiently increases spatial resolution while maintaining computational efficiency[^1]. The core idea behind pixel shuffle is to rearrange elements from lower-dimensional feature maps into higher-dimensional ones without introducing additional parameters. The operation can be mathematically described by transforming a tensor with shape \([C, H, W]\) where \(C\) represents channels, and \(H\),\(W\) represent height and width respectively; this transformation results in another tensor having dimensions \([\frac{C}{r^{2}}, rH, rW]\). Here, \(r\) denotes scaling factor which determines how much larger we want our output size compared to input size. Below demonstrates Python code implementing PyTorch's `pixel_shuffle` function: ```python import torch.nn as nn class SubPixelConvolution(nn.Module): def __init__(self, num_channels, upscale_factor=2): super(SubPixelConvolution, self).__init__() self.conv = nn.Conv2d(num_channels, num_channels * (upscale_factor ** 2), kernel_size=3, stride=1, padding=1) self.shuffle = nn.PixelShuffle(upscale_factor) def forward(self, x): out = self.conv(x) out = self.shuffle(out) return out ``` This module first applies convolutional layers followed by applying pixel shuffling through `nn.PixelShuffle`. By doing so, it effectively expands low-resolution images into high-resolution counterparts during inference time. Compared to other upsampling techniques like nearest neighbor or bilinear interpolation, pixel shuffle offers better performance because it learns optimal mappings between pixels directly via training data rather than relying on fixed rules. Moreover, since no extra learnable weights are involved after convolutions, memory usage remains minimal throughout processing stages. --related questions-- 1. How does sub-pixel convolution compare against traditional bicubic interpolation methods? 2. Can you explain why using transposed convolutions might lead to checkerboard artifacts when performing SR tasks? 3. What modifications could enhance the effectiveness of pixel shuffle within GAN architectures for generating realistic textures at finer scales?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值