李理:自動梯度求解——使用自動求導實現(xiàn)多層神經(jīng)網(wǎng)絡

本系列文章面向深度學習研發(fā)者,希望通過Image Caption Generation,一個有意思的具體任務,深入淺出地介紹深度學習的知識。本系列文章涉及到很多深度學習流行的模型,如CNN,RNN/LSTM,Attention等。本文為第6篇。

作者:李理

目前就職于環(huán)信,即時通訊云平臺和全媒體智能客服平臺,在環(huán)信從事智能客服和智能機器人相關工作,致力于用深度學習來提高智能機器人的性能。

相關文章:

李理:從Image Caption Generation理解深度學習(part I)

李理:從Image Caption Generation理解深度學習(part II)

李理:從Image Caption Generation理解深度學習(part III)

李理:自動梯度求解 反向傳播算法的另外一種視角

李理:自動梯度求解——cs231n的notes

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

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

前面的話

如果讀者自己嘗試了上一部分的代碼,調過3層和5層全連接的神經(jīng)網(wǎng)絡的參數(shù),我們會發(fā)現(xiàn)神經(jīng)網(wǎng)絡的層數(shù)越多,參數(shù)(超參數(shù))就越難調。但是如果參數(shù)調得好,深的網(wǎng)絡的效果確實比較淺的好(這也是為什么我們要搞深度學習的原因)。所以深度學習有這樣的說法:“三個 bound 不如一個 heuristic,三個 heuristic 不如一個trick”。以前搞機器學習就是feature engineering加調參,現(xiàn)在就剩下調參了。網(wǎng)絡的結構,參數(shù)的初始化,learning_rate,迭代次數(shù)等等都會影響最終的結果。有興趣的同學可以看看Michael Nielsen這個電子書的相應章節(jié),cs231n的Github資源也有介紹,另外《Neural Networks: Tricks of the Trade》這本書,看名字就知道講啥的了吧。

不過我們還是回到正題“卷積神經(jīng)網(wǎng)絡”吧。

CNN簡介

在之前的章節(jié)我們使用了神經(jīng)網(wǎng)絡來解決手寫數(shù)字識別(MNIST)的問題。我們使用了全連接的神經(jīng)網(wǎng)絡,也就是前一層的每一個神經(jīng)元都會連接到后一層的每一個神經(jīng)元,如果前一層有m個節(jié)點,后一層有n個,那么總共有m*n條邊(連接)。連接方式如下圖所示:

具體來講,對于輸入圖片的每一個像素,我們把它的灰度值作為對應神經(jīng)元的輸入。對于28×28的圖像來說,我們的網(wǎng)絡有784個輸入神經(jīng)元。然后我們訓練這個網(wǎng)絡的weights和biases來使得它可以正確的預測對應的數(shù)字。

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

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

卷積神經(jīng)網(wǎng)絡有3個基本的idea:局部感知域(Local Recpetive Field),權值共享和池化(Pooling)。下面我們來一個一個的介紹它們。

局部感知域

在前面圖示的全連接的層里,輸入是被描述成一列神經(jīng)元。而在卷積網(wǎng)絡里,我們把輸入看成28×28方格的二維神經(jīng)元,它的每一個神經(jīng)元對應于圖片在這個像素點的強度(灰度值),如下圖所示:

和往常一樣,我們把輸入像素連接到隱藏層的神經(jīng)元。但是我們這里不再把輸入的每一個像素都連接到隱藏層的每一個神經(jīng)元。與之不同,我們把很小的相臨近的區(qū)域內的輸入連接在一起。

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

輸入圖像的這個區(qū)域叫做那個隱藏層神經(jīng)元的局部感知域。這是輸入像素的一個小窗口。每個連接都有一個可以學習的權重,此外還有一個bias。你可以把那個神經(jīng)元想象成用來分析這個局部感知域的。

我們然后在整個輸入圖像上滑動這個局部感知域。對于每一個局部感知域,都有一個隱藏層的神經(jīng)元與之對應。為了具體一點的展示,我們首先從最左上角的局部感知域開始:

然后我們向右滑動這個局部感知域:

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

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

共享權值

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

σ(b+∑l=04∑m=04wl,maj+l,k+m)

這里,σ是激活函數(shù),可以是我們之前提到的sigmoid函數(shù)。b是共享的bias,Wl,m?是5×5的共享權值。ax,y?是輸入在x,y的激活。

【從這個公式可以看出,權值是5×5的矩陣,不同的局部感知域使用這一個參數(shù)矩陣和bias】

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

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

上面描述的網(wǎng)絡結構只能檢測一種局部的特征。為了識別圖片,我們需要更多的特征映射。隱藏一個完整的卷積神經(jīng)網(wǎng)絡會有很多不同的特征映射:

在上面的例子里,我們有3個特征映射。每個映射由一個5×5的weights和一個biase確定。因此這個網(wǎng)絡能檢測3種特征,不管這3個特征出現(xiàn)在圖像的那個局部感知域里。

為了簡化,上面之展示了3個特征映射。在實際使用的卷積神經(jīng)網(wǎng)絡中我們會使用非常多的特征映射。早期的一個卷積神經(jīng)網(wǎng)絡——LeNet-5,使用了6個特征映射,每一個都是5×5的局部感知域,來識別MNIST數(shù)字。因此上面的例子和LeNet-5很接近。后面我們開發(fā)的卷積層將使用20和40個特征映射。下面我們先看看模型學習到的一些特征:

這20個圖片對應了20個不同的特征映射。每個映射是一個5×5的圖像,對應于局部感知域的5×5個權重。顏色越白(淺)說明權值越小(一般都是負的),因此對應像素對于識別這個特征越不重要。顏色越深(黑)說明權值越大,對應的像素越重要。

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

共享權重和bias的一大好處是它極大的減少了網(wǎng)絡的參數(shù)數(shù)量。對于每一個特征映射,我們只需要 25=5×5 個權重,再加一個bias。因此一個特征映射只有26個參數(shù)。如果我們有20個特征映射,那么只有20×26=520個參數(shù)。如果我們使用全連接的神經(jīng)網(wǎng)絡結構,假設隱藏層有30個神經(jīng)元(這并不算很多),那么就有784*30個權重參數(shù),再加上30個bias,總共有23,550個參數(shù)。換句話說,全連接的網(wǎng)絡比卷積網(wǎng)絡的參數(shù)多了40倍。

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

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

池化(Pooling)

除了上面的卷積層,卷積神經(jīng)網(wǎng)絡也包括池化層(pooling layers)。池化層一般都直接放在卷積層后面池化層的目的是簡化從卷積層輸出的信息。

更具體一點,一個池化層把卷積層的輸出作為其輸入并且輸出一個更緊湊(condensed)的特征映射。比如,池化層的每一個神經(jīng)元都提取了之前那個卷積層的一個2×2區(qū)域的信息。更為具體的一個例子,一種非常常見的池化操作叫做Max-pooling。在Max-Pooling中,這個神經(jīng)元選擇2×2區(qū)域里激活值最大的值,如下圖所示:

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

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

我們可以把max-pooling看成神經(jīng)網(wǎng)絡關心某個特征在這個區(qū)域里是否出現(xiàn)。它忽略了這個特征出現(xiàn)的具體位置。直覺上看,如果某個特征出現(xiàn)了,那么這個特征相對于其它特征的精確位置是不重要的【精確位置不重要,但是大致的位置是重要的,比如識別一個貓,兩只眼睛和鼻子有一個大致的相對位置關系,但是在一個2×2的小區(qū)域里稍微移動一下眼睛,應該不太影響我們識別一只貓,而且它還能解決圖像拍攝角度變化,扭曲等問題】。而且一個很大的好處是池化可以減少特征的個數(shù)【2×2的max-pooling讓特征的大小變?yōu)樵瓉淼?/4】,因此減少了之后層的參數(shù)個數(shù)。

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

放到一起

現(xiàn)在我們可以把這3個idea放到一起來構建一個完整的卷積神經(jīng)網(wǎng)絡了。它和之前我們看到的結構類似,不過增加了一個有10個神經(jīng)元的輸出層,這個層的每個神經(jīng)元對應于0-9直接的一個數(shù)字:

這個網(wǎng)絡的輸入的大小是28×28,每一個輸入對于MNIST圖像的一個像素。然后使用了3個特征映射,局部感知域的大小是5×5。這樣得到3×24×24的輸出。然后使用對每一個特征映射的輸出應用2×2的max-pooling,得到3×12×12的輸出。

最后一層是全連接的網(wǎng)絡,3×12×12個神經(jīng)元會連接到輸出10個神經(jīng)元中的每一個。這和之前介紹的全連接神經(jīng)網(wǎng)絡是一樣的。

卷積結構和之前的全連接結構有很大的差別。但是整體的圖景是類似的:一個神經(jīng)網(wǎng)絡有很多神經(jīng)元,它們的行為有weights和biase確定。并且整體的目標也是類似的:使用訓練數(shù)據(jù)來訓練網(wǎng)絡的weights和biases使得網(wǎng)絡能夠盡量好的識別圖片。

和之前介紹的一樣,這里我們仍然使用隨機梯度下降來訓練。不過反向傳播算法有所不同。原因是之前bp算法的推導是基于全連接的神經(jīng)網(wǎng)絡。不過幸運的是求卷積和max-pooling的導數(shù)是非常簡單的。如果你想了解細節(jié),請自己推導。【這篇文章不會介紹CNN的梯度求解,后面實現(xiàn)使用的是theano,后面介紹CS231N的CNN是會介紹怎么自己來基于自動求導來求這個梯度,而且還會介紹高效的算法,感興趣的讀者請持續(xù)關注】

CNN實戰(zhàn)

前面我們介紹了CNN的基本理論,但是沒有講怎么求梯度。這里的代碼是用theano來自動求梯度的。我們可以暫時把cnn看出一個黑盒,試試用它來識別MNIST的數(shù)字。后面的文章會介紹theano以及怎么用theano實現(xiàn)CNN。

代碼

首先得到代碼:git clone

安裝theano

參考這里?;如果是ubuntu的系統(tǒng),可以參考這里?;如果您的機器有gpu,請安裝好cuda以及讓theano支持gpu。

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

baseline

首先我們實現(xiàn)一個baseline的系統(tǒng),我們構建一個只有一個隱藏層的3層全連接網(wǎng)絡,隱藏層100個神經(jīng)元。我們訓練時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上的準確率,這個模型使用訓練數(shù)據(jù)訓練,并根據(jù)validation_data來選擇當前最好的模型。使用validation數(shù)據(jù)來可以避免過擬合。讀者運行時可能結果會有一些差異,因為模型的參數(shù)是隨機初始化的。

改進版本1

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

在這種網(wǎng)絡結構中,我們可以認為卷積和池化層可以學會輸入圖片的局部的空間特征,而全連接的層整合全局的信息,學習出更抽象的特征。這是卷積神經(jīng)網(wǎng)絡的常見結構。

下面是代碼:

>>> 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),經(jīng)過一個20個5 5的卷積池層后變成了(mini_batch_size, 20, 24,24),然后在經(jīng)過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

在這個網(wǎng)絡結構中,我們吧卷積和池化層看出一個整體。這只是一種習慣。network3.py會把它們當成一個整體,每個卷積層后面都會跟一個池化層。但實際的一些卷積神經(jīng)網(wǎng)絡并不都要接池化層。

改進版本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),經(jīng)過一個20個5 5的卷積池層后變成了(mini_batch_size, 20, 24,24),然后在經(jīng)過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個不同的”圖片“,而不是一個”圖片“。那第二個卷積層的神經(jīng)元學到的是什么呢?【如果第一層的卷積網(wǎng)絡能識別”眼睛“,”鼻子“,”耳朵“。那么第二層的”臉“就是2個眼睛,2個耳朵,1個鼻子,并且它們滿足一定的空間約束。所以第二層的每一個神經(jīng)元需要連接第一層的每一個輸出,如果第二層只連接”眼睛“這個特征映射,那么只能學習出2個眼睛,3個眼睛這樣的特征,那就沒有什么用處了】

改進版本3

使用ReLU激活函數(shù)。ReLU的定義是:

ReLU(x)=max(0,x)

>>> 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%。從作者的經(jīng)驗來看,ReLU總是要比sigmoid激活函數(shù)要好。

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

改進版本4

擴展數(shù)據(jù)。

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

$ python expand_mnist.py

expand_mnist.py這個腳本就會擴展數(shù)據(jù)。它只是簡單的把圖片向上下左右各移動了一個像素。擴展后訓練數(shù)據(jù)從50000個變成了250000個。

接下來我們用擴展后的數(shù)據(jù)來訓練模型:

>>> 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%。擴展數(shù)據(jù)看起來非常trival,但是卻極大的提高了識別準確率。

改進版本5

接下來還有改進的辦法嗎?我們的全連接層只有100個神經(jīng)元,增加神經(jīng)元有幫助嗎? 作者嘗試了300和1000個神經(jīng)元的全連接層,得到了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個神經(jīng)元的全連接層。得到的準確率是99.43%,把這一層的神經(jīng)元個數(shù)從100增加到300個和1000個得到的準確率是99.48 %和99.47%。有一些提高但是也不明顯。

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

下面是在兩個全連接層都加入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%的一個模型。

這里有兩點值得注意:

訓練的epoch變成了40.因為dropout減少了過擬合,所以我們不需要60個epoch。

全連接層使用了1000個神經(jīng)元。因為dropout會丟棄50%的神經(jīng)元,所以從直覺來看1000個神經(jīng)元也相當于只有500個。如果過用100個神經(jīng)元感覺太少了點。作者經(jīng)過驗證發(fā)現(xiàn)有了dropout用1000個比300個的效果好。

改進版本6

ensemble多個神經(jīng)網(wǎng)絡。作者分別訓練了5個神經(jīng)網(wǎng)絡,每一個都達到了99.6%的準確率,然后用它們來投票,得到了99.67%準確率的模型。

這是一個非常不錯的模型了,10000個測試數(shù)據(jù)只有33個是錯誤的,我們把錯誤的圖片都列舉了出來:

片的右上角是正確的分類,右下角是模型的分類。可以發(fā)現(xiàn)有些錯誤可能人也會犯,因為有些數(shù)字人也很難分清楚。

【為什么只對全連接的層使用dropout?】

如果讀者仔細的閱讀代碼,你會發(fā)現(xiàn)我們只對全連接層進行了dropout,而卷積層沒有。當然我們也可以對卷積層進行dropout。但是沒有必要。因為卷積層本身就有防止過擬合的能力。原因是權值共享強制網(wǎng)絡學到的特征是能夠應用到任何位置的特征。這讓它不太容易學習到特別局部的特征。因此也就沒有必要對它進行的dropout了。

更進一步

感興趣的讀者可以參考這里,列舉了MNIST數(shù)據(jù)集的最好結果以及對應的論文。目前最好的結果是99.79%

What’s Next?

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容