1. 程式人生 > >李理:詳解卷積神經網路

李理:詳解卷積神經網路

接下來介紹一種非常重要的神經網路——卷積神經網路。這種神經網路在計算機視覺領域取得了重大的成功,而且在自然語言處理等其它領域也有很好的應用。深度學習受到大家的關注很大一個原因就是Alex等人實現的AlexNet(一種深度卷積神經網路)在LSVRC-2010 ImageNet這個比賽中取得了非常好的成績。此後,卷積神經網路及其變種被廣泛應用於各種影象相關任務。

這裡主要參考了Neural Networks and Deep Learning和cs231n的課程來介紹CNN,兩部分都會有理論和程式碼。前者會用theano來實現,而後者會使用我們前一部分介紹的自動梯度來實現。下面首先介紹Michael Nielsen的部分(其實主要是翻譯,然後加一些我自己的理解)。

前面的話

如果讀者自己嘗試了上一部分的程式碼,調過3層和5層全連線的神經網路的引數,我們會發現神經網路的層數越多,引數(超引數)就越難調。但是如果引數調得好,深的網路的效果確實比較淺的好(這也是為什麼我們要搞深度學習的原因)。所以深度學習有這樣的說法:“三個 bound 不如一個 heuristic,三個 heuristic 不如一個trick”。以前搞機器學習就是feature engineering加調參,現在就剩下調參了。網路的結構,引數的初始化,learning_rate,迭代次數等等都會影響最終的結果。有興趣的同學可以看看Michael Nielsen這個電子書的相應章節,cs231n的

Github資源也有介紹,另外《Neural Networks: Tricks of the Trade》這本書,看名字就知道講啥的了吧。

不過我們還是回到正題“卷積神經網路”吧。

CNN簡介

在之前的章節我們使用了神經網路來解決手寫數字識別(MNIST)的問題。我們使用了全連線的神經網路,也就是前一層的每一個神經元都會連線到後一層的每一個神經元,如果前一層有m個節點,後一層有n個,那麼總共有m*n條邊(連線)。連線方式如下圖所示:

圖片描述

具體來講,對於輸入圖片的每一個畫素,我們把它的灰度值作為對應神經元的輸入。對於28×28的影象來說,我們的網路有784個輸入神經元。然後我們訓練這個網路的weights和biases來使得它可以正確的預測對應的數字。

我們之前設計的神經網路工作的很好:在MNIST手寫識別資料集上我們得到了超過98%的準確率。但是仔細想一想的話,使用全連線的網路來識別影象有一些奇怪。因為這樣的網路結構沒有考慮影象的空間結構。比如,它對於空間上很近或者很遠的畫素一樣的對待。這些空間的概念【比如7字會出現某些畫素在某個水平方向同時灰度值差不多,也就是上面的那一橫】必須靠網路從訓練資料中推測出來【但是如果訓練資料不夠而且影象沒有做居中等歸一化的話,如果訓練資料的7的一橫都出現在影象靠左的地方,而測試資料把7寫到右下角,那麼網路很可能學不到這樣的特徵】。那為什麼我們不能設計一直網路結構考慮這些空間結構呢?這樣的想法就是下面我們要討論的CNN的思想。

這種神經網路利用了空間結構,因此非常適合用來做圖片分類。這種結構訓練也非常的快,因此也可以訓練更“深”的網路。目前,影象識別大都使用深層的卷積神經網路及其變種。

卷積神經網路有3個基本的idea:區域性感知域(Local Recpetive Field),權值共享和池化(Pooling)。下面我們來一個一個的介紹它們。

區域性感知域

在前面圖示的全連線的層裡,輸入是被描述成一列神經元。而在卷積網路裡,我們把輸入看成28×28方格的二維神經元,它的每一個神經元對應於圖片在這個畫素點的強度(灰度值),如下圖所示:

圖片描述

和往常一樣,我們把輸入畫素連線到隱藏層的神經元。但是我們這裡不再把輸入的每一個畫素都連線到隱藏層的每一個神經元。與之不同,我們把很小的相臨近的區域內的輸入連線在一起。

更加具體的來講,隱藏層的每一個神經元都會與輸入層一個很小的區域(比如一個5×5的區域,也就是25個畫素點)相連線。隱藏對於隱藏層的某一個神經元,連線如下圖所示:

圖片描述

輸入影象的這個區域叫做那個隱藏層神經元的區域性感知域。這是輸入畫素的一個小視窗。每個連線都有一個可以學習的權重,此外還有一個bias。你可以把那個神經元想象成用來分析這個區域性感知域的。

我們然後在整個輸入影象上滑動這個區域性感知域。對於每一個區域性感知域,都有一個隱藏層的神經元與之對應。為了具體一點的展示,我們首先從最左上角的區域性感知域開始:

圖片描述

然後我們向右滑動這個區域性感知域:

圖片描述

以此類推,我們可以構建出第一個隱藏層。注意,如果我們的輸入是28×28,並且使用5×5的區域性關注域,那麼隱藏層是24×24。因為我們只能向右和向下移動23個畫素,再往下移動就會移出影象的邊界了。【說明,後面我們會介紹padding和striding,從而讓影象在經過這樣一次卷積處理後尺寸可以不變小】

這裡我們展示了一次向右/下移動一個畫素。事實上,我們也可以使用一次移動不止一個畫素【這個移動的值叫stride】。比如,我們可以一次向右/下移動兩個畫素。在這篇文章裡,我們只使用stride為1來實驗,但是請讀者知道其他人可能會用不同的stride值。

共享權值

之前提到過每一個隱藏層的神經元有一個5×5的權值。這24×24個隱藏層對應的權值是相同的。也就是說,對於隱藏層的第j,k個神經元,輸出如下:

這裡,σ是啟用函式,可以是我們之前提到的sigmoid函式。b是共享的bias,Wl,m 是5×5的共享權值。ax,y 是輸入在x,y的啟用。

【從這個公式可以看出,權值是5×5的矩陣,不同的區域性感知域使用這一個引數矩陣和bias】

這意味著這一個隱藏層的所有神經元都是檢測同一個特徵,只不過它們位於圖片的不同位置而已。比如這組weights和bias是某個區域性感知域學到的用來識別一個垂直的邊。那麼預測的時候不管這條邊在哪個位置,它都會被某個對於的區域性感知域檢測到。更抽象一點,卷積網路能很好的適應圖片的位置變化:把圖片中的貓稍微移動一下位置,它仍然知道這是一隻貓。

因為這個原因,我們有時把輸入層到隱藏層的對映叫做特徵對映(feature map)。我們把定義特徵對映的權重叫做共享的權重(shared weights),bias叫做共享的bias(shared bais)。這組weights和bias定義了一個kernel或者filter。

上面描述的網路結構只能檢測一種區域性的特徵。為了識別圖片,我們需要更多的特徵對映。隱藏一個完整的卷積神經網路會有很多不同的特徵對映:

圖片描述

在上面的例子裡,我們有3個特徵對映。每個對映由一個5×5的weights和一個biase確定。因此這個網路能檢測3種特徵,不管這3個特徵出現在影象的那個區域性感知域裡。

為了簡化,上面之展示了3個特徵對映。在實際使用的卷積神經網路中我們會使用非常多的特徵對映。早期的一個卷積神經網路——LeNet-5,使用了6個特徵對映,每一個都是5×5的區域性感知域,來識別MNIST數字。因此上面的例子和LeNet-5很接近。後面我們開發的卷積層將使用20和40個特徵對映。下面我們先看看模型學習到的一些特徵:

圖片描述

這20個圖片對應了20個不同的特徵對映。每個對映是一個5×5的影象,對應於區域性感知域的5×5個權重。顏色越白(淺)說明權值越小(一般都是負的),因此對應畫素對於識別這個特徵越不重要。顏色越深(黑)說明權值越大,對應的畫素越重要。

那麼我們可以從這些特徵對映裡得出什麼結論呢?很顯然這裡包含了非隨機的空間結構。這說明我們的網路學到了一些空間結構。但是,也很難說它具體學到了哪些特徵。我們學到的不是一個 Gabor濾波器 的。事實上有很多研究工作嘗試理解機器到底學到了什麼樣的特徵。如果你感興趣,可以參考Matthew Zeiler 和 Rob Fergus在2013年的論文 Visualizing and Understanding Convolutional Networks

共享權重和bias的一大好處是它極大的減少了網路的引數數量。對於每一個特徵對映,我們只需要 25=5×5 個權重,再加一個bias。因此一個特徵對映只有26個引數。如果我們有20個特徵對映,那麼只有20×26=520個引數。如果我們使用全連線的神經網路結構,假設隱藏層有30個神經元(這並不算很多),那麼就有784*30個權重引數,再加上30個bias,總共有23,550個引數。換句話說,全連線的網路比卷積網路的引數多了40倍。

當然,我們不能直接比較兩種網路的引數,因為這兩種模型有本質的區別。但是,憑直覺,由於卷積網路有平移不變的特性,為了達到相同的效果,它也可能使用更少的引數。由於引數變少,卷積網路的訓練速度也更快,從而相同的計算資源我們可以訓練更深的網路。

“卷積”神經網路是因為公式(1)裡的運算叫做“卷積運算”。更加具體一點,我們可以把公式(1)裡的求和寫成卷積:$a^1 = \sigma(b + w * a^0)$。*在這裡不是乘法,而是卷積運算。這裡不會討論卷積的細節,所以讀者如果不懂也不要擔心,這裡只不過是為了解釋卷積神經網路這個名字的由來。【建議感興趣的讀者參考colah的部落格文章 《Understanding Convolutions》

池化(Pooling)

除了上面的卷積層,卷積神經網路也包括池化層(pooling layers)。池化層一般都直接放在卷積層後面池化層的目的是簡化從卷積層輸出的資訊。

更具體一點,一個池化層把卷積層的輸出作為其輸入並且輸出一個更緊湊(condensed)的特徵對映。比如,池化層的每一個神經元都提取了之前那個卷積層的一個2×2區域的資訊。更為具體的一個例子,一種非常常見的池化操作叫做Max-pooling。在Max-Pooling中,這個神經元選擇2×2區域裡啟用值最大的值,如下圖所示:

圖片描述

注意卷積層的輸出是24×24的,而池化後是12×12的。

就像上面提到的,卷積層通常會有多個特徵對映。我們會對每一個特徵對映進行max-pooling操作。因此,如果一個卷積層有3個特徵對映,那麼卷積加max-pooling後就如下圖所示:

圖片描述

我們可以把max-pooling看成神經網路關心某個特徵在這個區域裡是否出現。它忽略了這個特徵出現的具體位置。直覺上看,如果某個特徵出現了,那麼這個特徵相對於其它特徵的精確位置是不重要的【精確位置不重要,但是大致的位置是重要的,比如識別一個貓,兩隻眼睛和鼻子有一個大致的相對位置關係,但是在一個2×2的小區域裡稍微移動一下眼睛,應該不太影響我們識別一隻貓,而且它還能解決影象拍攝角度變化,扭曲等問題】。而且一個很大的好處是池化可以減少特徵的個數【2×2的max-pooling讓特徵的大小變為原來的1/4】,因此減少了之後層的引數個數。

Max-pooling不是唯一的池化方法。另外一種常見的是L2 Pooling。這種方法不是取2×2區域的最大值,而是2×2區域的每個值平方然後求和然後取平方根。雖然細節有所不同,但思路和max-pooling是類似的:L2 Pooling也是從卷積層壓縮資訊的一種方法。在實踐中,兩種方法都被廣泛使用。有時人們也使用其它的池化方法。如果你真的想嘗試不同的方法來提供效能,那麼你可以使用validation資料來嘗試不同池化方法然後選擇最合適的方法。但是這裡我們不在討論這些細節。【Max-Pooling是用的最多的,甚至也有人認為Pooling並沒有什麼卵用。深度學習一個問題就是很多經驗的tricks由於沒有太多理論依據,只是因為最早的人用了,而且看起來效果不錯(但可能換一個數據集就不一定了),所以後面的人也跟著用。但是過了沒多久又被認為這個trick其實沒啥用】

放到一起

現在我們可以把這3個idea放到一起來構建一個完整的卷積神經網路了。它和之前我們看到的結構類似,不過增加了一個有10個神經元的輸出層,這個層的每個神經元對應於0-9直接的一個數字:

圖片描述

這個網路的輸入的大小是28×28,每一個輸入對於MNIST影象的一個畫素。然後使用了3個特徵對映,區域性感知域的大小是5×5。這樣得到3×24×24的輸出。然後使用對每一個特徵對映的輸出應用2×2的max-pooling,得到3×12×12的輸出。

最後一層是全連線的網路,3×12×12個神經元會連線到輸出10個神經元中的每一個。這和之前介紹的全連線神經網路是一樣的。

卷積結構和之前的全連線結構有很大的差別。但是整體的圖景是類似的:一個神經網路有很多神經元,它們的行為有weights和biase確定。並且整體的目標也是類似的:使用訓練資料來訓練網路的weights和biases使得網路能夠儘量好的識別圖片。

和之前介紹的一樣,這裡我們仍然使用隨機梯度下降來訓練。不過反向傳播演算法有所不同。原因是之前bp演算法的推導是基於全連線的神經網路。不過幸運的是求卷積和max-pooling的導數是非常簡單的。如果你想了解細節,請自己推導。【這篇文章不會介紹CNN的梯度求解,後面實現使用的是theano,後面介紹CS231N的CNN是會介紹怎麼自己來基於自動求導來求這個梯度,而且還會介紹高效的演算法,感興趣的讀者請持續關注】

CNN實戰

前面我們介紹了CNN的基本理論,但是沒有講怎麼求梯度。這裡的程式碼是用theano來自動求梯度的。我們可以暫時把cnn看出一個黑盒,試試用它來識別MNIST的數字。後面的文章會介紹theano以及怎麼用theano實現CNN。

程式碼

安裝theano

參考這裡 ;如果是ubuntu的系統,可以參考這裡 ;如果您的機器有gpu,請安裝好cuda以及讓theano支援gpu。

預設的network3.py的第52行是 GPU = True,如果您的機器沒有gpu,請把這一行改成GPU = False

baseline

首先我們實現一個baseline的系統,我們構建一個只有一個隱藏層的3層全連線網路,隱藏層100個神經元。我們訓練時60個epoch,使用learning rate $\eta = 0.1$,batch大小是10,沒有正則化:

$cd src
$ipython
>>> import network3
>>> from network3 import Network
>>> from network3 import ConvPoolLayer, FullyConnectedLayer, SoftmaxLayer
>>> training_data, validation_data, test_data = network3.load_data_shared()
>>> mini_batch_size = 10
>>> net = Network([
        FullyConnectedLayer(n_in=784, n_out=100),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(training_data, 60, mini_batch_size, 0.1, 
            validation_data, test_data)

得到的分類準確率是97.8%。這是在test_data上的準確率,這個模型使用訓練資料訓練,並根據validation_data來選擇當前最好的模型。使用validation資料來可以避免過擬合。讀者執行時可能結果會有一些差異,因為模型的引數是隨機初始化的。

改進版本1

我們首先在輸入的後面增加一個卷積層。我們使用5 5的區域性感知域,stride等於1,20個特徵對映。然後接一個2 2的max-pooling層。之後接一個全連線的層,最後是softmax(仿射變換加softmax):

圖片描述

在這種網路結構中,我們可以認為卷積和池化層可以學會輸入圖片的區域性的空間特徵,而全連線的層整合全域性的資訊,學習出更抽象的特徵。這是卷積神經網路的常見結構。

下面是程式碼:

>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28), 
                      filter_shape=(20, 1, 5, 5), 
                      poolsize=(2, 2)),
        FullyConnectedLayer(n_in=20*12*12, n_out=100),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(training_data, 60, mini_batch_size, 0.1, 
            validation_data, test_data)   

【注意圖片的大小,開始是(mini_batch_size, 1, 28 ,28),經過一個20個5 5的卷積池層後變成了(mini_batch_size, 20, 24,24),然後在經過2 2的max-pooling後變成了(mini_batch_size, 20, 12, 12),然後接全連線層的時候可以理解成把所以的特徵對映展開,也就是20 12 12,所以FullyConnectedLayer的n_in是20 12 12】

這個模型得到98.78%的準確率,這相對之前的97.8%是一個很大的提高。事實上我們的錯誤率減少了1/3,這是一個很大的提高。【準確率很高的時候就看錯誤率的減少,這樣比較有成就感,哈哈】

如果要用gpu,可以把上面的命令儲存到一個檔案test.py,然後:

$THEANO_FLAGS=mode=FAST_RUN,device=gpu,floatX=float32 python test.py 

在這個網路結構中,我們吧卷積和池化層看出一個整體。這只是一種習慣。network3.py會把它們當成一個整體,每個卷積層後面都會跟一個池化層。但實際的一些卷積神經網路並不都要接池化層。

改進版本2

我們再加入第二個卷積-池化層。這個卷積層插入在第一個卷積層和全連線層中間。我們使用同樣的5×5的區域性感知域和2×2的max-pooling。程式碼如下:

>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28), 
                      filter_shape=(20, 1, 5, 5), 
                      poolsize=(2, 2)),
        ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12), 
                      filter_shape=(40, 20, 5, 5), 
                      poolsize=(2, 2)),
        FullyConnectedLayer(n_in=40*4*4, n_out=100),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(training_data, 60, mini_batch_size, 0.1, 
            validation_data, test_data) 

【注意圖片的大小,開始是(mini_batch_size, 1, 28 ,28),經過一個20個5 5的卷積池層後變成了(mini_batch_size, 20, 24,24),然後在經過2 2的max-pooling後變成了(mini_batch_size, 20, 12, 12)。然後是40個5*5的卷積層,變成了(mini_batch_size, 40, 8, 8),然後是max-pooling得到(mini_batch_size, 40, 4, 4)。然後是全連線的層】
這個模型得到99.6%的準確率!

這裡有兩個很自然的問題。第一個是:加第二個卷積-池化層有什麼意義呢?事實上,你可以認為第二個卷積層的輸入是12*12的”圖片“,它的”畫素“代表某個區域性特徵。【比如你可以認為第一個卷積層識別眼睛鼻子,而第二個卷積層識別臉,不同生物的臉上面鼻子和眼睛的相對位置是有意義的】

這是個看起來不錯的解釋,那麼第二個問題來了:第一個卷積層的輸出是不同的20個不同的區域性特徵,因此第二個卷積層的輸入是20 12 12。這就像我們輸入了20個不同的”圖片“,而不是一個”圖片“。那第二個卷積層的神經元學到的是什麼呢?【如果第一層的卷積網路能識別”眼睛“,”鼻子“,”耳朵“。那麼第二層的”臉“就是2個眼睛,2個耳朵,1個鼻子,並且它們滿足一定的空間約束。所以第二層的每一個神經元需要連線第一層的每一個輸出,如果第二層只連線”眼睛“這個特徵對映,那麼只能學習出2個眼睛,3個眼睛這樣的特徵,那就沒有什麼用處了】

改進版本3

使用ReLU啟用函式。ReLU的定義是:

>>> from network3 import ReLU
>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28), 
                      filter_shape=(20, 1, 5, 5), 
                      poolsize=(2, 2), 
                      activation_fn=ReLU),
        ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12), 
                      filter_shape=(40, 20, 5, 5), 
                      poolsize=(2, 2), 
                      activation_fn=ReLU),
        FullyConnectedLayer(n_in=40*4*4, n_out=100, activation_fn=ReLU),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(training_data, 60, mini_batch_size, 0.03, 
            validation_data, test_data, lmbda=0.1)

使用ReLU後準確率從99.06%提高到99.23%。從作者的經驗來看,ReLU總是要比sigmoid啟用函式要好。

但為什麼ReLU就比sigmoid或者tanh要好呢?目前並沒有很好的理論介紹。ReLU只是在最近幾年開始流行起來的。為什麼流行的原因是經驗:有一些人嘗試了ReLU,然後在他們的任務裡取得了比sigmoid好的結果,然後其他人也就跟風。理論上沒有人證明ReLU是更好的啟用函式。【所以說深度學習有很多tricks,可能某幾年就流行起來了,但過幾年又有人認為這些tricks沒有意義。比如最早的pretraining,現在幾乎沒人用了。】

改進版本4

擴充套件資料。

深度學習非常依賴於資料。我們可以根據任務的特點”構造“新的資料。一種簡單的方法是把訓練資料裡的數字進行一下平移,旋轉等變換。雖然理論上卷積神經網路能學到與位置無關的特徵,但如果訓練資料裡數字總是出現在固定的位置,實際的模型也不一定能學到。所以我們構造一些這樣的資料效果會更好。

$ python expand_mnist.py

expand_mnist.py這個指令碼就會擴充套件資料。它只是簡單的把圖片向上下左右各移動了一個畫素。擴充套件後訓練資料從50000個變成了250000個。

接下來我們用擴充套件後的資料來訓練模型:

>>> expanded_training_data, _, _ = network3.load_data_shared(
        "../data/mnist_expanded.pkl.gz")
>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28), 
                      filter_shape=(20, 1, 5, 5), 
                      poolsize=(2, 2), 
                      activation_fn=ReLU),
        ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12), 
                      filter_shape=(40, 20, 5, 5), 
                      poolsize=(2, 2), 
                      activation_fn=ReLU),
        FullyConnectedLayer(n_in=40*4*4, n_out=100, activation_fn=ReLU),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(expanded_training_data, 60, mini_batch_size, 0.03, 
            validation_data, test_data, lmbda=0.1)

這個模型的準確率是99.37%。擴充套件資料看起來非常trival,但是卻極大的提高了識別準確率。

改進版本5

接下來還有改進的辦法嗎?我們的全連線層只有100個神經元,增加神經元有幫助嗎? 作者嘗試了300和1000個神經元的全連線層,得到了99.46%和99.43%的準確率。相對於99.37%並沒有本質的提高。

那再加一個全連線的層有幫助嗎?我們了嘗試一下:

>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28), 
                      filter_shape=(20, 1, 5, 5), 
                      poolsize=(2, 2), 
                      activation_fn=ReLU),
        ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12), 
                      filter_shape=(40, 20, 5, 5), 
                      poolsize=(2, 2), 
                      activation_fn=ReLU),
        FullyConnectedLayer(n_in=40*4*4, n_out=100, activation_fn=ReLU),
        FullyConnectedLayer(n_in=100, n_out=100, activation_fn=ReLU),
        SoftmaxLayer(n_in=100, n_out=10)], mini_batch_size)
>>> net.SGD(expanded_training_data, 60, mini_batch_size, 0.03, 
            validation_data, test_data, lmbda=0.1)

在第一個全連線的層之後有加了一個100個神經元的全連線層。得到的準確率是99.43%,把這一層的神經元個數從100增加到300個和1000個得到的準確率是99.48 %和99.47%。有一些提高但是也不明顯。

為什麼增加更多層提高不多呢,按說它的表達能力變強了,可能的原因是過擬合。那怎麼解決過擬合呢?一種方法就是dropout。drop的詳細解釋請參考這裡。簡單來說,dropout就是在訓練的時候隨機的讓一些神經元的啟用“丟失”,這樣網路就能學到更加魯棒的特徵,因為它要求某些神經元”失效“的情況下網路仍然能工作,因此就不會那麼依賴某一些神經元,而是每個神經元都有貢獻。

下面是在兩個全連線層都加入50%的dropout:

>>> net = Network([
        ConvPoolLayer(image_shape=(mini_batch_size, 1, 28, 28), 
                      filter_shape=(20, 1, 5, 5), 
                      poolsize=(2, 2), 
                      activation_fn=ReLU),
        ConvPoolLayer(image_shape=(mini_batch_size, 20, 12, 12), 
                      filter_shape=(40, 20, 5, 5), 
                      poolsize=(2, 2), 
                      activation_fn=ReLU),
        FullyConnectedLayer(
            n_in=40*4*4, n_out=1000, activation_fn=ReLU, p_dropout=0.5),
        FullyConnectedLayer(
            n_in=1000, n_out=1000, activation_fn=ReLU, p_dropout=0.5),
        SoftmaxLayer(n_in=1000, n_out=10, p_dropout=0.5)], 
        mini_batch_size)
>>> net.SGD(expanded_training_data, 40, mini_batch_size, 0.03, 
            validation_data, test_data)

使用dropout後,我們得到了99.60%的一個模型。

這裡有兩點值得注意:

  1. 訓練的epoch變成了40.因為dropout減少了過擬合,所以我們不需要60個epoch。
  2. 全連線層使用了1000個神經元。因為dropout會丟棄50%的神經元,所以從直覺來看1000個神經元也相當於只有500個。如果過用100個神經元感覺太少了點。作者經過驗證發現有了dropout用1000個比300個的效果好。

改進版本6

ensemble多個神經網路。作者分別訓練了5個神經網路,每一個都達到了99.6%的準確率,然後用它們來投票,得到了99.67%準確率的模型。

這是一個非常不錯的模型了,10000個測試資料只有33個是錯誤的,我們把錯誤的圖片都列舉了出來:

圖片描述

圖片的右上角是正確的分類,右下角是模型的分類。可以發現有些錯誤可能人也會犯,因為有些數字人也很難分清楚。

【為什麼只對全連線的層使用dropout?】

如果讀者仔細的閱讀程式碼,你會發現我們只對全連線層進行了dropout,而卷積層沒有。當然我們也可以對卷積層進行dropout。但是沒有必要。因為卷積層本身就有防止過擬合的能力。原因是權值共享強制網路學到的特徵是能夠應用到任何位置的特徵。這讓它不太容易學習到特別區域性的特徵。因此也就沒有必要對它進行的dropout了。

更進一步

感興趣的讀者可以參考這裡,列舉了MNIST資料集的最好結果以及對應的論文。目前最好的結果是99.79%

What’s Next?

接下來的文章會介紹theano,一個非常流行的深度學習框架,然後會講解network3.py,也就是怎麼用theano實現CNN。敬請關注。

本系列文章也將在CSDN人工智慧公眾號AI_Thinker中進行連載,掃描下方二維碼即可關注。

圖片描述