卷積層(2)

本文收錄在無痛的機器學習第一季。

這一回我們來看看卷積層的解法。我們將採用兩種方法求解:

  • 一種是實力派解法

  • 一種是軟體庫中常用的套路——「整容」後的偶像派解法。

這裡需要一個小例子,我們假定一個1*5*5的輸入,卷積層的維度是1*1*3*3,同時stride=1,padding=0。最終的輸出是1*3*3。

這裡先畫個詳細的圖,圖像中對其中的變數做了定義:

其中:

X表示輸入的矩陣,我們用一維的0-24表示下標。

K表示卷積核的矩陣,我們用一維的0-8表示下標。

Z表示卷積的結果,我們用一維的0-8表示下標。

由於前面我們提到操作中stride=1,padding=0,所以我們可以給出簡單版和複雜版的輸出維度計算公式:

首先是簡單版:

H_{out}=H_{in}-H_{kernel}+1

W_{out}=W_{in}-W_{kernel}+1

然後是複雜版:

H_{out}=frac{H_{in}+2*H_{padding}-H_{kernel}}{H_{stride}}+1

W_{out}=frac{W_{in}+2*W_{padding}-W_{kernel}}{W_{stride}}+1

很顯然,當stride=1,padding=0時,兩個公式是等價的。

實力派解法

所謂的實力派解法就是用卷積定義(這裡就用相關操作)去做前向計算,然後利用前向的演算法去求反向。

接下來這張圖上詳細介紹了輸出的每一個數值是利用哪一部分的信息計算出來的:

上面這張圖上詳細地講述了每一個像素的前向計算公式,同時在計算的過程中,我們將每一個輸出結果對應的輸入和kernel的信息作了標記。實際上這和上一回我們說的演算法是一樣的。

剛才我們看了前向傳播,下面我們看看反向傳播的計算。反向傳播中的偏置項bias這裡就不說了,和全連接的反向演算法一樣,相對簡單些。另外我們需要計算兩個數值:

  1. 給下一層傳導的loss

  2. 本層卷積核參數的導數。

首先是下一層參數的loss,也可以理解為Loss對輸入數據的梯度。從卷積的過程中直接觀察是比較困難的,我們需要講觀看的視角做一下轉換。想要求出每個輸入的導數,就需要列出每個輸入元素參與的計算,這樣我們用前面得到的Loss和與這個元素相乘的參數進行相乘再相加就可以得到想要的結果了。這個思路和全連接求導的思路是一致的,只是變換的過程需要費點腦筋:

frac{partial Loss}{partial X_i}=[frac{partial Loss}{partial Z_j}, ...]^T * [frac{partial Z_j}{partial K_l},...]

為了更加清晰地展示演算法,我畫了下面這張圖:

從這張圖可以清楚地看出輸入數據和輸出loss的關係。右邊的圖主體上是由5*5的小圖組成,每一個小圖就相當於輸入所在位置的數據參與卷積計算的過程。可以看出,每個輸入數據在卷積過程的參與度是不同的。對於我們這個問題,最終的輸出是9個數字,每個數字由大小為9的卷積計算得到,也就是說我們一共進行了81次乘加操作,我們用矩陣的形式把這個參與次數再寫出來:

1 2 3 2 1

2 4 6 4 2

3 6 9 6 3

2 4 6 4 2

1 2 3 2 1

相加後發現確實是81。數字的數量是相符的。

然後我們還可以從延展的虛線中看出,如果將上面小圖中的兩部分標出的位置進行乘加操作後就可以得出輸入所在點的梯度了,而且另外一個發現就是——把所有輸入點的計算組合起來,是輸出數據的梯度和卷積參數核的卷積操作,但是其中有2處不同:

  1. 卷積核需要旋轉180度

  2. 輸出數據的梯度所在的矩陣需要在周圍做padding,padding的數量等於卷積核的維度減1

所以這一步的求解最終可以轉化成這個偽代碼:

residual_x=conv2(padding(residual_z,kernel.shape()-1), rot180(kernel))n

通過比較複雜的分析,我們最終得到了一個十分簡潔的計算方法。

看完了給下一層傳導的loss,下面是本層的參數導數。和之前類似,我們需要重新整理運算關係。同樣地,我們再來一張圖進行解釋:

從這張圖又可以很清楚地看出這個關係。這一次我們直接把輸入數據和輸出數據的殘差做卷積,就可以得到參數w的導數。比上面的計算還要簡單點。這裡的求解也只需要一步就可以:

residual_w=conv2(x,residual_z)n

實際上到此為止,卷積層的實力解法就到此結束了,實際上它與全連接層最大的不同就是線性部分,關於非線性部分的事情我們後面再說,但是由於線性部分和非線性部分相互獨立,因此分來分析也是完全沒有問題的。

所以從上面的分析中可以看出,想要採用實力派的解法需要能夠清晰地畫出下面三張圖:

  • 前向圖:標有卷積核id->的輸入小圖->組成的輸出大圖

  • 下層Loss:標有卷積核id->的輸出小圖->組成的輸入大圖

  • 本層w導數:標有輸入id->的輸出小圖->組成的卷積核大圖

剛才看到的是stride=1,padding=0的解法,相對來說省略了一些細節的考慮。如果把這些加上,這種方法也是可以解的,只不過比剛才要複雜一些。這裡就不做更進一步的推導了,有志成為「實力派」的童鞋可以去嘗試一下進一步地推導。但不管做怎麼樣的變換,都離不開上面三張圖的推導。熟練推導三張圖能夠讓我們更加深刻地理解卷積(相關)操作內部的過程,是絕對有好處的。

「偶像派」解法

說實話,像神經網路裡面這樣的推導所涉及到的數學能力並不高,但是想熟練掌握需要好好地刷一刷。但是如果不想把自己搞得這麼痛苦,就可以試試偶像派的解法。

偶像派的解法是怎麼做到的呢?我們前面說了卷積層的卷積計算是線性的,既然是線性的我們能不能把輸入矩陣做一個變換,使運算變成一個矩陣和卷積核向量相乘的運算呢?當然可以了,這就涉及到我們的「整容」過程了。

整容的過程只需要用到上面的第一張圖——前向圖。前向圖中每一個小圖都可以表示輸入數據的一部分和卷積核做點積的過程。最終我們求出了9個結果值,因此也就有9個輸入的部分數據和卷積做了點積。這個過程同樣可以畫成一張圖:

這樣看來,前向計算和求解卷積核參數導數的計算就會變得容易得多。不過如果考慮了padding和stride,寫這個轉換的演算法還是需要點細心的。至於向下一層傳遞的導數,做法也類似,轉成矩陣乘法的形式即可,這裡就不再贅述了。

這樣,偶像派的解法也就完成了。藉助了矩陣和向量的乘法,我們的大腦得到了極大的解放。那麼問題來了,對於我們常用的軟體庫,大家是怎麼實現這個演算法的呢?

基本上大家都選擇偶像的道路……倒不是因為偶像派的思路清晰,而是因為矩陣乘法這樣的運算經過大家多年的研究,運算效率非常有保障。而卷積運算在實現過程中像cache友好性這樣的問題會差些,所以最終被淘汰。

當然,現在被廣大群眾所愛戴的——CUDNN庫的實現比我們偶像派的做法在細節上更為精細,可以算是偶像+實力兼備。感興趣的童鞋可以去了解一下。

聊完了線性部分求解的事情,下次就來一起看看非線性部分的一些問題。

廣告時間

更多精彩盡在《深度學習輕鬆學:核心演算法與視覺實踐》!

推薦閱讀:

caffe 在Ubuntu下如何用已訓練出來的模型測試一張圖片?
沐神的第三代parameter server的worker節點只需要保存部分參數,怎麼理解?
目前主流的attention方法都有哪些?
求台大林軒田的learning from data (yaser)這本書後面的problem的答案?
如何系統的學習深度學習?

TAG:机器学习 | 深度学习DeepLearning |