卷積神經網絡學習筆記——ZFNet(Tensorflow實現)

完整代碼及其數據,請移步小編的GitHub地址

  傳送門:請點擊我

  如果點擊有誤:https://github.com/LeBron-Jian/DeepLearningNote

  這個網絡應該是CNN的鼻祖,早就出來了,這篇筆記也早就寫完了,但是一直是未發佈狀態,估計是忘了。雖然說現在已經意義不大了,還是就當自己清理庫存,溫習一下吧。下面開始。

1,ZFNet提出的意義

  由於AlexNet的提出,大型卷積網絡變得流行起來,但是人們對於網絡究竟爲什麼能表現的這麼好,以及怎麼樣變得更好尚不清楚,因此爲了針對上述兩個問題,提出了一個新穎的可視化技術來一窺中間特徵層的功能以及分類的操作。

  ILSCRC 2013 分類任務的冠軍,使用反捲積對CNN的中間特徵圖進行可視化分析,通過分析特徵行爲找到提升模型的辦法,微調 AlexNet 提升了表現。ZFNet 的 Z和F指的是Zeiler和Fergus,曾是 hinton的學生,後在紐約大學讀博的Zeiler,聯手紐約大學研究神經網絡的 Fergus 提出了 ZFNet。

  冠軍?:嚴格意義上來說當時分類冠軍是 Clarifai,實際上 ZFNet排在第三(前兩名分別爲 Clarifai和 NUS),但是我們通常討論的 ILSVRC 2013冠軍(winner)指的是 ZFNet,爲什麼呢?因爲Clarifia和ZFNet都出自Zeiler之手,ZF中的 Zeiler 是 Clarifai 的創建者和 CEO。

  ZFNet(2013)在 AlexNet(2012)的基礎上,性能再次提升,如下圖所示,圖片來自:http://cs231n.stanford.edu/slides/2019/cs231n_2019_lecture09.pdf

  該論文是在AlexNet 基礎上進行了一些細節的改進,網絡結構上並沒有太大的突破。該論文最大的貢獻在於通過使用可視化技術揭示了神經網絡各層到底幹什麼,起到了什麼作用。從科學的觀點觸發,如果不知道神經網絡爲什麼取得了如此好的效果,那麼只能靠不停地實驗來尋找更好的模型。使用一個多層的反捲積網絡來可視化訓練過程中特徵的演化及發現潛在的問題;同時根據遮擋圖像局部對分類結果的影響來探討對分類任務而言到底那部分輸入信息更重要。

  總結來說,論文中最大的貢獻有兩個:

  • 1,提出了 ZFNet,一種比 AlexNet 性能更好的網絡架構
  • 2,提出了一種特徵可視化的方法,並據此來分析和理解網絡

 

2,實現方法

2.1  訓練過程

  對前一層的輸入進行卷積——relu —— max pooling(可選)——局部對比操作(可選)——全連接層——softmax分類器。

  輸入的是(x,  y),計算 y 與 y 的估計值之間的交叉熵損失,反向傳播損失值的梯度,使用隨機梯度下降算法來更新參數(w 和 b)以完成模型的訓練。

  反捲積可視化以各層得到的特徵圖作爲輸入,進行反捲積,得到反捲積的結果,用以驗證顯示各層提取到的特徵圖。比如:假設你想要查看AlexNet的 conv5提取到了什麼東西,我們就用 conv5 的特徵圖後面接一個反捲積網絡,然後通過:反池化,反激活,反捲積,這樣的一個過程,把本來一張 13*13 大小的特徵圖(Conv5 大小爲 13*13),放大回去,最後得到一張與原始輸入圖片一樣大小的圖片(227*227)。

2.2  反池化過程

  嚴格意義上的反池化是無法實現的。作者採用近似的實現,在訓練過程中記錄每一個池化操作的一個 z*z 的區域內輸入的最大值的位置,這樣在反池化的時候,就將最大值返回到其應該在的位置,其他位置的值補0。

   relu:卷積神經網絡使用 relu 非線性函數來保證輸出的 feature map 總是爲正數。在反捲積的時候,也需要保證每一層的 feature map 都是正值,所以這裏還是使用 relu 作爲非線性激活函數。

  池化過程是不可逆的過程,然而我們可以通過記錄池化過程中,最大激活值的座標位置。然後在反池化的時候,只把池化過程中最大激活值所在的位置座標的值激活,其他的值置爲0,當然這個過程只是一種近似,因爲我們在池化的過程中,除了最大值所在的位置,其他的值也是不爲零的。在論文《Stacked What-Where Auto-encoders》,裏面有個反捲積示意圖畫的比較好,所有就截下圖,用這篇文獻的示意圖進行講解:

  以上面的圖片爲例,上面的圖片中左邊表示 pooling 過程,右邊表示 unpooling 過程。假設我們 pooling 塊的大小爲 3*3,採用 max pooling後,我們可以得到一個輸出神經元其激活值爲9, pooling 是一個下采樣的過程,本來是 3*3 大小,經過 pooling後,就變成了 1*1 大小的圖片了。而 unpooling 剛好與 pooling 過程相反,它是一個上採樣的過程,是 pooling 的一個反向運算,當我們由一個神經元要擴展到 3*3個神經元的時候,我們需要藉助於 pooling 過程中,記錄下最大值所在的位置座標(0,  1),然後在 unpooling 過程的時候,就把(0,  1)這個像素點的位置填上去,其他的神經元激活值全部爲0。

  再來一個例子:

  在 max pooling 的時候,我們不僅要得到最大值,同時還要記錄下最大值的座標(-1, -1),然後在 unpooling 的時候,就直接把(-1,  -1)這個點的值填上去,其他的激活值全部爲0。

2.3 反激活

  我們在AlexNet 中, relu 函數是用於保護每層輸出的激活值都是正數,因此對於反向過程,我們同樣需要保證每層的特徵圖爲正值,也就是說這個反激活過程和激活過程沒有什麼差別,都是直接採用 relu函數。

2.4  反捲積操作(對應卷積網絡卷積操作)

  (參考地址:https://zhuanlan.zhihu.com/p/140896660

  卷積操作是低效操作,主流神經網絡框架都是通過 im2col + 矩陣乘法實現卷積,以空間換效率。輸入中的每個卷積窗口內的元素被拉直成爲單獨一列,這樣輸入就被轉換爲了 H_out * W_out 列的矩陣(Columns),im2col 由此得名;將卷積核也拉成一列後(Kernel),左乘輸入矩陣,得到卷積結果(Output)。im2col 和矩陣乘法如下兩圖:

   本文中提到的反捲積操作其實是轉置卷積,神經網絡框架藉助轉置卷積實現梯度的反向傳播:如下兩圖:將卷積核矩陣轉置(Weight_T)後,左乘梯度輸出(GradOutput),得到梯度列矩陣(GradColumns),通過 col2im 還原到輸入大小,便得到了相對於輸入的梯度(GradInput)。

 

   梯度的反向傳播一般用於卷積網絡的訓練過程,通過梯度更新權重和偏正取值;而本文轉置卷積不是用於梯度反向傳播,操作對象不是梯度而是特徵值。

3,網絡結構

  ZFNet的網絡結構實際上和AlexNet沒有很大的變換,差異表現在AlexNet用了兩塊GPU的稀疏連接結構,而ZFNet只用了一塊GPU的稠密連接結構;同時,由於可視化可以用來選擇好的網絡結構,通過可視化發現ALexNet第一層中有大量的高頻和低頻信息混合,缺幾乎沒有覆蓋到中間的頻率信息,且第二層中由於第一層卷積用的步長爲4太大了,導致有非常多的混疊情況,因此改變了AlexNet的第一層,即將濾波器的大小由 11*11 變成 7*7,並且將步長 4變爲2,下圖爲AlexNet網絡結構與ZFNet的比較。

 

   其實它和AlexNet一樣,頭兩個全連接層後面加 0.5 的 dropout。相比於 AlexNet,主要區別是使用了更小的卷積核和步長,11*11 的卷積核變成 7*7 的卷積核,stride由4變爲2。另外,通過可視化發現第一層的卷積核影響大,於是對第一層的卷積核做了規範化,如果RMS(Root Mean Square)超過 0.1,就把卷積核的均方根 normalize 爲固定 0.1。

  其實僅僅修改了上面兩個內容,就能獲得幾個點的性能提升。所以爲什麼這樣修改?這樣修改的動機是什麼?論文中這樣描述:

  通過對 AlexNet 的特徵進行可視化,文章作者發現第2層出現了 aliasing。在數字信號處理中, aliasing 是指在採樣頻率過低時出現的不同信號混淆的現象,作者認爲這是第一個卷積層 stride 過大引起的,爲了解決這個問題,可以提高採樣頻率,所以將 stride 從4調整到2,與之相應的將 kernel size 也縮小(可以認爲 stride 變小了,kernel沒有必要看那麼大範圍),這樣修改前後,特徵的變化情況如下圖所示,第1層呈現了更具區分力的特徵,第2層的特徵也更加清晰,沒有 aliasing 的線性。

  這就引起了另外一個問題,如何將特徵可視化?正如論文

  標題Visualizing and Understanding Convolutional Networks所顯示的那樣,與提出一個性能更好的網絡結構相比,這篇論文更大的貢獻在於提出一種將卷積神經網絡深層特徵可視化的方法

4,特徵可視化

  可視化操作,針對的是已訓練好的網絡,或者訓練過程中的網絡快照,可視化操作不會改變網絡的權重,只是用於分析和理解在給定輸入圖像時網絡觀察到了什麼樣的特徵,以及訓練過程中特徵發生了什麼變化。

  可視化技術揭露了激發模型中每層單獨的特徵圖,也允許觀察在訓練階段特徵的演變過程且診斷出模型的潛在問題。

  可視化技術用到了多層解卷積網絡,即有特徵激活返回到輸入像素空間。同時進入了分類器輸出的敏感性分析,即通過阻止部分輸入圖像來揭示那部分對於分類是重要的。

  這個可視化技術提供了一個非參數的不變性來展示來自訓練集的哪一塊激活那個特徵圖,不僅需要裁剪輸入圖像,而且自上而下的投影來揭露來自每塊的結構激活一個特徵圖。

  可視化技術依賴於解卷積操作,即卷積操作的逆過程,將特徵映射到像素上。由於解卷積是一種非監督學習,因此只能作爲已經訓練過的卷積網的探究,不能用作任何學習途徑。

  下圖爲卷積過程以及解卷積過程。

  對於一般的卷積神經網絡,前向傳播時不斷經歷 input image→conv → rectification → pooling →……,可視化時,則從某一層的feature map開始,依次反向經歷 unpooling → rectification → deconv → …… → input space,如下圖所示,上方對應更深層,下方對應更淺層,前向傳播過程在右半側從下至上,特徵可視化過程在左半側從上至下:

  上圖左半部分是一個解卷積層,右半部分爲一個卷積層。解卷積層將會重建一個來自下一層的卷積特徵近似版本。圖中使用  switch 來記錄在卷積網中進行最大池化操作時每個池化區域的局部最大值的位置,經過非池化操作之後,原來的非最大值的位置都置爲零。

5,總結

  AlexNet的改進,改動不大,主要是引入了可視化,使用瞭解卷積核反赤化(無法實現,只能近似)的近似每一層進行可視化,並採用一個 GPU進行訓練。

   提出了一種可視化的方法;發現學習到的特徵遠不是無法解釋的,而是特徵間存在層次性,層數越深,特徵不變性越強,類別的判別能力越強;通過可視化模型中間層,在AlexNet基礎上進一步提升了分類效果;而且遮擋實驗表明分類時模型和局部塊的特徵高度相關;模型的深度很關鍵;預訓練模型可以在其他數據集上 fine-tuning 得到很好的結果。

  按點總結:

  • 1,在擴充訓練集的時候,調整圖像角度是關鍵,不需要過多的將圖像切割成多片進行訓練。
  • 2,仔細考慮每個層對其他層的影響,可適當精簡層,特別是全連接層
  • 3,可先進行其他數據集的預訓練
  • 4,大部分 CNN 結構中,如果網絡的輸出是一整張圖片的話,那麼就需要使用到反捲積網絡,比如圖片語義分割,圖片去模糊,可視化,圖片無監督學習,圖片深度估計,像這種網絡的輸出是一整張圖片的任務,很多都有相關的文獻,而且都是利用了反捲積網絡。
  • 5,提出了一個新穎的可視化方法,通過可視化證明了卷積網絡的一些特徵,複合型,特徵不變性和網絡深度的關係
  • 6,通過可視化還可以調試網絡結構,通過遮蔽實驗證明網絡學習到一種隱的相關性,通過消減實驗證明深度很重要
  • 7,特徵推廣

6,TensorFlow 卷積(Conv)實現

  從一個通道的圖片進行卷積生成新的單通道圖的過程很容易理解,對於多個通道卷積後生成多個通道的圖理解起來有點抽象。下面以通俗易懂的方式對卷積的原理進行實現。

  注意:這裏只針對 batch_size = 1,padding='SAME', stride=[1, 1, 1, 1]進行實驗和解釋,如果其他不是這個參數設置,原理其實也是一樣的。

  下面看一下卷積實現原理,對於有 input_c 個通道的輸入圖,如果需要經過卷積後輸出 output_c 個通道圖,那麼總共需要 input_c * output_c 個卷積核參與運算。如下圖:

   如上圖,輸入爲 [h: 5, w: 5, c: 4] ,那麼對應輸出的每個通道,需要 4 個卷積核。上圖中,輸出爲 3 個通道,所以總共需要 3*4 = 12 個卷積核。對於單個輸出通道中的每個點,取值爲對應的一組 4 個不同的卷積核經過卷積計算後的和。

  接下來,我們以輸入爲 2 個通道寬高分別爲 5 的輸入,3*3 的卷積核,1個通道寬高分別爲5的輸出,作爲一個例子展開。

  2個通道,5*5的輸入定義如下:

#輸入,shape=[c,h,w]
input_data=[
              [[1,0,1,2,1],
               [0,2,1,0,1],
               [1,1,0,2,0],
               [2,2,1,1,0],
               [2,0,1,2,0]],

               [[2,0,2,1,1],
                [0,1,0,0,2],
                [1,0,0,2,1],
                [1,1,2,1,0],
                [1,0,1,1,1]],
 
            ]

   對於輸出爲1通道的 map,根據前面計算方法,需要 2*1 個卷積核,定義卷積核如下:

#卷積核,shape=[in_c,k,k]=[2,3,3]
weights_data=[ 
               [[ 1, 0, 1],
                [-1, 1, 0],
                [ 0,-1, 0]],
               [[-1, 0, 1],
                [ 0, 0, 1],
                [ 1, 1, 1]] 
             ]

   上面定義的數據,在接下來的計算對應關係將按下圖所描述的方式進行。

   由於 TensorFlow 定義的 tensor 的 shape 爲 [n, h, w, c],這裏我們可以直接把 n 設置爲 1,即batch_size 爲1。還有一個問題,就是剛纔定義的輸入爲 [c, h, w] ,所以需要將其轉換爲 [h, w, c]。轉換方式如下,註釋已經解釋的很詳細了:

def get_shape(tensor):
    [s1,s2,s3]= tensor.get_shape() 
    s1=int(s1)
    s2=int(s2)
    s3=int(s3)
    return s1,s2,s3

def chw2hwc(chw_tensor): 
    [c,h,w]=get_shape(chw_tensor) 
    cols=[]
   
    for i in range(c):
        #每個通道里面的二維數組轉爲[w*h,1]即1列 
        line = tf.reshape(chw_tensor[i],[h*w,1])
        cols.append(line)

    #橫向連接,即將所有豎直數組橫向排列連接
    input = tf.concat(cols,1)#[w*h,c]
    #[w*h,c]-->[h,w,c]
    input = tf.reshape(input,[h,w,c])
    return input

   同理,Tensorflow使用卷積核的時候,使用的格式是 [k, k, input_c, output_c] 。而我們在定義卷積核的時候是按照 [input_c, k, k ] 的方式定義的,這裏需要將  [input_c, k, k] 轉換爲 [k, k, input_c],由於爲了簡化工作量,我們規定輸出爲 1 個通道,即 output_c = 1。所以這裏我們可以直接簡單地對 weights_data 調用  chw2hwc,再在第3維度上擴充一下即可。

  完整代碼如下:

import tensorflow as tf
import numpy as np

input_data = [
    [[1, 0, 1, 2, 1],
     [0, 2, 1, 0, 1],
     [1, 1, 0, 2, 0],
     [2, 2, 1, 1, 0],
     [2, 0, 1, 2, 0]],

    [[2, 0, 2, 1, 1],
     [0, 1, 0, 0, 2],
     [1, 0, 0, 2, 1],
     [1, 1, 2, 1, 0],
     [1, 0, 1, 1, 1]],

]
weights_data = [
    [[1, 0, 1],
     [-1, 1, 0],
     [0, -1, 0]],
    [[-1, 0, 1],
     [0, 0, 1],
     [1, 1, 1]]
]


def get_shape(tensor):
    [s1, s2, s3] = tensor.get_shape()
    s1 = int(s1)
    s2 = int(s2)
    s3 = int(s3)
    return s1, s2, s3


def chw2hwc(chw_tensor):
    [c, h, w] = get_shape(chw_tensor)
    cols = []

    for i in range(c):
        # 每個通道里面的二維數組轉爲[w*h,1]即1列
        line = tf.reshape(chw_tensor[i], [h * w, 1])
        cols.append(line)

    # 橫向連接,即將所有豎直數組橫向排列連接
    input = tf.concat(cols, 1)  # [w*h,c]
    # [w*h,c]-->[h,w,c]
    input = tf.reshape(input, [h, w, c])
    return input


def hwc2chw(hwc_tensor):
    [h, w, c] = get_shape(hwc_tensor)
    cs = []
    for i in range(c):
        # [h,w]-->[1,h,w]
        channel = tf.expand_dims(hwc_tensor[:, :, i], 0)
        cs.append(channel)
    # [1,h,w]...[1,h,w]---->[c,h,w]
    input = tf.concat(cs, 0)  # [c,h,w]
    return input


def tf_conv2d(input, weights):
    conv = tf.nn.conv2d(input, weights, strides=[1, 1, 1, 1], padding='SAME')
    return conv


def main():
    const_input = tf.constant(input_data, tf.float32)
    const_weights = tf.constant(weights_data, tf.float32)

    input = tf.Variable(const_input, name="input")
    # [2,5,5]------>[5,5,2]
    input = chw2hwc(input)
    # [5,5,2]------>[1,5,5,2]
    input = tf.expand_dims(input, 0)

    weights = tf.Variable(const_weights, name="weights")
    # [2,3,3]-->[3,3,2]
    weights = chw2hwc(weights)
    # [3,3,2]-->[3,3,2,1]
    weights = tf.expand_dims(weights, 3)

    # [b,h,w,c]
    conv = tf_conv2d(input, weights)
    rs = hwc2chw(conv[0])

    init = tf.global_variables_initializer()
    sess = tf.Session()
    sess.run(init)
    conv_val = sess.run(rs)

    print(conv_val[0])


if __name__ == '__main__':
    main()

   上面代碼有幾個地方提一下:

  • 1,由於輸出通道爲1,因此可以對卷積核數據轉換的時候直接調用  chw2hwc,如果輸入通道不爲1,則不能這樣完成轉換
  • 2,輸入完成 chw 轉 hwc 後,記得在第 0 維擴充維數,因爲卷積要求輸入爲  [n, h, w, c]
  • 3,爲了方便我們查看結果,記得將 hwc 的 shape 轉爲 chw

  執行代碼,運行結果如下:

[[ 2.  0.  2.  4.  0.]
 [ 1.  4.  4.  3.  5.]
 [ 4.  3.  5.  9. -1.]
 [ 3.  4.  6.  2.  1.]
 [ 5.  3.  5.  1. -2.]]

   這個結果的計算過程,如下動圖:

 

7,TensorFlow 反捲積(DeConv)實現

  反捲積原理不太好用文字描述,這裏直接以一個簡單例子描述反捲積過程。

  假設輸入如下:

[[1,0,1],
 [0,2,1],
 [1,1,0]]

   反捲積卷積核如下:

[[ 1, 0, 1],
 [-1, 1, 0],
 [ 0,-1, 0]]

   現在通過 stride = 2 來進行反捲積,使得尺寸由原來的 3*3 變爲 6*6 。那麼在 TensorFlow框架中,反捲積的過程如下(不同框架在裁剪這步可能不一樣):

 

   其實上面這張圖,作者已經把原理講的很清晰了,大致步驟就是,先填充零,然後進行卷積,最後還要進行裁剪。

  下面反捲積我們將輸出通道設置爲多個,這樣更符合實際場景。

  先定義輸入和卷積核:

input_data=[
               [[1,0,1],
                [0,2,1],
                [1,1,0]],

               [[2,0,2],
                [0,1,0],
                [1,0,0]],

               [[1,1,1],
                [2,2,0],
                [1,1,1]],

               [[1,1,2],
                [1,0,1],
                [0,2,2]]

            ]
weights_data=[ 
              [[[ 1, 0, 1],
                [-1, 1, 0],
                [ 0,-1, 0]],
               [[-1, 0, 1],
                [ 0, 0, 1],
                [ 1, 1, 1]],
               [[ 0, 1, 1],
                [ 2, 0, 1],
                [ 1, 2, 1]], 
               [[ 1, 1, 1],
                [ 0, 2, 1],
                [ 1, 0, 1]]],

              [[[ 1, 0, 2],
                [-2, 1, 1],
                [ 1,-1, 0]],
               [[-1, 0, 1],
                [-1, 2, 1],
                [ 1, 1, 1]],
               [[ 0, 0, 0],
                [ 2, 2, 1],
                [ 1,-1, 1]], 
               [[ 2, 1, 1],
                [ 0,-1, 1],
                [ 1, 1, 1]]]  
           ]

   上面定義的輸入和卷積核,在接下的運算過程如下圖所示:

 

   可以看到實際上,反捲積和娟姐基本一致,差別在於,反捲積需要填充過程,並在最後一步需要裁剪,具體實現代碼如下:

#根據輸入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()

   計算卷積代碼,運行結果如下:

[[[  4.   3.   6.   2.   7.   3.]
  [  4.   3.   3.   2.   7.   5.]
  [  8.   6.   8.   5.  11.   2.]
  [  3.   2.   7.   2.   3.   3.]
  [  5.   5.  11.   3.   9.   3.]
  [  2.   1.   4.   5.   4.   4.]]

 [[  4.   1.   7.   0.   7.   2.]
  [  5.   6.   0.   1.   8.   5.]
  [  8.   0.   8.  -2.  14.   2.]
  [  3.   3.   9.   8.   1.   0.]
  [  3.   0.  13.   0.  11.   2.]
  [  3.   5.   3.   1.   3.   0.]]]

   爲了驗證實現的代碼的正確性,我們使用 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() 

   上面代碼中,有幾點需要注意:

  • 1,每個卷積核需要旋轉 180度後,再傳入 tf.nn.conv2d_transpose函數中,因爲 tf.nn.conv2d_transpose 內部會旋轉 180度,所以提前旋轉,再經過內部旋轉後,能保證卷積核跟我們所使用的卷積核的數據排列一致。
  • 2,我們定義的輸入的 shape 爲 [c, h, w] 需要轉爲 TensorFlow所使用的 [n, h, w, c]
  • 3,我們定義的卷積核 shape爲 [output_c, input_c, h, w] ,需要轉爲 TensorFlow反捲積中所使用的 [h, w, output_c, input_c]

  執行上面代碼後,執行結果如下:

[[  4.   3.   6.   2.   7.   3.]
 [  4.   3.   3.   2.   7.   5.]
 [  8.   6.   8.   5.  11.   2.]
 [  3.   2.   7.   2.   3.   3.]
 [  5.   5.  11.   3.   9.   3.]
 [  2.   1.   4.   5.   4.   4.]]
[[  4.   1.   7.   0.   7.   2.]
 [  5.   6.   0.   1.   8.   5.]
 [  8.   0.   8.  -2.  14.   2.]
 [  3.   3.   9.   8.   1.   0.]
 [  3.   0.  13.   0.  11.   2.]
 [  3.   5.   3.   1.   3.   0.]]

   對比結果發現,數據是一致的,證明我們寫的是正確的。

 

 https://blog.csdn.net/cdknight_happy/article/details/78855172

 https://blog.csdn.net/cdknight_happy/article/details/78855172

參考文獻:https://blog.csdn.net/chenyuping333/article/details/82178769

https://www.cnblogs.com/shine-lee/p/11563237.html

https://blog.csdn.net/cdknight_happy/article/details/78855172

https://blog.csdn.net/weixin_43624538/article/details/84309889

卷積:https://www.jianshu.com/p/abb7d9b82e2a

反捲積:https://www.jianshu.com/p/f0674e48894c

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章