PyTorch學習筆記(10)——上採樣和PixelShuffle

去年曾經使用過FCN(全卷積神經網絡)及其派生Unet,再加上在愛奇藝的時候做過一些超分辨率重建的內容,其中用到了畢業於帝國理工的華人博士Shi Wenzhe(在Twitter任職)發表的PixelShuffleReal-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=1r<1\frac{1}{r} < 1 時,可以讓卷積後的feature map的高和寬變大——即分辨率增大,這個新的操作叫做sub-pixel convolution,具體原理可以看PixelShuffleReal-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)。

但是其實現過程不是直接通過插值等方式產生這個高分辨率圖像,而是通過卷積先得到r2r^2個通道的特徵圖(特徵圖大小和輸入低分辨率圖像一致),然後通過週期篩選(periodic shuffing)的方法得到這個高分辨率的圖像,其中rr上採樣因子(upscaling factor),也就是圖像的擴大倍率。

定義

該類定義如下:

class torch.nn.PixleShuffle(upscale_factor)

這裏的upscale_factor就是放大的倍數,數據類型爲int
以四維輸入(N,C,H,W)爲例,Pixelshuffle會將爲(∗,r2Cr^2C,H,W)的Tensor給reshape成(∗,C,rH,rW)的Tensor。形式化地說,它的輸入輸出的shape如下:

  • 輸入: (N,C x upscale_factor2^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(新版本中推薦使用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]]]])

2. 知識回顧

本段主要轉自《一邊Upsample一邊Convolve:Efficient Sub-pixel-convolutional-layers詳解

2.1 Transposed convolution(轉置卷積)

下面以一維向量進行卷積爲例進行說明(stride=2),x爲輸入y爲輸出,通過1維卷積核/濾波器f來實現這個過程,x的size爲8,f爲[1, 2, 3, 4],y爲5,x中灰色的方塊表示用0進行padding。在f權重中的灰色方塊代表f中某些值與x中的0進行了相乘。下圖就是1維卷積的過程,從x到y。
在這裏插入圖片描述

容易地,可以發現1維卷積的方式很直觀,那麼什麼是轉置卷積呢?故名思意,就是將卷積倒過來
在這裏插入圖片描述
如上圖所示,1維卷積核/濾波器被轉過來了,這裏進行一下額外的說明:
假設x = [x1x_1, x2x_2, …, x5x_5],y = [y1y_1, y2y_2, …, y12y_{12}],則最上面的白色塊體對應的是y3y_3。那麼:
y3y_3 = 3x1+x23x_1 + x_2

2.2 Sub-pixel convolution

還是以一維卷積爲例,輸入爲x = [x1x_1, x2x_2, …, x5x_5],輸出爲y = [y1y_1, y2y_2, …, y12y_{12}]。sub-pixel convolution(stride=1/2)如圖:
在這裏插入圖片描述

1.1 PixelShuffle中說過,sub-pixel convolution的步長是介於0到1之間的,但是這個操作是如何實現的呢?簡而言之,分爲兩步:

  • ① 將stride設爲1
  • ② 將輸入數據dilation(以stride=1/2爲例,sub-pixel是將輸入x的元素之間插入一些元素0,並在前後補上一些元素0),或者說根據分數索引(fractional indices)重新創建數據的排列形式。

2.3 Deconvolution

這裏以2維卷積來進行演示,輸入一個4 x 4的單通道圖像,卷積核取1個4 x 4的,假設這裏取上採樣比例爲2,那麼我們的目標就是恢復成一個8 x 8的單通道圖像。
在這裏插入圖片描述

如上圖,我們首先通過fractional indices從原input中創建一個sub-pixel圖像,其中白色的像素點就是原input中的像素(在LR sapce中),灰色像素點則是通過zero padding而來的。
在這裏插入圖片描述

用一個4 x 4的卷積核來和剛纔生成的sub-pixel圖像進行stride=1的卷積,首先發現卷積核和sub-pixel圖像中非零的像素進行了第一次有效卷積(圖中紫色像素代表被激活的權重),然後我們將sub-pixels整體向右移動一格,讓卷積核再進行一次卷積操作,會發現卷積核中藍色像素的權重被激活,同理綠色紅色(注意這裏是中間的那個8×8的sub-pixel圖像中的白色像素點進行移動,而每次卷積的方式都相同)。

在這裏插入圖片描述

最後我們輸出得到8 x 8的高分辨率圖像(HR圖像),HR圖像和sub-pixel圖像的大小是一致的,我們將其塗上顏色,顏色代表卷積核中權重和sub-pixel圖像中哪個像素點進行了卷積(也就是哪個權重對對應的像素進行了貢獻)。

Deconvlution的動態過程可見我之前翻譯過的一篇文章《CNN概念之上採樣,反捲積,Unpooling概念解釋

顯然,我們可以看出,紫、藍、綠、紅四部分是相互獨立的,那麼,可以將這個4 x 4的卷積核分成4個2 x 2的卷積核如下:

在這裏插入圖片描述
注意,這個操作是可逆的。因爲每個卷積權重在操作過程中都是獨立的。

因此,我們可以直接對原始圖像(未經過sub-pixel處理)直接進行2 x 2的卷積,並對輸出進行週期篩選(periodic shuffling)來得到同樣的8 x 8的高分辨率圖像。

在這裏插入圖片描述

3. 說明

在新版本PyTorch中,這些插值Vision Layer都不推薦使用了,官方的說法是將其放在了torch.nn.functional.interpolate中,用此方法可以更個性化的定製用戶的上採樣或者下采樣的需求。

4. 參考資料

[1] 一邊Upsample一邊Convolve:Efficient Sub-pixel-convolutional-layers詳解
[2] 雙線性插值(Bilinear Interpolation)
[3] torch.nn.functional.interpolate說明
[4] PyTorch 0.4.1——Vision layers

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章