上采樣和PixelShuffle(轉)


有些地方還沒看懂, mark一下
文章來源: https://blog.csdn.net/g11d111/article/details/82855946

去年曾經使用過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= 1 r < 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)。

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

定義

該類定義如下:

class torch.nn.PixleShuffle(upscale_factor) 
   
   
   
           
  • 1
  • 1

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

  • 輸入: (N,C x upscale_factor 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
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

怎么樣,是不是看起來挺簡單的?我將在最后完整的介紹一下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) 
   
   
   
           
  • 1
  • 1

其中:

  • 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

1.3 UpsamplingNearest2d

本質上其實就是對jpg、png等格式圖像數據的Upsample(mode='nearest')

定義

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

輸入輸出:
在這里插入圖片描述

例子

>>> 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

1.4 UpsamplingBilinear2d

跟1.3類似,本質上其實就是對jpg、png等格式圖像數據的Upsample(mode='bilinear')

定義

 class torch.nn.UpsamplingBilinear2d(size=None, scale_factor=None) 
   
   
   
           
  • 1
  • 1

輸入輸出:
在這里插入圖片描述

例子

>>> 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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

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 = [ x 1 x_1 , x 2 x_2 , …, x 5 x_5 ],y = [ y 1 y_1 , y 2 y_2 , …, y 12 y_{12} ],則最上面的白色塊體對應的是 y 3 y_3 。那么:
y 3 y_3 = 3 x 1 + x 2 3x_1 + x_2

2.2 Sub-pixel convolution

還是以一維卷積為例,輸入為x = [ x 1 x_1 , x 2 x_2 , …, x 5 x_5 ],輸出為y = [ y 1 y_1 , y 2 y_2 , …, y 12 y_{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

        </div>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM