您當前的位置:首頁 > 攝影

轉置卷積(反捲積)

作者:由 風度千賀 發表于 攝影時間:2020-03-16

轉置卷積又稱反捲積,逆卷積。在主流的深度學習框架之中,如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的卷積核也進行拼接,如下圖

轉置卷積(反捲積)

我們記向量化的影象為

I^T

, 向量化的卷積矩陣為

C

,輸出特徵向量為

O

I^T*C=O^T

轉置卷積(反捲積)

我們將一個1*16的行向量乘以一個16*4的矩陣,得到一個1*4的行向量,那麼反過來一個1*4的向量乘以一個4*16的矩陣不就是能得到一個1*16的行向量,這既是轉置卷積的思想。

轉置卷積

一般卷積操作(這裡只考慮最簡單的無padding,stride=1的情況),都將輸入的資料越卷越小,根據卷積核大小的不同,和步長的不同,輸出尺寸變化也很大。但是有時候,我們需要輸入一個小的特徵,輸出更大的尺寸的特徵。比如,影象語義分割中,往往要求最終的輸出的特徵尺寸和原始的輸入尺寸相同,但是在網路卷積核池化的過程中特徵圖的尺寸逐漸變小,這裡轉置卷積便能派上用場。在數學上,轉置卷積的操作非常簡單,把正常的卷積操作反過來即可。

Q^T*C^T=I^T

轉置卷積(反捲積)

這裡需要注意的是,這兩個操作並不是可逆的,對於用一個卷積核,經過轉置卷積操作後並不能恢復到原始的數值,只是保留了原始的形狀

形象化的轉置卷積

視覺化轉置卷積,以上式的第一列為例

轉置卷積(反捲積)

這裡將輸入還原為一個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))

引用學習:

標簽: 卷積  tf  轉置  輸入  sess