文章導讀:
- 卷積神經網絡
- 卷積神經網絡實踐
深度神經網絡在可以模擬更加復雜的情形,但是在上一章中,我們發現訓練深度神經網絡的時候會出現梯度消失的問題,從而導致模型訓練失敗。這一章,將會介紹可以被用在深度學習上的一些技術。
這章的主要內容是介紹一種應用最廣泛的深度神經網絡:卷積神經網絡。我們將會了解到卷積,池化等概念,通過在之前的代碼上利用這些技術進行優化達到了驚人的99.67%的准確率。
除此之外,本章還將介紹一些其他的基本神經網絡,例如循環神經網絡。在介紹完這些之后,還會介紹深度學習技術的發展現狀及未來的發展方向。
一. 卷積神經網絡
之前我們在進行MNIST數字分類的時候,輸入數據是將每張圖片按像素展開得到的784維向量,這樣訓練得到的結果雖然不錯,但是仔細想想就會發現它的問題。對於圖片來說,不同的不同的像素點之間的距離很遠,舊的方法就完全沒有考慮像素點之間的空間聯系。這一節介紹的卷積神經網絡就考慮了這種空間聯系,並且訓練迅速,在圖像分類問題上得到了非常好的效果。
卷積神經網絡涉及到三個基本思想:local receptive fields(局部感受野),shared weights(參數共享),pooling(池化),接下來依次介紹。
Local receptive fields:
在之前的神經網絡中,輸入神經網絡的數據是一個多維向量,與輸入層連接的隱藏層的每一個神經元都和所有的輸入層神經元連接。
這樣一副784個像素點的圖片中的每一個像素點都是一個輸入神經元,后一層的每個神經元和所有這些輸入神經元都有連接。
local receptive fields局部感受野的概念就是后面一層的神經元只會和部分窗口下的輸入神經元連接,例如,對於5*5的窗口:
我們可以將窗口從左到右,從上到下進行平移,每移動一次,當前窗口下的輸入神經元就對應一個隱藏層的神經元。
對於28*28的圖像,使用5*5的窗口的話,一共可以移動23*23次,也就是說隱藏層將會有24*24個神經元。
上面的例子中,每次平移的步長為1,實際中這個步長其實也是可以根據需要改變的。
Shared weights and biases:
這里的參數共享說的是對於和輸入層連接的24*24個隱藏層神經元,每個隱藏層神經元的參數是一樣的,也就是說不同窗口和其對應的隱藏層神經元共用一套參數。對於24*24個隱藏神經元中的第j, k個神經元其輸出為:
其中\(w_{l,m}\)對應着24*24個神經元中任意神經元與每個5*5窗口下的所有輸入神經元連接的5*5個參數。這樣就說明,第一個隱藏層中所有的神經元識別的是同一個特征,區別在於該特征出現在原圖中的位置不同。基於這些原因,我們也把從輸入層到隱藏層的這個映射關系稱為特征映射,其中的共享權重和偏差作為決定了這個特征映射,通常也被成為一個核或濾波器。
上面我們在隱藏層中只用到了一種核,也就是只檢測了一個特征,事實上我們也可以使用多個核來檢測多個特征:
這個例子中就使用了3個5*5的核來檢測三個不同的特征。實際中,可能使用更多的特征,比如我們隨后的代碼中就分別使用了20和40個核。
這個例子中的20幅圖分別對應了20個不同的5*5核代表的權重,其中黑色方塊代表高權重,白色方塊代表低權重,這些圖上的黑色區域就是該核所檢測的特征。在這些特征圖上,我們看到有很多自區域存在很明顯的黑白分界,說明這里的確存在一些空間上的特征,至於這些特征是什么,我們並不是特別清楚,畢竟它不是非常規則的幾何圖形。
卷積的另外一個好處就是減少了需要學習的參數。考慮到最初的神經網絡,在使用30個隱藏層神經元的情況下,一共有784*30(權重)+30(偏差)=23550個參數。而現在的話,5*5的核對應26個參數,如果使用20個特征,則有20*26=520個參數,減少了大概40倍。我們有理由相信這將大大減少訓練的時間,使得深度網絡的訓練成為可能。
(125)式也是“卷積”這個名字的由來,卷積操作就不說了,其實和以前信號系統里學的卷積沒什么區別,只是由一維變成了二維情形(圖片),並且由連續積分變成了離散求和。
池化層:
卷積神經網絡的另外一個部分就是池化層,池化層的使用發生在卷積層之后,它的作用是對卷積層的輸出結果進行簡化。
對於上面中的24*24的卷積層的輸出,池化層的每一個窗口單元,例如2*2的窗口,對卷積層結果進行處理。如果使用max-pooling的方法,就是說用這個2*2的窗口內的最大值作為這四個值的代表:
通過這樣的方法,就將24*24的卷積層輸出簡化為了12*12的池化層輸出。
池化的作用當然也可以應用在多個特征的情況下,例如下圖中3個特征的情況:
max-pooling可以看作是一種檢測特征在上一層卷積層是否被檢測到的方法。隨后max-pooling將不再關心該特征在圖像中的具體位置,而是處理特征之間的相對位置。通過這樣的方法,除掉了很多不明顯的特征,顯著減小了后一層的參數。
除了max-pooling以外,還存在其他的池化方法。例如L2-pooling,對於2*2的窗口,它不再是求最大值,而是使用四個數的L2范數代替。雖然和max-pooling有點不一樣,但是它們的目的都是為了壓縮卷積層輸出的信息。
總覽:
將上面的卷積層,池化層和輸入輸出神經元結合起來就得到如下的一個簡單的卷積神經網絡:
雖然結構上和之前有點不一樣,但是還是有很大的共同點的。它們都是有簡單的單層相互連接起來的,單層由它們自己的參數決定,輸入上一層的輸出,將自己的輸出又作為后一層的輸入這樣傳遞下去。
在后面的內容里,我們將會用隨機梯度下降算法和反向傳播算法對卷積神經網絡進行訓練。訓練方法大致和之前一樣,但是對反向傳播過程需要進行一些修改。因為之前的反向傳播針對的是全連接的神經網絡,而卷積神經網絡並是部分連接的。
拓展:
卷積神經網絡下的反向傳播:
之前的公式bp(1)-bp(4)給出的是全連接神經網絡下的反向傳播方程。對於由一個輸出層,一個卷積層,一個max-pooling層,一個全連接輸出層的卷積神經網絡,給出該情形下的反向傳播方程。
二. 卷積神經網絡實踐
在了解了卷積神經網絡的核心概念之后,再來看看它在實際中的應用。這里的代碼中調用了Theano庫,一來可以快速的實現卷積神經網絡的反向傳播算法,另外相比於我們自己的實現神經網絡,Theano的計算更快速,使得我們可以實現更復雜的模型。
先從一個簡答的模型開始,只有一個包含100個神經元的隱藏層的神經網絡:
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%的准確率,為了避免過擬合的影響,這個准確率是在validation數據上達到最優准確率時對測試數據測得的。
和之前得到的最好的結果98.04%相比較,主要有兩點不同。首先,之前的結果是在使用了正則化的基礎上得到的,正則化的確可以改善結果但是並不是很大的改善,所以我們隨后再考慮。其次,之前實用的是sigmoid輸出層加交叉熵的組合,這里我們實用的是softmax加對數似然損失函數。這兩者都可以加速神經網絡的訓練,后者在圖像分類問題上應用的更加普遍。
接着我們試驗更復雜的深度網絡看看結果有沒有改善。
我們在隱藏層之前添加一個卷積層,使用5*5的局部窗口,步長為1,共20個特征映射,隨后使用2*2的池化窗口:
在這個結構中,卷積和池化層可以看作是學習輸入圖像中的局部空間的特征,隨后的全連接的隱藏層的學習則是更抽象的層次,學習整個圖像上的全局信息。
訓練該模型:
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)
得到了98.78%准確率,比之前的任何結果都要好。
這個結果還能繼續提升嗎?我們在現有卷積層和全連接的隱藏層之間再加入一個卷積層,仍然使用5*5的局部接受野和2*2的池化窗口:
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)
這次我們得到了驚人的99.06%的正確率。
這里我們可能會有兩個疑問。第一個問題是第二個卷積層表示了什么?第二個卷積層的輸入是第一個卷積層輸出的12*12圖像,它的像素點表示了局部特征在原始圖像中的出現。可以將其看作是原始圖片的一個抽象壓縮過的版本。由於它仍然存在一些局部空間特征,所以我們仍然可以使用第二個卷積層進行提取。
第二個問題就是既然是特殊的圖像,第一個卷積層處理的原始圖片只有一張,這里由於20個特征映射,也就是有20張12*12的圖片,第二卷積層應該怎么處理呢?事實上,它對所有20幅圖片都是可見的。也就是說,每個神經元處理20張圖片中的同一塊局部接受野。
使用RELU激活函數
上面的神經網絡使用的都是sigmoid激活函數,我們接着試用一下RELU函數看看效果。作者還發現使用L2正則化會優化結果,於是我們使用\(\lambda = 0.1\)的L2正則化:
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)
得到了99.23%的准確率,相對於99.06%來說算是一個小的進步。不過,作者表示在他的所有試驗中,RELU激活函數整體都優於sigmoid激活函數,因此RELU激活函數是更好的選擇。
是什么使得RELU比sigmoid效果要好呢?很遺憾,目前我們並沒有理論指導如何選擇激活函數,都是一些經驗上或啟發式的結論,類似於我們之前討論過,RELU在輸入增大的情況下不會出現saturated的狀態,它可以繼續學習,所以結果要好於sigmoid函數。
擴展訓練數據
另外一個提升訓練效果的方法就是增大訓練數據集了。最簡單的方法就是我們對原圖片進行平移,分別向上下左右四個方向平移得到四張圖片,於是原始的50000張的訓練數據集就增大到了250000張。接着使用之前一樣的使用RELU激活函數的結構,得到99.37%的准確率,又提升了一點。當然這個方法還有改進空間,可以通過對圖像進行其他的變換更進一步擴大訓練數據集。
增加全連接隱藏層
我們先試着增大之前唯一的一個全連接隱藏層的規模,分別測試了300個和1000個神經元的情況,得到了99.46%和99.43%的結果,相對99.37%來說並不是什么大的提升。
那么如果是增加一層隱藏層呢,仍然是100個神經元的隱藏層:
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)
這里的結果是99.43%,同樣沒有什么明顯的提升。試驗300和1000的雙層的情況,得到99.48%和99.47%,有效果,但是並不顯著。
這是為什么呢?是添加隱藏層真的沒有效果還是我們的學習方式出錯了呢?我們用之前介紹的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)
這次的結果是99.60%,這次總算是得到了一個不錯的提升了。
這里有兩點參數的變化需要說一下。首先是epoch從之前的60改為了40,因為dropout減輕了過擬合,學習過程就縮短了。其次是全連接的隱藏層都使用了1000個神經元,因為dropout在訓練過程中會拋棄掉很多神經元,所以適當的增加其規模是有必要的。事實上我們同樣測試了300個情形,發現1000個的時候的確效果要更好。
神經網絡的集成
由於初始狀態的隨機性,一個很簡單的想法是用上面的方法訓練5個不一樣的神經網絡。其中每個單獨的准確率都在96%左右,然后用這5個模型的結果進行投票,少數服從多數,判斷一張圖片最終的分類。
這聽起來有點簡單的不可思議,但是確是神經網絡或機器學習上經常使用的方法。在這里也的確獲得了提升,得到了99.67%的准確率。也就是說在10000張圖片中,我們只識別錯了33張,來看一下這33張的結果:
可以看到這些都是非常潦草的手寫體,即便是正常的人也不一定能全部識別准確,更別說是機器了。要知道我們的機器已經識別准確9967張圖片了,已經非常接近正常人的識別水平了。
為什么只在全連接隱藏層上使用dropout
細心的話可以發現之前的dropout只發生在全連接層上,為什么不對卷積層使用dropout呢?事實上可以這樣做,但是沒必要。因為卷積層的設計天生就可以防止過擬合。因為共享參數的設置,導致它更多的是從整張圖片上去學習規律,而不是僅僅着眼於一些局部的細節。所以對於卷積層,沒有必要去使用一些正則化的方法。
為什么可以訓練深度網絡
之前我們討論了在神經網絡層增加的時候會出現梯度不穩定的問題,我們現在為什么又能訓練了呢?事實上,我們並沒有避免這個問題,我們只是使用了一些方法來幫助處理:1. 使用卷積神經網絡大大減少了需要訓練的參數,使得訓練更容易 2. 使用了更強大的正則化技術(尤其是dropout)減輕了過擬合 3. 使用了RELU激活函數加速訓練 4. 使用GPU加速訓練。
除此之外,我們還使用了其它的一些技術:拓展訓練數據集(避免過擬合);使用正確的損失函數(避免訓練速度下降);使用正確的參數初始化方法(避免由於神經元saturation導致的訓練速度下降)。
這些技術雖然都是簡單的基礎技術,但是只要應用得到,將它們合理組合起來,就可以得到強大效果。
這章作者還提及一些其它技術,不過都是簡要說明了一下並沒有詳細介紹,這里就不寫了。
這一系列的六章就結束了,相對原書來說可能少了很多內容,而且代碼實現也沒有細說,想了解的同學還是推薦git拉一下作者給的代碼自己詳細研究一下。
學無止境,以后還會接着及時寫博客作為對自己工作學習的總結。