我們可以通過卷積和池化等技術可以將圖像進行降維,因此,一些研究人員也想辦法恢復原分辨率大小的圖像,特別是在語義分割領域應用很成熟。通過對一些資料的學習,簡單的整理下三種恢復方法,並進行對比。
1、上采樣(Upsampling)[沒有學習過程]
在FCN、U-net等網絡結構中,涉及到了上采樣。上采樣概念:上采樣指的是任何可以讓圖像變成更高分辨率的技術。最簡單的方式是重采樣和插值:將輸入圖片進行rescale到一個想要的尺寸,而且計算每個點的像素點,使用如雙線性插值bilinear等插值方法對其余點進行插值來完成上采樣過程。

pytorch torch.nn 實現上采樣
在PyTorch中,上采樣的層被封裝在torch.nn
中的Vision Layers
里面,一共有4種:
- ① PixelShuffle
- ② Upsample
- ③ UpsamplingNearest2d
- ④ UpsamplingBilinear2d
0)PixelShuffle
感性認識
但當我們的stride = (1/r) < 1時,可以讓卷積后的feature map變大——即分辨率變大,這個新的操作叫做sub-pixel convolution,具體原理可以看PixelShuffle:Real-Time Single Image and Video Super-Resolution Using an Efficient Sub-Pixel Convolutional Neural Network這篇paper。
pixelshuffle算法的實現流程如上圖,其實現的功能是:將一個H × W的低分辨率輸入圖像(Low Resolution),通過Sub-pixel操作將其變為rH x rW的高分辨率圖像(High Resolution)。
但是其實現過程不是直接通過插值等方式產生這個高分辨率圖像,而是通過卷積先得到r2個通道的特征圖(特征圖大小和輸入低分辨率圖像一致),然后通過周期篩選(periodic shuffing)的方法得到這個高分辨率的圖像,其中r為上采樣因子(upscaling factor),也就是圖像的擴大倍率。
定義
該類定義如下:
class torch.nn.PixleShuffle(upscale_factor)
這里的upscale_factor
就是放大的倍數,數據類型為int
。
輸入輸出的shape
具體一點來說,Pixelshuffle
會將shape為(∗, r2C , H , W ) 的Tensor
給reshape成( ∗ , C , rH , rW ) 的Tensor
。形式化地說,它的輸入輸出的shape如下:
其中N代表batch size。
例子
下面舉個例子
ps = nn.PixelShuffle(3)# 縮放到三倍,r == 3 input = torch.tensor(1, 9, 4, 4)## r^2 C == 9,所以C == 1 output = ps(input) print(output.size()) # 輸出為: # torch.Size([1, 1, 12, 12])
1)Upsample(新版本中推薦使用torch.nn.functional.interpolate
)
CLASS torch.nn.Upsample(size=None, scale_factor=None, mode='nearest', align_corners=None)
上采樣一個給定的多通道的 1D (temporal,如向量數據), 2D (spatial,如jpg、png等圖像數據) or 3D (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
此算法支持最近鄰'nearest',線性插值'linear',雙線性插值'bilinear',雙三次'bicubic'和三線性插值'trilinear'對3維、4維、5維的輸入Tensor分別進行上采樣(Upsample)。
你可以給定scale_factor來指定輸出為輸入的scale_factor倍或直接使用參數size指定目標輸出的大小(但是不能同時制定兩個)
參數:
-
size (int or Tuple[int] or Tuple[int, int] or Tuple[int, int, int], optional) – 根據不同的輸入類型制定的輸出大小
-
scale_factor (float or Tuple[float] or Tuple[float, float] or Tuple[float, float, float], optional) – 指定輸出為輸入的多少倍數。如果輸入為tuple,其也要制定為tuple類型
-
mode (str, optional) – 可使用的上采樣算法,有
'nearest'
,'linear'
,'bilinear'
,'bicubic'
and'trilinear'
.默認使用
'nearest'
-
align_corners (bool, optional) – 如果為True,輸入的角像素將與輸出張量對齊,因此將保存下來這些像素的值。僅當使用的算法為
'linear'
,'bilinear'
or'trilinear'時可以使用。
默認設置為
False
輸入輸出形狀:
注意:
當align_corners = True時,線性插值模式(線性、雙線性、雙三線性和三線性)不按比例對齊輸出和輸入像素,因此輸出值可以依賴於輸入的大小。這是0.3.1版本之前這些模式的默認行為。從那時起,默認行為是align_corners = False,如下圖:
上面的圖是source pixel為4*4上采樣為target pixel為8*8的兩種情況,這就是對齊和不對齊的差別,會對齊左上角元素,即設置為align_corners = True時輸入的左上角元素是一定等於輸出的左上角元素。但是有時align_corners = False時左上角元素也會相等,官網上給的例子就不太能說明兩者的不同(也沒有試出不同的例子,大家理解這個概念就行了)
如果您想下采樣/常規調整大小,您應該使用interpolate()方法,這里的上采樣方法已經不推薦使用了。
舉例:
import torch from torch import nn input = torch.arange(1, 5, dtype=torch.float32).view(1, 1, 2, 2) input
返回:
tensor([[[[1., 2.], [3., 4.]]]])
'nearest'
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.]]]])
'bilinear' align_corners=False
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]]]])
'bilinear' align_corners=True
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]]]])
m = nn.Upsample(size=(3,5), mode='bilinear',align_corners=True) m(input)
返回:
tensor([[[[1.0000, 1.2500, 1.5000, 1.7500, 2.0000], [2.0000, 2.2500, 2.5000, 2.7500, 3.0000], [3.0000, 3.2500, 3.5000, 3.7500, 4.0000]]]])
如果你使用的數據都是JPG等圖像數據,那么你就能夠直接使用下面的用於2D數據的方法:
2)UpsamplingNearest2d
本質上其實就是對jpg、png等格式圖像數據的Upsample(mode='nearest')
CLASS torch.nn.UpsamplingNearest2d(size=None, scale_factor=None)
形狀:
舉例:
m = nn.UpsamplingNearest2d(scale_factor=2) m(input)
input即上面例子的input,返回:
tensor([[[[1., 1., 2., 2.], [1., 1., 2., 2.], [3., 3., 4., 4.], [3., 3., 4., 4.]]]])
m = nn.UpsamplingNearest2d(size=(3,5)) m(input)
返回:
tensor([[[[1., 1., 1., 2., 2.], [1., 1., 1., 2., 2.], [3., 3., 3., 4., 4.]]]])
3)UpsamplingBilinear2d
本質上其實就是對jpg、png等格式圖像數據的Upsample(mode='bilinear')
CLASS torch.nn.UpsamplingBilinear2d(size=None, scale_factor=None)
專門用於2D數據的雙線性插值算法,參數等跟上面的差不多,省略
形狀:
注意:最好還是使用nn.functional.interpolate(..., mode='bilinear', align_corners=True)
舉例:
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]]]])
m = nn.UpsamplingBilinear2d(size=(3,5)) m(input)
返回:
tensor([[[[1.0000, 1.2500, 1.5000, 1.7500, 2.0000], [2.0000, 2.2500, 2.5000, 2.7500, 3.0000], [3.0000, 3.2500, 3.5000, 3.7500, 4.0000]]]])
2、上池化(Unpooling)[沒有學習過程]
Unpooling是在CNN中常用的來表示max pooling的逆操作。這是從2013年紐約大學Matthew D. Zeiler和Rob Fergus發表的《Visualizing and Understanding Convolutional Networks》中引用的:因為max pooling不可逆,因此使用近似的方式來反轉得到max pooling操作之前的原始情況:


對比上面兩個示意圖,可以發現區別:
- 兩者的區別在於UnSampling階段沒有使用MaxPooling時的位置信息,而是直接將內容復制來擴充Feature Map。第一幅圖中右邊4*4矩陣,用了四種顏色的正方形框分割為四個區域,每一個區域內的內容是直接復制上采樣前的對應信息。
- UnPooling的過程,特點是在Maxpooling的時候保留最大值的位置信息,之后在unPooling階段使用該信息擴充Feature Map,除最大值位置以外,其余補0。從圖中即可看到兩者結果的不同。
pytorch實現上采樣,參考代碼:segnet_pytorch:
# Stage 5 x51 = F.relu(self.bn51(self.conv51(x4p))) x52 = F.relu(self.bn52(self.conv52(x51))) x53 = F.relu(self.bn53(self.conv53(x52))) #這個id5記錄的是池化操作時最大值的index,其要設置參數return_indices為True x5p, id5 = F.max_pool2d(x53,kernel_size=2, stride=2,return_indices=True) # Stage 5d #這個是進行最大值上采樣的函數,其是根據id5來把值放到什么位置,其它位置沒有值的地方 #補0 x5d = F.max_unpool2d(x5p, id5, kernel_size=2, stride=2) x53d = F.relu(self.bn53d(self.conv53d(x5d))) x52d = F.relu(self.bn52d(self.conv52d(x53d))) x51d = F.relu(self.bn51d(self.conv51d(x52d)))
測試:
#測試上采樣 m=nn.MaxPool2d((3,3),stride=(1,1),return_indices=True) upm=nn.MaxUnpool2d((3,3),stride=(1,1)) data4=torch.randn(1,1,3,3) output5,indices=m(data4) output6=upm(output5,indices) print('\ndata4:',data4, '\nmaxPool2d',output5, '\nindices:',indices, '\noutput6:',output6)
其輸出為:
data4: tensor([[[[ 2.3151, -1.0391, 0.1074], [ 1.9360, 0.2524, 2.3735], [-0.1151, 0.4684, -1.8800]]]]) maxPool2d tensor([[[[2.3735]]]]) indices: tensor([[[[5]]]]) output6: tensor([[[[0.0000, 0.0000, 0.0000], [0.0000, 0.0000, 2.3735], [0.0000, 0.0000, 0.0000]]]])
3、反卷積(Deconvolution)或稱為fractionally-strided convolutions[具有學習過程]
在介紹反卷積之前,我們需要深入了解一下卷積,一個簡單的卷積層運算,卷積參數為示意圖如下:
對於上述卷積運算,我們把上圖所示的3×3卷積核展成一個如下所示的[4,16]的稀疏矩陣C,如下:
我們再把4×4的輸入特征展成[16,1]的矩陣 ,那么
則是一個[4,1]的輸出特征矩陣,把它重新排列2×2的輸出特征就得到最終的結果,從上述分析可以看出卷積層的計算其實是可以轉化成矩陣相乘的。
ps. 值得注意的是,在一些深度學習網絡的開源框架中並不是通過這種這個轉換方法來計算卷積的,因為這個轉換會存在很多無用的0乘操作。
通過上述的分析,我們已經知道卷積層的前向操作可以表示為和矩陣相乘,很容易得到卷積層的反向傳播就是和
的轉置相乘。
我們已經說過反卷積又被稱為Transposed(轉置) Convolution,我們可以看出其實卷積層的前向傳播過程就是反卷積層的反向傳播過程,卷積層的反向傳播過程就是反卷積層的前向傳播過程。因為卷積層的前向反向計算分別為乘 C和 ,而反卷積層的前向反向計算分別為乘
和
,所以它們的前向傳播和反向傳播剛好交換過來。
下圖表示一個和上圖卷積計算對應的反卷積操作,其中他們的輸入輸出關系正好相反。如果不考慮通道以卷積運算的反向運算來計算反卷積運算的話,還可以通過離散卷積的方法來求反卷積。
當給一個特征圖a, 以及給定的卷積核設置,我們分為三步進行逆卷積操作:
第一步:對輸入的特征圖a進行一些變換,得到新的特征圖a’
第二步:求新的卷積核設置,得到新的卷積核設置,后面都會用右上角加撇點的方式區分
第三步:用新的卷積核在新的特征圖上做常規的卷積,得到的結果就是逆卷積的結果,就是我們要求的結果。
pytorch中的實現:
torch.nn.
ConvTranspose2d
torch.nn.
ConvTranspose2d
(in_channels, out_channels, kernel_size, stride=1, padding=0, output_padding=0, groups=1, bias=True, dilation=1, padding_mode='zeros')
The parameters kernel_size
, stride
, padding
, output_padding
can either be:
-
a single
int
– in which case the same value is used for the height and width dimensions 高寬兩個方向參數相同 -
a
tuple
of two ints – in which case, the first int is used for the height dimension, and the second int for the width dimension分別為高寬兩個方向指定參數
Shape:
- Input: (N,Cin,Hin,Win)
- Output: (N,Cout,Hout,Wout) where
Hout=(Hin−1)×stride[0]−2×padding[0]+dilation[0]×(kernel_size[0]−1)+output_padding[0]+1
Hout=(Hin−1)×stride[0]−2×padding[0]+dilation[0]×(kernel_size[0]−1)+output_padding[0]+1
參數的含義如下:
- in_channels(int) – 輸入信號的通道數
- out_channels(int) – 卷積產生的通道數
- kerner_size(int or tuple) - 卷積核的大小
- stride(int or tuple,optional) - 卷積步長,即要將輸入擴大的倍數。
- padding(int or tuple, optional) - 輸入的每一條邊補充0的層數,高寬都增加2*padding
- output_padding(int or tuple, optional) - 輸出邊補充0的層數,高寬都增加padding
- groups(int, optional) – 從輸入通道到輸出通道的阻塞連接數
- bias(bool, optional) - 如果bias=True,添加偏置
- dilation(int or tuple, optional) – 卷積核元素之間的間距
對於每一條邊輸入輸出的尺寸的公式如下:
4、一些反卷積的論文截圖
上圖為反卷積和全卷積網絡為核心的語義分割網絡。
圖(a)是輸入層;圖b、d、f、h、j是不同featrue map大小的反卷積的結果;圖c、e、g、i是不同featrue map大小的UnPooling結果。