去年曾經使用過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= 時,可以讓卷積後的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)。
但是其實現過程不是直接通過插值等方式產生這個高分辨率圖像,而是通過卷積先得到個通道的特徵圖(特徵圖大小和輸入低分辨率圖像一致),然後通過週期篩選(periodic shuffing)的方法得到這個高分辨率的圖像,其中爲上採樣因子(upscaling factor),也就是圖像的擴大倍率。
定義
該類定義如下:
class torch.nn.PixleShuffle(upscale_factor)
這裏的upscale_factor就是放大的倍數,數據類型爲int
。
以四維輸入(N,C,H,W)爲例,Pixelshuffle會將爲(∗,,H,W)的Tensor給reshape成(∗,C,rH,rW)的Tensor。形式化地說,它的輸入輸出的shape如下:
- 輸入: (N,C x upscale_factor,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 = [, , …, ],y = [, , …, ],則最上面的白色塊體對應的是。那麼:
=
2.2 Sub-pixel convolution
還是以一維卷積爲例,輸入爲x = [, , …, ],輸出爲y = [, , …, ]。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