轉置卷積(反捲積)
轉置卷積又稱反捲積,逆卷積。在主流的深度學習框架之中,如Tensorflow,Pytorch,Kreas中的函式名都是conv_transpose
將一個4*4的輸入透過3*3的卷積核核進行普通卷積後(無padding,stride=1),將得到2*2的輸出。而轉置卷積將一個2*2的輸入透過同樣的3*3的卷積核,將得到一個4*4的輸出。這看起來像是普通卷積的逆過程。事實上,這兩者沒有任何關係,操作過程也是不可逆的。
普通卷積(直接卷積)
但在實際計算中,並不是透過卷積核在輸入上進行滑動計算,效率太低,而是將卷積核轉換為等效矩陣,將輸入轉化為向量,透過輸入向量核卷積核矩陣的相乘獲得輸出向量。輸出的向量經過整形便可得到我們的二維輸出特徵。
具體操作如下圖所示,由於一個3*3的卷積核要在輸入上不同位置卷積卷積4次,所以透過補0的方式,將卷積核分別置於一個4*4矩陣的四個角落,這樣我們的輸入可以直接和這四個4*4的矩陣進行卷積,而捨去了滑動操作。
進一步我們將輸入拉成長向量,四個4*4的卷積核也進行拼接,如下圖
我們記向量化的影象為
, 向量化的卷積矩陣為
,輸出特徵向量為
我們將一個1*16的行向量乘以一個16*4的矩陣,得到一個1*4的行向量,那麼反過來一個1*4的向量乘以一個4*16的矩陣不就是能得到一個1*16的行向量,這既是轉置卷積的思想。
轉置卷積
一般卷積操作(這裡只考慮最簡單的無padding,stride=1的情況),都將輸入的資料越卷越小,根據卷積核大小的不同,和步長的不同,輸出尺寸變化也很大。但是有時候,我們需要輸入一個小的特徵,輸出更大的尺寸的特徵。比如,影象語義分割中,往往要求最終的輸出的特徵尺寸和原始的輸入尺寸相同,但是在網路卷積核池化的過程中特徵圖的尺寸逐漸變小,這裡轉置卷積便能派上用場。在數學上,轉置卷積的操作非常簡單,把正常的卷積操作反過來即可。
這裡需要注意的是,這兩個操作並不是可逆的,對於用一個卷積核,經過轉置卷積操作後並不能恢復到原始的數值,只是保留了原始的形狀
形象化的轉置卷積
視覺化轉置卷積,以上式的第一列為例
這裡將輸入還原為一個2*2的張量,新的卷積核由於左上角有非零值,可以計算得到右側結果
對每一個列向量都可以做這樣的變換
結合整體,彷彿是有一個更大的卷積核在2*2的大小的輸入上滑動,但是輸入太小,每一次卷積只能對應卷積核的一部分
直接卷積是用一個小窗戶看大世界,而轉置卷積是用一個大窗戶的一部分去看小世界。
這裡需要注意。我們定義的卷積是左上角為a,右下角為i,但是在視覺化卷積的過程中需要將卷積核旋轉180度 後再進行卷積。由於輸入影象太小,我們按照卷積核的尺寸來進行補0操作,補0數量為0即3-1,這樣就將一個轉置卷積轉換為對應的直接卷積
總結一下轉置卷積轉換為直接卷積的步驟(這裡只考慮stride=1 padding=0的情況)
設卷積核大小為k*k,輸入為方形矩陣
(1)對輸入進行四邊補0,單邊補0的數量為k-1
(2)將卷積核旋轉180度,再新的輸入上進行直接卷積
# -*- coding: utf-8 -*-
# @Author : qiaohezhe
# @github : https://github。com/fengduqianhe
# @Date : 2020/1/23 12:41
# version: Python 3。7。8
# @File : tensorflow_example6。py
# @Software: PyCharm
#轉置卷積的驗證例子
import tensorflow as tf
x = tf。reshape(tf。constant([[1,2],
[4,5]],dtype=tf。float32), [1, 2, 2, 1])
kernel = tf。reshape(tf。constant([[1,2,3],
[4,5,6],
[7,8,9]],dtype=tf。float32), [3, 3, 1, 1])
transpose_conv = tf。nn。conv2d_transpose(x, kernel, output_shape=[1, 4, 4, 1], strides=[1,1,1,1], padding=‘VALID’)
sess = tf。Session()
print(sess。run(x))
print(sess。run(kernel))
print(sess。run(transpose_conv))
x2 = tf。reshape(tf。constant([[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 1, 2, 0, 0],
[0, 0, 4, 5, 0, 0],
[0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0]],dtype=tf。float32), [1, 6, 6, 1])
kernel2 = tf。reshape(tf。constant([[9,8,7],
[6,5,4],
[3,2,1]],dtype=tf。float32), [3, 3, 1, 1])
conv = tf。nn。conv2d(x2,kernel2,strides=[1,1,1,1],padding=‘VALID’)
print(sess。run(x2))
print(sess。run(kernel2))
print(sess。run(conv))
引用學習: