前言git
訓練神經網絡模型時,若是訓練樣本較少,爲了防止模型過擬合,Dropout能夠做爲一種trikc供選擇。Dropout是hintion最近2年提出的,源於其文章Improving neural networks by preventing co-adaptation of feature detectors.中文大意爲:經過阻止特徵檢測器的共同做用來提升神經網絡的性能。本篇博文就是按照這篇論文簡單介紹下Dropout的思想,以及從用一個簡單的例子來講明該如何使用dropout。github
基礎知識:網絡
Dropout是指在模型訓練時隨機讓網絡某些隱含層節點的權重不工做,不工做的那些節點能夠暫時認爲不是網絡結構的一部分,可是它的權重得保留下來(只是暫時不更新而已),由於下次樣本輸入時它可能又得工做了(有點抽象,具體實現看後面的實驗部分)。函數
按照hinton的文章,他使用Dropout時訓練階段和測試階段作了以下操做:性能
在樣本的訓練階段,在沒有采用pre-training的網絡時(Dropout固然能夠結合pre-training一塊兒使用),hintion並非像一般那樣對權值採用L2範數懲罰,而是對每一個隱含節點的權值L2範數設置一個上限bound,當訓練過程當中若是該節點不知足bound約束,則用該bound值對權值進行一個規範化操做(即同時除以該L2範數值),說是這樣可讓權值更新初始的時候有個大的學習率供衰減,而且能夠搜索更多的權值空間(沒理解)。學習
在模型的測試階段,使用」mean network(均值網絡)」來獲得隱含層的輸出,其實就是在網絡前向傳播到輸出層前時隱含層節點的輸出值都要減半(若是dropout的比例爲50%),其理由文章說了一些,能夠去查看(沒理解)。測試
關於Dropout,文章中沒有給出任何數學解釋,Hintion的直觀解釋和理由以下:ui
1. 因爲每次用輸入網絡的樣本進行權值更新時,隱含節點都是以必定機率隨機出現,所以不能保證每2個隱含節點每次都同時出現,這樣權值的更新再也不依賴於有固定關係隱含節點的共同做用,阻止了某些特徵僅僅在其它特定特徵下才有效果的狀況。this
2. 能夠將dropout看做是模型平均的一種。對於每次輸入到網絡中的樣本(多是一個樣本,也多是一個batch的樣本),其對應的網絡結構都是不一樣的,但全部的這些不一樣的網絡結構又同時share隱含節點的權值。這樣不一樣的樣本就對應不一樣的模型,是bagging的一種極端狀況。我的感受這個解釋稍微靠譜些,和bagging,boosting理論有點像,但又不徹底相同。spa
3. native bayes是dropout的一個特例。Native bayes有個錯誤的前提,即假設各個特徵之間相互獨立,這樣在訓練樣本比較少的狀況下,單獨對每一個特徵進行學習,測試時將全部的特徵都相乘,且在實際應用時效果還不錯。而Droput每次不是訓練一個特徵,而是一部分隱含層特徵。
4. 還有一個比較有意思的解釋是,Dropout相似於性別在生物進化中的角色,物種爲了使適應不斷變化的環境,性別的出現有效的阻止了過擬合,即避免環境改變時物種可能面臨的滅亡。
文章最後固然是show了一大把的實驗來講明dropout能夠阻止過擬合。這些實驗都是些常見的benchmark,好比Mnist, Timit, Reuters, CIFAR-10, ImageNet.
實驗過程:
本文實驗時用mnist庫進行手寫數字識別,訓練樣本2000個,測試樣本1000個,用的是matlab的https://github.com/rasmusbergpalm/DeepLearnToolbox,代碼在test_example_NN.m上修改獲得。關於該toolbox的介紹能夠參考網友的博文【面向代碼】學習 Deep Learning(一)Neural Network。這裏我只用了個簡單的單個隱含層神經網絡,隱含層節點的個數爲100,因此輸入層-隱含層-輸出層節點依次爲784-100-10. 爲了使本例子簡單話,沒用對權值w進行規則化,採用mini-batch訓練,每一個mini-batch樣本大小爲100,迭代20次。權值採用隨機初始化。
實驗結果:
沒用Dropout時:
訓練樣本錯誤率(均方偏差):0.032355
測試樣本錯誤率:15.500%
使用Dropout時:
訓練樣本錯誤率(均方偏差):0.075819
測試樣本錯誤率:13.000%
能夠看出使用Dropout後,雖然訓練樣本的錯誤率較高,可是訓練樣本的錯誤率下降了,說明Dropout的泛化能力不錯,能夠防止過擬合。
實驗主要代碼及註釋:
test_dropout.m:
%% //導入minst數據並歸一化 load mnist_uint8; train_x = double(train_x(1:2000,:)) / 255; test_x = double(test_x(1:1000,:)) / 255; train_y = double(train_y(1:2000,:)); test_y = double(test_y(1:1000,:)); % //normalize [train_x, mu, sigma] = zscore(train_x);% //歸一化train_x,其中mu是個行向量,mu是個列向量 test_x = normalize(test_x, mu, sigma);% //在線測試時,歸一化用的是訓練樣本的均值和方差,須要特別注意 %% //without dropout rng(0); nn = nnsetup([784 100 10]);% //初步構造了一個輸入-隱含-輸出層網絡,其中包括了 % //權值的初始化,學習率,momentum,激發函數類型, % //懲罰係數,dropout等 opts.numepochs = 20; % //Number of full sweeps through data opts.batchsize = 100; % //Take a mean gradient step over this many samples [nn, L] = nntrain(nn, train_x, train_y, opts); [er, bad] = nntest(nn, test_x, test_y); str = sprintf('testing error rate is: %f',er); disp(str) %% //with dropout rng(0); nn = nnsetup([784 100 10]); nn.dropoutFraction = 0.5; % //Dropout fraction,每一次mini-batch樣本輸入訓練時,隨機扔掉50%的隱含層節點 opts.numepochs = 20; % //Number of full sweeps through data opts.batchsize = 100; % //Take a mean gradient step over this many samples nn = nntrain(nn, train_x, train_y, opts); [er, bad] = nntest(nn, test_x, test_y); str = sprintf('testing error rate is: %f',er); disp(str)
下面來分析與dropout相關的代碼,集中在上面test.m代碼的後面with drop部分。首先在訓練過程當中須要將神經網絡結構nn的dropoutFraction設置爲必定比例,這裏設置爲50%:nn.dropoutFraction = 0.5;
而後進入test_dropout.m中的nntrain()函數,沒有發現與dropoutFraction相關的代碼,繼續進入網絡前向傳播函數nnff()函數中,在網絡的隱含層節點激發函數值被計算出來後,有下面的代碼:
if(nn.dropoutFraction > 0) if(nn.testing) nn.a{i} = nn.a{i}.*(1 - nn.dropoutFraction); else nn.dropOutMask{i} = (rand(size(nn.a{i}))>nn.dropoutFraction); nn.a{i} = nn.a{i}.*nn.dropOutMask{i}; end end
由上面的代碼可知,隱含層節點的輸出值以dropoutFraction百分比的概率被隨機清0(注意此時是在訓練階段,因此是else那部分的代碼),既然前向傳播時有些隱含節點值被清0了,那麼在偏差方向傳播時也應該有相應的處理,果真,在反向傳播函數nnbp()中,有下面的代碼:
if(nn.dropoutFraction>0) d{i} = d{i} .* [ones(size(d{i},1),1) nn.dropOutMask{i}]; end
也就是說計算節點偏差那一項時,其偏差項也應該清0。從上面能夠看出,使用dropout時,其訓練部分的代碼更改不多。
(有網友發私信說,反向傳播計算偏差項時能夠不用乘以dropOutMask{i}矩陣,後面我仔細看了下bp的公式,一開始也感受不用乘有道理。由於源碼中有爲:
for i = 1 : (n - 1) if i+1==n nn.dW{i} = (d{i + 1}' * nn.a{i}) / size(d{i + 1}, 1); else nn.dW{i} = (d{i + 1}(:,2:end)' * nn.a{i}) / size(d{i + 1}, 1); end end
代碼進行權重更新時,因爲須要乘以nn.a{i},而nn.a{i}在前向過程當中若是被mask清掉的話(使用了dropout前提下),則已經爲0了。但其實這時錯誤的,由於對偏差
敏感值做用的是與它相鏈接的前一層權值,並非本層的權值,而本層的輸出a只對它的下一層權值更新有效。)
再來看看測試部分,測試部分如hintion論文所說的,採用mean network,也就是說前向傳播時隱含層全部節點的輸出同時減少dropoutFraction百分比,即保留(1- dropoutFraction)百分比,代碼依舊是上面貼出的nnff()函數裏知足if(nn.testing)的部分:
if(nn.dropoutFraction > 0) if(nn.testing) nn.a{i} = nn.a{i}.*(1 - nn.dropoutFraction); else nn.dropOutMask{i} = (rand(size(nn.a{i}))>nn.dropoutFraction); nn.a{i} = nn.a{i}.*nn.dropOutMask{i}; end end
上面只是個簡單的droput實驗,能夠用來幫助你們理解dropout的思想和使用步驟。其中網絡的參數都是採用toolbox默認的,並無去調整它,若是該實驗將訓練樣本增大,好比6w張,則參數不變的狀況下使用了dropout的識別率還有可能會下降(固然這頗有多是其它參數沒調到最優,另外一方面也說明在樣本比較少的狀況下,droput確實能夠防止過擬合),爲了體現droput的優點,這裏我只用了2000張訓練樣本。
參考資料:
Hinton, G. E., et al. (2012). "Improving neural networks by preventing co-adaptation of feature detectors." arXiv preprint arXiv:1207.0580.