CNN 自寫代碼調參經驗

對非線性函數的一個最成功的改進就是糾正線性單元(Rectified Linear Units,ReLU),其作用是如果卷積計算的值小於0,則讓其等於0,否則即保持原來的值不變。這種做法所謂是簡單粗暴,但ReLU使網絡的激活具有了稀疏的特性,實驗說明一切。


起因

從去年開始接觸神經網絡,覺得CNN結構很簡單沒什麼難的,直到有一天被一位拿過信息競賽金牌的學弟鄙視了……中期答辯之後不忙,於是我花了幾天用MATLAB寫了一個CNN,算是給自己一個交代。

爲什麼用MATLAB?

  1. 相比c/c++,MATLAB實現更方便
  2. 我沒打算寫又快又漂亮的產品級的代碼,只是希望摳一遍算法的各種細節,所以theano等各種成熟的工具包就不考慮了

實現

這篇文章不打算講各種基礎的細節,只是總結一下我踩的各種坑……

CNN訓練無非是反向傳播,現成的公式很容易找到,自己花一些時間也能推導出來。我參照的是這篇文章:

@article{bouvrie2006notes,
title={Notes on convolutional neural networks},
author={Bouvrie, Jake},
year={2006}
}

這篇文章的好處在於作者在推導公式的同時順便給出了MATLAB上的實現方法。

遇到的問題

梯度消失

我在實現過程中犯的第一個錯誤是沒有循序漸進。仗着自己寫過一些神經網絡的代碼以爲手到擒來,直接按照 LeNet-5 的結構寫,過於複雜的結構給測試和調試都帶來了很大的麻煩,可謂不作死就不會死。

簡單分析一下LeNet-5的結構:第一層8個5*5的卷積核,第二層分別作2*2pooling,第三層16個5*5的卷積核,第四層2*2pooling,隨後是三個節點數分別爲120、84、10的全連接層(做手寫數字識別)。這時的參數個數爲卷積核及其偏置+pooling層放縮係數及偏置+全連接層權重及偏置,全鏈接層的參數個數比前四層的參數個數 多了兩個數量級 。

過多的參數個數會極大地提升運算時間,降低測試的效率;同時過多的層數伴隨着嚴重的梯度消失問題(vanishing gradient),嚴重影響訓練效果。在上面提到的網絡結構下,我發現傳到第一層的梯度已經小於1e-10,只有後面的全連接層在變化,卷積層幾乎訓不動了。

我對付這個問題的手段包括:

  1. 減少層數
  2. 增大學習率(learning rate)
  3. 用ReLU代替sigmoid

其中前兩種雖然也有效果,只能算是權宜之計,第三種纔算是正經的解決了這個問題。在採用ReLU作爲激活函數之後,傳到輸入層的梯度已經達到1e-5~1e-4這個量級。我用MNIST數據集裏的5000個樣本訓練,1000個樣本測試,發現測試結果差不多,收斂速度快了2倍左右。據@kevin好好學習的說法,ReLU還使網絡的激活具有了稀疏的特性,我對這方面就沒研究了。

非線性映射的位置

在前面提到的Bouvrie的文章中,非線性映射放在卷積層還是pooling層後面都可以,微博上幾位大牛也是這個觀點。奇怪的是,我在一開始實現的時候把sigmoid放在了pooling層後面,雖然很渣但至少能跑出結果。後來我把sigmoid放在卷積層後面就訓不動了,感覺跟梯度消失的時候很像。我猜測也許是卷積層和pooling層激活程度不同導致傳回去的梯度大小不一樣。

權重衰減(weight decay)

因爲我用的數據很少(數據多了跑起來太慢),我擔心會出現過擬合,所以在cost function里加了一項正則項。但結果是不加正則項訓練結果很好,一加就出現嚴重的under fitting,不管怎麼調參數都沒用。原來一直聽說CNN的權重共享就相當於自帶某種正則化,現在看起來確實是這樣。

隨機梯度下降(SGD)的參數選擇

最主要的就一個參數,minibatch的大小。在 Deep Learning Toolbox 的demo裏,這個值取的是50。但是我的實驗中,似乎minibatch取1的時候收斂最快。

我的感覺:訓練樣本順序隨機的話,每次產生的梯度也是隨機的,那麼50個樣本平均一下梯度就很小了,也許這樣比較穩健,但收斂會比較慢;相反,每個樣本更新一次梯度就大得多,不過也許會比較盲目。

另外還有一個參數是learning rate。在實驗中,增大minibatch會出現訓不動的情況,這時適當增大learning rate能夠讓training cost繼續下降。

另外Yann LeCun似乎說過每一層應該採取不同的learning rate,不過我沒試。

增加全連接層數後的性能

關於這點我不是很確定,但似乎除了增加訓練時間外沒什麼實際作用……

網絡的改進

自動學習組合係數

爲了打破對稱性,從第一個卷積層到第二個卷積層並不是全鏈接,例如第二層第一個卷積核只卷積第一層的第123個feature map,第二層第二個卷積核只卷積第一層的第345個feature map。在LeNet裏這個組合是人爲規定的,Bouvrie的文章中提出這個組合係數也是可以學出來的。但是我的實驗里人爲規定和自動學習的效果好像差別不大,不知道更復雜的數據集上會怎麼樣。

ReLU的位置

如果網絡的最後一層是softmax分類器的話似乎其前一層就不能用ReLU,因爲ReLU輸出可能相差很大(比如0和幾十),這時再經過softmax就會出現一個節點爲1其它全0的情況。softmax的cost function裏包含一項log(y),如果y正好是0就沒法算了。所以我在倒數第二層還是採用sigmoid。

其他高級玩法

本來還打算玩一些其他更有趣的東西,比如dropout、maxout、max pooling等等。但是時間有限,老闆已經嫌我不務正業了。

總結

這次的收穫:

  1. 寫複雜的算法不能急於求成,要循序漸進
  2. 測試很重要。上Andrew Ng公開課時候覺得gradient check很煩,現在看來簡直是神器
  3. 機器越快越好……


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