文章目錄
- 1.空洞卷積
- 2.轉置卷積
- 矩陣角度
- 轉置卷積實作
- 3.分離卷積
1.空洞卷積
空洞卷積在普通卷積的感受野上增加一個 dilation rate 引數,用于控制感受野區域的采樣步長,如下圖所示:當感受野的采樣步長 dilation rate 為 1 時,每個感受野采樣點之間的距離為 1,此時的空洞卷積退化為普通的卷積;但 dilation rate 為 2 時,感受野每 2 個單元采樣一個點,如圖中間的綠色方框中綠色格子所示,每個采樣格子之間的距離為 2;同樣的方法,圖右邊的 dilation rate 為 3,采樣步長為 3,盡管 dilation rate 的增大會使得感受野區域增大,但是實際參與運算的點數仍然保持不變,

在 TensorFlow 中,可以通過設定 layers.Conv2D() 類的 dilation_rate 引數來選擇使用普通卷積還是空洞卷積:
import tensorflow as tf
from tensorflow.keras import datasets,layers,losses,optimizers,Sequential
x = tf.random.normal([1,7,7,1])# 模擬輸入
# 空洞卷積,1 個 3x3 的卷積核
layer = layers.Conv2D(1,kernel_size=3,strides=1,dilation_rate=2)
out = layer(x)
out.shape
TensorShape([1, 3, 3, 1])
當 dilation_rate 引數設定為默認值 1 時,使用普通卷積方式進行運算;當 dilation_rate 引數大于 1 時,采樣空洞卷積方式進行計算,
2.轉置卷積
轉置卷積:通過在輸入之間填充大量的 padding 來實作輸出高寬大于輸入高寬的效果,從而實作向上采樣的目的,

下面我們用用輸入為 5x5,步長𝑠 = 2,填充𝑝 = 0,3x3 的普通卷積運算進行驗證演示,如下圖所示:

我們使用代碼來實作:
# 創建 X 矩陣,高寬為 5x5
x = tf.range(25)+1 # Reshape 為合法維度的張量
x = tf.reshape(x,[1,5,5,1])
x = tf.cast(x, tf.float32)
# 創建固定內容的卷積核矩陣
w = tf.constant([[-1,2,-3.],[4,-5,6],[-7,8,-9]])
# 調整為合法維度的張量
w = tf.expand_dims(w,axis=2)
w = tf.expand_dims(w,axis=3) # 進行普通卷積運算
out = tf.nn.conv2d(x,w,strides=2,padding='VALID')
out
<tf.Tensor: shape=(1, 2, 2, 1), dtype=float32, numpy=
array([[[[ -67.],
[ -77.]],
[[-117.],
[-127.]]]], dtype=float32)>
現在我們將普通卷積的輸出作為轉置卷積的輸入,驗證轉置卷積的輸出是否為 5x5:
# 普通卷積的輸出作為轉置卷積的輸入,進行轉置卷積運算
xx = tf.nn.conv2d_transpose(out, w, strides=2,padding='VALID',output_shape=[1,5,5,1])
xx.shape
TensorShape([1, 5, 5, 1])
矩陣角度
轉置卷積的轉置是指卷積核矩陣 W 產生的稀疏矩陣W′在計算程序中需要先轉置WT, 再進行矩陣相乘運算,
以 4 行 4 列的輸入 X,高寬為 3x3,步長為 1,無 padding 的卷積核 W 為例,首先將 X 打平成一維形式X′:

然后將卷積核 W 轉換成稀疏矩陣W′

此時通過一次矩陣相乘即可實作普通卷積運算:O′ = W′@X′
如果給定O,希望能夠生成與 X 同形狀大小的張量,怎么實作呢?將W′轉置后與重排后的O’完成矩陣相乘即可:X = W’T@O’. 得到的X′通過 reshape 與原來的輸入 X 尺寸一致,但是內容不同,
轉置卷積實作
在 TensorFlow 中,可以通過 nn.conv2d_transpose 實作轉置卷積運算,我們先通過 nn.conv2d 完成普通卷積運算,注意轉置卷積的卷積核的定義格式為 [k,k,cout,cin]:
# 創建 4x4 大小的輸入
x = tf.range(16)+1
x = tf.reshape(x,[1,4,4,1])
x = tf.cast(x, tf.float32)
# 創建 3x3 卷積核
w = tf.constant([[-1,2,-3.],[4,-5,6],[-7,8,-9]])
w = tf.expand_dims(w,axis=2)
w = tf.expand_dims(w,axis=3) # 普通卷積運算
out = tf.nn.conv2d(x,w,strides=1,padding='VALID')
out
<tf.Tensor: shape=(1, 2, 2, 1), dtype=float32, numpy=
array([[[[-56.],
[-61.]],
[[-76.],
[-81.]]]], dtype=float32)>
在保持 strides=1, padding=’VALID’,卷積核不變的情況下,我們通過卷積核 w 與輸出 out的轉置卷積運算嘗試恢復與輸入 x 相同大小的高寬張量:
xx = tf.nn.conv2d_transpose(out, w, strides=1, padding='VALID', output_shape=[1,4,4,1])
xx = tf.squeeze(xx)
xx = tf.squeeze(xx)
xx
<tf.Tensor: shape=(4, 4), dtype=float32, numpy=
array([[ 56., -51., 46., 183.],
[-148., -35., 35., -123.],
[ 88., 35., -35., 63.],
[ 532., -41., 36., 729.]], dtype=float32)>
tf.nn.conv2d_transpose 并不支持自定義 padding 設定,只能設定為 VALID 或者 SAME,如果設定 padding=’VALID時,輸出大小表達為:𝑜 = (𝑖 ? 1) ? 𝑠 + 𝑘,如果設定padding=’SAME時,輸出大小表達為:𝑜= (𝑖 ? 1) ? 𝑠 + 1
轉置卷積也可以和其他層一樣,通過 layers.Conv2DTranspose 類創建一個轉置卷積層,然后呼叫實體即可完成前向計算:
# 創建轉置卷積類
layer = layers.Conv2DTranspose(1,kernel_size=3,strides=1,padding='VALID')
xx2 = layer(out)
xx2.shape
TensorShape([1, 4, 4, 1])
3.分離卷積
這里以深度可分離卷積(Depth-wise Separable Convolution)為例,普通卷積在對多通道輸入進行運算時,卷積核的每個通道與輸入的每個通道分別進行卷積運算,得到多通道的特征圖,再對應元素相加產生單個卷積核的最終輸出:

分離卷積的計算流程則不同,卷積核的每個通道與輸入的每個通道進行卷積運算,得到多個通道的中間特征,如圖 10.61 所示,這個多通道的中間特征張量接下來進行多個1x1 卷集核的普通卷積運算,得到多個高寬不變的輸出,這些輸出在通道軸上面進行拼接,從而產生最終的分離卷積層的輸出,可以看到,分離卷積層包含了兩步卷積運算,第一步卷積運算是單個卷積核,第二個卷積運算包含了多個卷積核,

那么采用分離卷積有什么優勢呢?一個很明顯的優勢在于,同樣的輸入和輸出,采用Separable Convolution 的引數量約是普通卷積的1/3,考慮上圖中的普通卷積和分離卷積的例子,普通卷積的引數量是 3 ? 3 ? 3 ? =108,分離卷積的第一部分引數量 3 ? 3 ? 3 ? =27,第二部分引數量 1 ? 1 ? 3 ? 4 =14
分離卷積的總引數量只有39,但是卻能實作普通卷積同樣的輸入輸出尺寸,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/202045.html
標籤:其他
