深度學習的許多應用中需要將提取的特征還原到原圖像大小,如圖像的語義分割、生成模型中的圖像生成任務等。通過卷積和池化等技術可以將圖像進行降維,因此,一些研究人員也想辦法恢復原分辨率大小的圖像,特別是在語義分割領域應用很成熟。
常見的上采樣方法有雙線性插值、轉置卷積、上采樣(unsampling)、上池化(unpooling)和亞像素卷積(sub-pixel convolution,PixelShuffle)。下面對其進行簡單介紹。
一、插值
插值常用的方式有nearest interpolation、bilinear interpolation、bicubic interpolation。
1、nearest interpolation
將離待插值最近的已知值賦值給待插值。
2、bilinear interpolation
根據離待插值最近的 個已知值來計算待插值,每個已知值的權重由距離待插值距離決定,距離越近權重越大。
雙線性插值,又稱為雙線性內插。在數學上,雙線性插值是對線性插值在二維直角網格上的擴展,用於對雙變量函數(例如 x 和 y)進行插值。其核心思想是在兩個方向分別進行一次線性插值。

然后在 y 方向進行線性插值,得到 f(x, y):
在FCN中上采樣用的就是雙線性插值。
Fully Convolutional Networks for Semantic Segmentation
3、bicubic interpolation
根據離待插值最近的 個已知值來計算待插值,每個已知值的權重由距離待插值距離決定,距離越近權重越大。
4、各種插值方式的區別與聯系
從nearest interpolation、bilinear interpolation到bicubic interpolation,插值所利用的信息越來越多,feature map越來越平滑,但是同時計算量也越來越大,nearest interpolation、bilinear interpolation、bicubic interpolation的區別與聯系可見下圖示意,其中黑色的點為預測值,其他彩色點為周圍已知值,用來計算預測值。
以上的這些方法都是一些插值方法(上采樣(up-sampling)操作),需要我們在決定網絡結構的時候進行挑選。這些方法就像是人工特征工程一樣,並沒有給神經網絡學習的余地,神經網絡不能自己學習如何更好地進行插值,這個顯然是不夠理想的。
二、上采樣(unsampling)
在FCN、U-net等網絡結構中,涉及到了上采樣。上采樣概念:上采樣指的是任何可以讓圖像變成更高分辨率的技術。最簡單的方式是重復采樣和插值(上面一中的插值屬於上采樣的一種?):將輸入圖片進行rescale到一個想要的尺寸,而且計算每個點的像素點,使用如雙線性插值等插值方法對其余點進行插值來完成上采樣過程。
其中右側為unsampling,可以看出unsampling就是將輸入feature map中的某個值映射填充到輸出上采樣的feature map的某片對應區域中,直接復制或插值。
三、上池化(unpooling)
Unpooling是在CNN中常用的來表示max pooling的逆操作。這是論文《Visualizing and Understanding Convolutional Networks》中產生的思想,下圖示意:
對比上面兩個示意圖,可以發現區別:
- 兩者的區別在於UnSampling階段沒有使用MaxPooling時的位置信息,而是直接將內容復制來擴充Feature Map。第一幅圖中右邊4*4矩陣,用了四種顏色的正方形框分割為四個區域,每一個區域內的內容是直接復制上采樣前的對應信息。
- UnPooling的過程,特點是在Maxpooling的時候保留最大值的位置信息,之后在unPooling階段使用該信息擴充Feature Map,除最大值位置以外,其余補0。從圖中即可看到兩者結果的不同。
《Visualizing and Understanding Convolutional Networks》:https://arxiv.org/abs/1311.2901
四、轉置卷積/反卷積/逆卷積/分數步長卷積
自從步入深度學習時代,我們越來越追求end2end,那么升采樣能不能不用人為定義的權重,而讓模型自己學習呢?答案是顯然的,deconv就是解決方案之一。在上面的雙線性插值方法中不需要學習任何參數。而轉置卷積就像卷積一樣需要學習參數,關於轉置卷積的具體計算可以參見 一文搞懂反卷積,轉置卷積。
卷積與轉置卷積的區別可將卷積表示為矩陣相乘演示:
1、stride=1
等價於stride=1的conv,只是padding方式不同,不能起到升采樣的作用。以一維的數據為例,示意圖如下,中間步驟是將卷積轉換為矩陣乘法的過程。
2、stride > 1
能起到升采樣的作用,一般用到的deconv,stride都大於1。以一維的數據為例,示意圖如下,中間步驟是將卷積轉換為矩陣乘法的過程。
deconvolution也叫transposed convolution,upconvolution等等。其中deconvolution這個名字有點歧義性,容易帶來困惑,transposed convolution比較容易理解。容易驗證1和2中convolution和deconvolution中的權重矩陣互為轉置。
需要注意的是:這里的轉置卷積矩陣的參數,不一定從原始的卷積矩陣中簡單轉置得到的,轉置這個操作只是提供了轉置卷積矩陣的形狀而已。
注意:轉置卷積會導致生成圖像中出現棋盤效應(checkerboard artifacts),這篇文章《Deconvolution and Checkerboard Artifacts》推薦了一種上采樣的操作(也就是插值操作)稱為 resize-convolution,這個操作接在一個卷積操作后面以減少這種現象。如果你的主要目的是生成盡可能少棋盤效應的圖像,那么這篇文章就值得你去閱讀。
計算公式
轉置卷積計算過程是將輸入的每個元素值作為卷積核的權重,相乘后作為該元素對應的上采樣輸出,不同輸入的重疊的輸出部分直接相加作為輸出.示意圖如下:
2-D轉置卷積操作:在輸入的相鄰像素間填充stride-1
個0,再在邊緣填充kernel_size - 1 - crop
個 zero-padding,再進行卷積運算。最后一步還要進行裁剪。
轉置卷積的運算示意圖如下:
為什么叫轉置卷積?
轉置卷積也被稱為分數步長卷積(Fractionally Strided Convolution),如果前向卷積中步長s>1,那么轉置卷積中步長s′<1,但是小於1的步長不能夠直接實現,
可以從另外的方式實現:在輸入特征單元之間插入 s−1 個0,此時步長 s′ 設置為1,不再是小數。
輸入輸出尺度關系:

轉置卷積的用途
- CNN可視化:通過轉置卷積將feature map還原到像素空間,以觀察特定的feature map對哪些pattern的圖片敏感。
- 上采樣:在圖像語義分割或生成對抗網絡中需要像素級別的預測,需要較高的圖像尺寸。
轉置卷積的實現
這篇文章采用python給出了轉置卷積的一種實現
可以看到實際上,反卷積和卷積基本一致,差別在於,反卷積需要填充過程,並在最后一步需要裁剪。具體實現代碼如下:
#根據輸入map([h,w])和卷積核([k,k]),計算卷積后的feature map import numpy as np def compute_conv(fm,kernel): [h,w]=fm.shape [k,_]=kernel.shape r=int(k/2) #定義邊界填充0后的map padding_fm=np.zeros([h+2,w+2],np.float32) #保存計算結果 rs=np.zeros([h,w],np.float32) #將輸入在指定該區域賦值,即除了4個邊界后,剩下的區域 padding_fm[1:h+1,1:w+1]=fm #對每個點為中心的區域遍歷 for i in range(1,h+1): for j in range(1,w+1): #取出當前點為中心的k*k區域 roi=padding_fm[i-r:i+r+1,j-r:j+r+1] #計算當前點的卷積,對k*k個點點乘后求和 rs[i-1][j-1]=np.sum(roi*kernel) return rs #填充0 def fill_zeros(input): [c,h,w]=input.shape rs=np.zeros([c,h*2+1,w*2+1],np.float32) for i in range(c): for j in range(h): for k in range(w): rs[i,2*j+1,2*k+1]=input[i,j,k] return rs def my_deconv(input,weights): #weights shape=[out_c,in_c,h,w] [out_c,in_c,h,w]=weights.shape out_h=h*2 out_w=w*2 rs=[] for i in range(out_c): w=weights[i] tmp=np.zeros([out_h,out_w],np.float32) for j in range(in_c): conv=compute_conv(input[j],w[j]) #注意裁剪,最后一行和最后一列去掉 tmp=tmp+conv[0:out_h,0:out_w] rs.append(tmp) return rs def main(): input=np.asarray(input_data,np.float32) input= fill_zeros(input) weights=np.asarray(weights_data,np.float32) deconv=my_deconv(input,weights) print(np.asarray(deconv)) if __name__=='__main__': main()
為了驗證實現的代碼的正確性,我們使用tensorflow的conv2d_transpose函數執行相同的輸入和卷積核,看看結果是否一致。驗證代碼如下:
import tensorflow as tf import numpy as np def tf_conv2d_transpose(input,weights): #input_shape=[n,height,width,channel] input_shape = input.get_shape().as_list() #weights shape=[height,width,out_c,in_c] weights_shape=weights.get_shape().as_list() output_shape=[input_shape[0], input_shape[1]*2 , input_shape[2]*2 , weights_shape[2]] print("output_shape:",output_shape) deconv=tf.nn.conv2d_transpose(input,weights,output_shape=output_shape, strides=[1, 2, 2, 1], padding='SAME') return deconv def main(): weights_np=np.asarray(weights_data,np.float32) #將輸入的每個卷積核旋轉180° weights_np=np.rot90(weights_np,2,(2,3)) const_input = tf.constant(input_data , tf.float32) const_weights = tf.constant(weights_np , tf.float32 ) input = tf.Variable(const_input,name="input") #[c,h,w]------>[h,w,c] input=tf.transpose(input,perm=(1,2,0)) #[h,w,c]------>[n,h,w,c] input=tf.expand_dims(input,0) #weights shape=[out_c,in_c,h,w] weights = tf.Variable(const_weights,name="weights") #[out_c,in_c,h,w]------>[h,w,out_c,in_c] weights=tf.transpose(weights,perm=(2,3,0,1)) #執行tensorflow的反卷積 deconv=tf_conv2d_transpose(input,weights) init=tf.global_variables_initializer() sess=tf.Session() sess.run(init) deconv_val = sess.run(deconv) hwc=deconv_val[0] print(hwc) if __name__=='__main__': main()
上面代碼中,有幾點需要注意:
- 每個卷積核需要旋轉180°后,再傳入tf.nn.conv2d_transpose函數中,因為tf.nn.conv2d_transpose內部會旋轉180°,所以提前旋轉,再經過內部旋轉后,能保證卷積核跟我們所使用的卷積核的數據排列一致。
- 我們定義的輸入的shape為[c,h,w]需要轉為tensorflow所使用的[n,h,w,c]。
- 我們定義的卷積核shape為[out_c,in_c,h,w],需要轉為tensorflow反卷積中所使用的[h,w,out_c,in_c]
轉置卷積和插值的區別與聯系
轉置卷積和插值,都是通過周圍像素點來預測空白像素點的值,區別在於一個權重由人為預先定義的公式計算,一個通過數據驅動來學習。
一些轉置卷積的論文截圖
上圖為以轉置卷積和全卷積網絡為核心的語義分割網絡。
圖(a)是輸入層;圖b、d、f、h、j是不同featrue map大小的轉置卷積的結果;圖c、e、g、i是不同featrue map大小的UnPooling結果。
《Learning Deconvolution Network for Semantic Segmentation》
五、亞像素卷積(sub-pixel convolution,PixelShuffle)
正常情況下,卷積操作會使feature map的高和寬變小。文章將這個算法描述為:LR network,即低分辨卷積網絡。文章拿來與之作對比的是HR network,高分辨卷積網絡,一般HR network是現將低分辨力的圖像進行二次插值變換后然后對變換后的圖像再進行卷積網絡。即HR network是先將圖像進行upsample后才進行卷積,而文中的這個算法操作則是在upsample的過程中對圖像就進行了卷積。
這個算法的實現流程如上圖,舉個例子,實現的功能就是將一個1×1的image通過Sub-pixel操作將其變為rxr的高分辨率圖像,但是這個實現過程不是直接產生這個高分辨率圖像而是先得到r2個通道特征圖然后通過周期篩選(periodic shuffing)得到這個高分辨率的圖像,其中r為upscaling factor,也就是圖像擴大倍率。
我們可以說這種操作:一邊Upsample一邊Convolve,或者說在卷積的同時進行了上采樣(因為擴大后的圖像來自卷積特征圖)。
轉置卷積與普通的亞采樣卷積
我們可以看到,這兩個操作之間的唯一區別是,當從x向y做貢獻時,所使用的權重的下標是不同的。如果我們在亞像素卷積中反轉濾波器f的元素下標,那么這一層將等於一個轉置的卷積層。換句話說,如果學習了過濾器,這兩個操作可以得到相同的結果。
Deconvolution layer vs Convolution in LR
從普通亞像素卷積到LR亞像素卷積:
普通亞像素卷積要先進行填充,從LR圖像創建亞像素圖像
注意到,不同顏色組的卷積核權重進行激活時是相互獨立的,因此完全可以將4x4的卷積核分解成4個2x2的小卷積核。
因此,普通的亞像素卷積可以改造成僅使用卷積運算的LR亞像素卷積,最后卷積完將特征圖reshape成最終的上采樣結果。
What does this mean?
This means that a network can learn to use r2 channels of LR image/feature maps to represent one HR image/feature maps if it is encouraged to do so.
一個網絡能夠學習使用r2 個channel的LR特征圖來表示HR圖像,如果它被鼓勵這么做。
至於為什么在LR上進行卷積更好,作者在《Is the deconvolution layer the same as a convolutional layer?》給出了一些經驗分析。
參考:
[1] https://www.jianshu.com/p/587c3a45df67
[2] https://zhuanlan.zhihu.com/p/41427866
[3] https://blog.csdn.net/qq_27871973/article/details/82973048
[4] https://blog.csdn.net/LoseInVain/article/details/81098502
[5] https://www.cnblogs.com/makefile/p/unpooling.html
[6] https://blog.csdn.net/g11d111/article/details/82855946
[7] https://oldpan.me/archives/upsample-convolve-efficient-sub-pixel-convolutional-layers