本文是對劉昕博士的《CNN的近期進展與實用技巧》的一個擴充性資料。
主要討論CNN的發展,并且引用劉昕博士的思路,對CNN的發展作一個更加詳細的介紹,將按下圖的CNN發展史進行描述:
上圖所示是劉昕博士總結的CNN結構演化的歷史,起點是神經認知機模型,此時已經出現了卷積結構,經典的LeNet誕生于1998年。然而之后CNN的鋒芒開始被SVM等手工設計的特征蓋過。隨著ReLU和dropout的提出,以及GPU和大數據帶來的歷史機遇,CNN在2012年迎來了歷史突破–AlexNet.
CNN的演化路徑可以總結為以下幾個方向:
從LeNet到AlexNet
進化之路一:網絡結構加深
進化之路二:加強卷積功能
進化之路三:從分類到檢測
進化之路四:新增功能模塊
本文將對CNN發展的四條路徑中最具代表性的CNN模型結構進行講解。
一切的開始( LeNet)
下圖是廣為流傳LeNet的網絡結構,麻雀雖小,但五臟俱全,卷積層、pooling層、全連接層,這些都是現代CNN網絡的基本組件。
- 輸入尺寸:32*32
- 卷積層:3個
- 降采樣層:2個
- 全連接層:1個
- 輸出:10個類別(數字0-9的概率)
因為LeNet可以說是CNN的開端,所以這里簡單介紹一下各個組件的用途與意義。
Input (32*32)
輸入圖像Size為32*32。這要比mnist數據庫中最大的字母(28*28)還大。這樣做的目的是希望潛在的明顯特征,如筆畫斷續、角點能夠出現在最高層特征監測子感受野的中心。
C1, C3, C5 (卷積層)
卷積核在二維平面上平移,并且卷積核的每個元素與被卷積圖像對應位置相乘,再求和。通過卷積核的不斷移動,我們就有了一個新的圖像,這個圖像完全由卷積核在各個位置時的乘積求和的結果組成。
二維卷積在圖像中的效果就是: 對圖像的每個像素的鄰域(鄰域大小就是核的大小)加權求和得到該像素點的輸出值。具體做法如下:
卷積運算一個重要的特點就是: 通過卷積運算,可以使原信號特征增強,并且降低噪音。
不同的卷積核能夠提取到圖像中的不同特征,這里有 在線demo,下面是不同卷積核得到的不同的feature map,
以C1層進行說明:C1層是一個卷積層,有6個卷積核(提取6種局部特征),核大小為5*5,能夠輸出6個特征圖Feature Map,大小為28*28。C1有156個可訓練參數(每個濾波器5*5=25個unit參數和一個bias參數,一共6個濾波器,共(5*5+1)*6 = 156個參數),共156 *(28*28) = 122,304個連接。
S2, S4 (pooling層)
S2, S4是下采樣層,是為了降低網絡訓練參數及模型的過擬合程度。池化/采樣的方式通常有以下兩種:
Max-Pooling:選擇Pooling窗口中的最大值作為采樣值;
Mean-Pooling:將Pooling窗口中的所有值相加取平均,以平均值作為采樣值;
S2層是6個14*14的feature map,map中的每一個單元于上一層的 2*2 領域相連接,所以,S2層是C1層的1/4。
F6 (全連接層)
F6是全連接層,類似MLP中的一個layer,共有84個神經元(為什么選這個數字?跟輸出層有關),這84個神經元與C5層進行全連接,所以需要訓練的參數是:(120+1)*84 = 10164. 如同經典神經網絡,F6層計算輸入向量和權重向量之間的點積,再加上一個偏置。然后將其傳遞給sigmoid函數產生單元i的一個狀態。
Output (輸出層)
輸出層由歐式徑向基函數(Euclidean Radial Basis Function)單元組成,每類一個單元,每個有84個輸入。 換句話說,每個輸出RBF單元計算輸入向量和參數向量之間的歐式距離。輸入離參數向量越遠,RBF輸出的越大。用概率術語來說,RBF輸出可以被理解為F6層配置空間的高斯分布的負log-likelihood。給定一個輸式,損失函數應能使得F6的配置與RBF參數向量(即模式的期望分類)足夠接近。
王者回歸(AlexNet)
AlexNet 可以說是具有歷史意義的一個網絡結構,可以說在AlexNet之前,深度學習已經沉寂了很久。歷史的轉折在2012年到來,AlexNet 在當年的ImageNet圖像分類競賽中,top-5錯誤率比上一年的冠軍下降了十個百分點,而且遠遠超過當年的第二名。
AlexNet 之所以能夠成功,深度學習之所以能夠重回歷史舞臺,原因在于:
- 非線性激活函數:ReLU
- 防止過擬合的方法:Dropout,Data augmentation
- 大數據訓練:百萬級ImageNet圖像數據
- 其他:GPU實現,LRN歸一化層的使用
下面簡單介紹一下AlexNet的一些細節:
Data augmentation
有一種觀點認為神經網絡是靠數據喂出來的,若增加訓練數據,則能夠提升算法的準確率,因為這樣可以避免過擬合,而避免了過擬合你就可以增大你的網絡結構了。當訓練數據有限的時候,可以通過一些變換來從已有的訓練數據集中生成一些新的數據,來擴大訓練數據的size。
其中,最簡單、通用的圖像數據變形的方式:
- 從原始圖像(256,256)中,隨機的crop出一些圖像(224,224)。【平移變換,crop】
- 水平翻轉圖像。【反射變換,flip】
- 給圖像增加一些隨機的光照。【光照、彩色變換,color jittering】
AlexNet 訓練的時候,在data augmentation上處理的很好:
- 隨機crop。訓練時候,對于256*256的圖片進行隨機crop到224*224,然后允許水平翻轉,那么相當與將樣本倍增到((256-224)^2)*2=2048。
- 測試時候,對左上、右上、左下、右下、中間做了5次crop,然后翻轉,共10個crop,之后對結果求平均。作者說,不做隨機crop,大網絡基本都過擬合(under substantial overfitting)。
- 對RGB空間做PCA,然后對主成分做一個(0, 0.1)的高斯擾動。結果讓錯誤率又下降了1%。
ReLU 激活函數
Sigmoid 是常用的非線性的激活函數,它能夠把輸入的連續實值“壓縮”到0和1之間。特別的,如果是非常大的負數,那么輸出就是0;如果是非常大的正數,輸出就是1.
但是它有一些致命的 缺點:
- Sigmoids saturate and kill gradients. sigmoid 有一個非常致命的缺點,當輸入非常大或者非常小的時候,會有飽和現象,這些神經元的梯度是接近于0的。如果你的初始值很大的話,梯度在反向傳播的時候因為需要乘上一個sigmoid 的導數,所以會使得梯度越來越小,這會導致網絡變的很難學習。
- Sigmoid 的 output 不是0均值. 這是不可取的,因為這會導致后一層的神經元將得到上一層輸出的非0均值的信號作為輸入。 產生的一個結果就是:如果數據進入神經元的時候是正的(e.g. x>0 elementwise in f=wTx+b),那么 w計算出的梯度也會始終都是正的。 當然了,如果你是按batch去訓練,那么那個batch可能得到不同的信號,所以這個問題還是可以緩解一下的。因此,非0均值這個問題雖然會產生一些不好的影響,不過跟上面提到的 kill gradients 問題相比還是要好很多的。
ReLU 的數學表達式如下:
![][01]
[01]:http://latex.codecogs.com/png.latex?f(x)%20=%20max(0,%20x)
很顯然,從圖左可以看出,輸入信號<0時,輸出都是0,>0的情況下,輸出等于輸入。w是二維的情況下,使用ReLU之后的效果如下:
Alex用ReLU代替了Sigmoid,發現使用 ReLU 得到的SGD的收斂速度會比 sigmoid/tanh 快很多。
主要是因為它是linear,而且 non-saturating(因為ReLU的導數始終是1),相比于 sigmoid/tanh,ReLU 只需要一個閾值就可以得到激活值,而不用去算一大堆復雜的運算。
關于激活函數更多內容,可以參考文章:神經網絡-激活函數面面觀。
Dropout
結合預先訓練好的許多不同模型,來進行預測是一種非常成功的減少測試誤差的方式(Ensemble)。但因為每個模型的訓練都需要花了好幾天時間,因此這種做法對于大型神經網絡來說太過昂貴。
然而,AlexNet 提出了一個非常有效的模型組合版本,它在訓練中只需要花費兩倍于單模型的時間。這種技術叫做Dropout,它做的就是以0.5的概率,將每個隱層神經元的輸出設置為零。以這種方式“dropped out”的神經元既不參與前向傳播,也不參與反向傳播。
所以每次輸入一個樣本,就相當于該神經網絡就嘗試了一個新的結構,但是所有這些結構之間共享權重。因為神經元不能依賴于其他特定神經元而存在,所以這種技術降低了神經元復雜的互適應關系。
正因如此,網絡需要被迫學習更為魯棒的特征,這些特征在結合其他神經元的一些不同隨機子集時有用。在測試時,我們將所有神經元的輸出都僅僅只乘以0.5,對于獲取指數級dropout網絡產生的預測分布的幾何平均值,這是一個合理的近似方法。
多GPU訓練
單個GTX 580 GPU只有3GB內存,這限制了在其上訓練的網絡的最大規模。因此他們將網絡分布在兩個GPU上。 目前的GPU特別適合跨GPU并行化,因為它們能夠直接從另一個GPU的內存中讀出和寫入,不需要通過主機內存。
他們采用的并行方案是:在每個GPU中放置一半核(或神經元),還有一個額外的技巧:GPU間的通訊只在某些層進行。
例如,第3層的核需要從第2層中所有核映射輸入。然而,第4層的核只需要從第3層中位于同一GPU的那些核映射輸入。
Local Responce Normalization
一句話概括:本質上,這個層也是為了防止激活函數的飽和的。
個人理解原理是通過正則化讓激活函數的輸入靠近“碗”的中間(避免飽和),從而獲得比較大的導數值。
所以從功能上說,跟ReLU是重復的。
不過作者說,從試驗結果看,LRN操作可以提高網絡的泛化能力,將錯誤率降低了大約1個百分點。
AlexNet 優勢在于:網絡增大(5個卷積層+3個全連接層+1個softmax層),同時解決過擬合(dropout,data augmentation,LRN),并且利用多GPU加速計算
網絡結構
在imagenet上的圖像分類challenge上Alex提出的alexnet網絡結構模型贏得了2012屆的冠軍。要研究CNN類型DL網絡模型在圖像分類上的應用,就逃不開研究alexnet,這是CNN在圖像分類上的經典模型(DL火起來之后)。在DL開源實現caffe的model樣例中,它也給出了alexnet的復現,具體網絡配置文件如下:
https://github.com/BVLC/caffe/blob/master/models/bvlc_reference_caffenet/train_val.prototxt
接下來將一步步對該網絡配置結構中各個層進行詳細的解讀(訓練階段):
1. conv1階段DFD(data flow diagram):
2. conv2階段DFD(data flow diagram):
3. conv3階段DFD(data flow diagram):
4. conv4階段DFD(data flow diagram):
5. conv5階段DFD(data flow diagram):
6. fc6階段DFD(data flow diagram):
7. fc7階段DFD(data flow diagram):
8. fc8階段DFD(data flow diagram):
各種layer的operation更多解釋可以參考http://caffe.berkeleyvision.org/tutorial/layers.html
從計算該模型的數據流過程中,該模型參數大概5kw+。
caffe的輸出中也有包含這塊的內容日志,詳情如下:
I0721 10:38:15.326920 4692 net.cpp:125] Top shape: 256 3 227 227 (39574272)
I0721 10:38:15.326971 4692 net.cpp:125] Top shape: 256 1 1 1 (256)
I0721 10:38:15.326982 4692 net.cpp:156] data does not need backward computation.
I0721 10:38:15.327003 4692 net.cpp:74] Creating Layer conv1
I0721 10:38:15.327011 4692 net.cpp:84] conv1 <- data
I0721 10:38:15.327033 4692 net.cpp:110] conv1 -> conv1
I0721 10:38:16.721956 4692 net.cpp:125] Top shape: 256 96 55 55 (74342400)
I0721 10:38:16.722030 4692 net.cpp:151] conv1 needs backward computation.
I0721 10:38:16.722059 4692 net.cpp:74] Creating Layer relu1
I0721 10:38:16.722070 4692 net.cpp:84] relu1 <- conv1
I0721 10:38:16.722082 4692 net.cpp:98] relu1 -> conv1 (in-place)
I0721 10:38:16.722096 4692 net.cpp:125] Top shape: 256 96 55 55 (74342400)
I0721 10:38:16.722105 4692 net.cpp:151] relu1 needs backward computation.
I0721 10:38:16.722116 4692 net.cpp:74] Creating Layer pool1
I0721 10:38:16.722125 4692 net.cpp:84] pool1 <- conv1
I0721 10:38:16.722133 4692 net.cpp:110] pool1 -> pool1
I0721 10:38:16.722167 4692 net.cpp:125] Top shape: 256 96 27 27 (17915904)
I0721 10:38:16.722187 4692 net.cpp:151] pool1 needs backward computation.
I0721 10:38:16.722205 4692 net.cpp:74] Creating Layer norm1
I0721 10:38:16.722221 4692 net.cpp:84] norm1 <- pool1
I0721 10:38:16.722234 4692 net.cpp:110] norm1 -> norm1
I0721 10:38:16.722251 4692 net.cpp:125] Top shape: 256 96 27 27 (17915904)
I0721 10:38:16.722260 4692 net.cpp:151] norm1 needs backward computation.
I0721 10:38:16.722272 4692 net.cpp:74] Creating Layer conv2
I0721 10:38:16.722280 4692 net.cpp:84] conv2 <- norm1
I0721 10:38:16.722290 4692 net.cpp:110] conv2 -> conv2
I0721 10:38:16.725225 4692 net.cpp:125] Top shape: 256 256 27 27 (47775744)
I0721 10:38:16.725242 4692 net.cpp:151] conv2 needs backward computation.
I0721 10:38:16.725253 4692 net.cpp:74] Creating Layer relu2
I0721 10:38:16.725261 4692 net.cpp:84] relu2 <- conv2
I0721 10:38:16.725270 4692 net.cpp:98] relu2 -> conv2 (in-place)
I0721 10:38:16.725280 4692 net.cpp:125] Top shape: 256 256 27 27 (47775744)
I0721 10:38:16.725288 4692 net.cpp:151] relu2 needs backward computation.
I0721 10:38:16.725298 4692 net.cpp:74] Creating Layer pool2
I0721 10:38:16.725307 4692 net.cpp:84] pool2 <- conv2
I0721 10:38:16.725317 4692 net.cpp:110] pool2 -> pool2
I0721 10:38:16.725329 4692 net.cpp:125] Top shape: 256 256 13 13 (11075584)
I0721 10:38:16.725338 4692 net.cpp:151] pool2 needs backward computation.
I0721 10:38:16.725358 4692 net.cpp:74] Creating Layer norm2
I0721 10:38:16.725368 4692 net.cpp:84] norm2 <- pool2
I0721 10:38:16.725378 4692 net.cpp:110] norm2 -> norm2
I0721 10:38:16.725389 4692 net.cpp:125] Top shape: 256 256 13 13 (11075584)
I0721 10:38:16.725399 4692 net.cpp:151] norm2 needs backward computation.
I0721 10:38:16.725409 4692 net.cpp:74] Creating Layer conv3
I0721 10:38:16.725419 4692 net.cpp:84] conv3 <- norm2
I0721 10:38:16.725427 4692 net.cpp:110] conv3 -> conv3
I0721 10:38:16.735193 4692 net.cpp:125] Top shape: 256 384 13 13 (16613376)
I0721 10:38:16.735213 4692 net.cpp:151] conv3 needs backward computation.
I0721 10:38:16.735224 4692 net.cpp:74] Creating Layer relu3
I0721 10:38:16.735234 4692 net.cpp:84] relu3 <- conv3
I0721 10:38:16.735242 4692 net.cpp:98] relu3 -> conv3 (in-place)
I0721 10:38:16.735250 4692 net.cpp:125] Top shape: 256 384 13 13 (16613376)
I0721 10:38:16.735258 4692 net.cpp:151] relu3 needs backward computation.
I0721 10:38:16.735302 4692 net.cpp:74] Creating Layer conv4
I0721 10:38:16.735312 4692 net.cpp:84] conv4 <- conv3
I0721 10:38:16.735321 4692 net.cpp:110] conv4 -> conv4
I0721 10:38:16.743952 4692 net.cpp:125] Top shape: 256 384 13 13 (16613376)
I0721 10:38:16.743988 4692 net.cpp:151] conv4 needs backward computation.
I0721 10:38:16.744000 4692 net.cpp:74] Creating Layer relu4
I0721 10:38:16.744010 4692 net.cpp:84] relu4 <- conv4
I0721 10:38:16.744020 4692 net.cpp:98] relu4 -> conv4 (in-place)
I0721 10:38:16.744030 4692 net.cpp:125] Top shape: 256 384 13 13 (16613376)
I0721 10:38:16.744038 4692 net.cpp:151] relu4 needs backward computation.
I0721 10:38:16.744050 4692 net.cpp:74] Creating Layer conv5
I0721 10:38:16.744057 4692 net.cpp:84] conv5 <- conv4
I0721 10:38:16.744067 4692 net.cpp:110] conv5 -> conv5
I0721 10:38:16.748935 4692 net.cpp:125] Top shape: 256 256 13 13 (11075584)
I0721 10:38:16.748955 4692 net.cpp:151] conv5 needs backward computation.
I0721 10:38:16.748965 4692 net.cpp:74] Creating Layer relu5
I0721 10:38:16.748975 4692 net.cpp:84] relu5 <- conv5
I0721 10:38:16.748983 4692 net.cpp:98] relu5 -> conv5 (in-place)
I0721 10:38:16.748998 4692 net.cpp:125] Top shape: 256 256 13 13 (11075584)
I0721 10:38:16.749011 4692 net.cpp:151] relu5 needs backward computation.
I0721 10:38:16.749022 4692 net.cpp:74] Creating Layer pool5
I0721 10:38:16.749030 4692 net.cpp:84] pool5 <- conv5
I0721 10:38:16.749039 4692 net.cpp:110] pool5 -> pool5
I0721 10:38:16.749050 4692 net.cpp:125] Top shape: 256 256 6 6 (2359296)
I0721 10:38:16.749058 4692 net.cpp:151] pool5 needs backward computation.
I0721 10:38:16.749074 4692 net.cpp:74] Creating Layer fc6
I0721 10:38:16.749083 4692 net.cpp:84] fc6 <- pool5
I0721 10:38:16.749091 4692 net.cpp:110] fc6 -> fc6
I0721 10:38:17.160079 4692 net.cpp:125] Top shape: 256 4096 1 1 (1048576)
I0721 10:38:17.160148 4692 net.cpp:151] fc6 needs backward computation.
I0721 10:38:17.160166 4692 net.cpp:74] Creating Layer relu6
I0721 10:38:17.160177 4692 net.cpp:84] relu6 <- fc6
I0721 10:38:17.160190 4692 net.cpp:98] relu6 -> fc6 (in-place)
I0721 10:38:17.160202 4692 net.cpp:125] Top shape: 256 4096 1 1 (1048576)
I0721 10:38:17.160212 4692 net.cpp:151] relu6 needs backward computation.
I0721 10:38:17.160222 4692 net.cpp:74] Creating Layer drop6
I0721 10:38:17.160230 4692 net.cpp:84] drop6 <- fc6
I0721 10:38:17.160238 4692 net.cpp:98] drop6 -> fc6 (in-place)
I0721 10:38:17.160258 4692 net.cpp:125] Top shape: 256 4096 1 1 (1048576)
I0721 10:38:17.160265 4692 net.cpp:151] drop6 needs backward computation.
I0721 10:38:17.160277 4692 net.cpp:74] Creating Layer fc7
I0721 10:38:17.160286 4692 net.cpp:84] fc7 <- fc6
I0721 10:38:17.160295 4692 net.cpp:110] fc7 -> fc7
I0721 10:38:17.342094 4692 net.cpp:125] Top shape: 256 4096 1 1 (1048576)
I0721 10:38:17.342157 4692 net.cpp:151] fc7 needs backward computation.
I0721 10:38:17.342175 4692 net.cpp:74] Creating Layer relu7
I0721 10:38:17.342185 4692 net.cpp:84] relu7 <- fc7
I0721 10:38:17.342198 4692 net.cpp:98] relu7 -> fc7 (in-place)
I0721 10:38:17.342208 4692 net.cpp:125] Top shape: 256 4096 1 1 (1048576)
I0721 10:38:17.342217 4692 net.cpp:151] relu7 needs backward computation.
I0721 10:38:17.342228 4692 net.cpp:74] Creating Layer drop7
I0721 10:38:17.342236 4692 net.cpp:84] drop7 <- fc7
I0721 10:38:17.342245 4692 net.cpp:98] drop7 -> fc7 (in-place)
I0721 10:38:17.342254 4692 net.cpp:125] Top shape: 256 4096 1 1 (1048576)
I0721 10:38:17.342262 4692 net.cpp:151] drop7 needs backward computation.
I0721 10:38:17.342274 4692 net.cpp:74] Creating Layer fc8
I0721 10:38:17.342283 4692 net.cpp:84] fc8 <- fc7
I0721 10:38:17.342291 4692 net.cpp:110] fc8 -> fc8
I0721 10:38:17.343199 4692 net.cpp:125] Top shape: 256 22 1 1 (5632)
I0721 10:38:17.343214 4692 net.cpp:151] fc8 needs backward computation.
I0721 10:38:17.343231 4692 net.cpp:74] Creating Layer loss
I0721 10:38:17.343240 4692 net.cpp:84] loss <- fc8
I0721 10:38:17.343250 4692 net.cpp:84] loss <- label
I0721 10:38:17.343264 4692 net.cpp:151] loss needs backward computation.
I0721 10:38:17.343305 4692 net.cpp:173] Collecting Learning Rate and Weight Decay.
I0721 10:38:17.343327 4692 net.cpp:166] Network initialization done.
I0721 10:38:17.343335 4692 net.cpp:167] Memory required for Data 1073760256
Refenence
[1] 【卷積神經網絡-進化史】從LeNet到AlexNet - csdn 仙道菜
[2] http://m.blog.csdn.net/article/details?id=51440344
[3] [caffe]深度學習之圖像分類模型AlexNet解讀
(轉載請注明出處,謝謝!)