“機器學習”是一個很實踐的過程。就像剛開始學遊泳,你在隻在岸上比劃一堆規定動作還不如先跳到水裡熟悉水性學習來得快。以我們學習“機器學習”的經驗來看,很多高大上的概念剛開始不懂也沒關系,先寫個東西來跑跑,有個感覺了之後再學習那些概念和理論就快多了。如果别人已經做好了輪子,直接拿過來用則更快。是以,本文直接用michael nielsen先生的代碼(github位址: https://github.com/mnielsen/neural-networks-and-deep-learning)作為例子,給大家展現神經網絡分析的普遍過程:導入資料,訓練模型,優化模型,啟發式了解等。
本文假設大家已經了解python的基本文法,并在自己機器上運作過簡單python腳本。
手寫數字識别是機器學習領域中一個經典的問題,是一個看似對人類很簡單卻對程式十分複雜的問題。很多早期的驗證碼就是利用這個特點來區分人類和程式行為的,當然此處就不提12306近乎反人類的奇葩驗證碼了。
回到手寫數字識别,比如我們要識别出一個手寫的“9”,人類可能通過識别“上半部分一個圓圈,右下方引出一條豎線”就能進行判斷。但用程式表達就似乎很困難了,你需要考慮非常多的描述方式,考慮非常多的特殊情況,最終發現程式寫得非常複雜而且效果不好。
而用(機器學習)神經網絡的方法,則提供了另一個思路:擷取大量的手寫數字的圖像,并且已知它們表示的是哪個數字,以此為訓練樣本集合,自動生成一套模型(如神經網絡的對應程式),依靠它來識别新的手寫數字。
本文中采用的資料集就是著名的“mnist資料集”。它的收集者之一是人工智能領域著名的科學家——yann lecu。這個資料集有60000個訓練樣本資料集和10000個測試用例。運用本文展示的單隐層神經網絡,就可以達到96%的正确率。
我們可以用下圖展示上面的粗略思路。
但是如何由“訓練集”來“生成模型”呢?
在這裡我們使用反複推薦的逆推法——假設這個模型已經生成了,它應該滿足什麼樣的特性,再以此特性為條件反過來求出模型。
可以推想而知,被生成的模型應該對于訓練集的區分效果非常好,也就是相應的訓練誤差非常低。比如有一個未知其相應權重和偏移的神經網絡,而訓練神經網絡的過程就是逐漸确定這些未知參數的過程,最終使得這些參數确定的模型在訓練集上的誤差達到最小值。我們将會設計一個數量名額衡量這個誤差,如果訓練誤差沒有達到最小,我們将繼續調整參數,直到這個名額達到最小。但這樣訓練出來的模型我們仍無法保證它面對新的資料仍會有這樣好的識别效果,就需要用測試集對模型進行考核,得出的測試結果作為對模型的評價。是以,上圖就可以細化成下圖:
但是,如果我們已經生成了多個模型,怎麼從中選出最好的模型?一個自然的思路就是通過比較不同模型在測試集上的誤差,挑選出誤差最小的模型。這個想法看似沒什麼問題,但是随着你測試的模型增多,你會覺得用測試集篩選出來的模型也不那麼可信。比如我們增加一個神經網絡的隐藏層節點,就會産生新的對應權重,産生一個新的模型。但是我也不知道增加多少個節點是合适的,是以比較全面的想法就是嘗試測試不同的節點數x∈(1,2,3,4,…,100), 來觀察這些不同模型的測試誤差,并挑出誤差最小的模型。這時我們發現我們的模型其實多出來了一個參數x, 我們挑選模型的過程就是确定最優化的參數x 的過程。這個分析過程與上面訓練參數的思路如出一轍!隻是這個過程是基于同一個測試集,而不訓練集。那麼,不同的神經網絡的層數是不是也是一個新的參數y∈(1,2,3,4,…,100), 也要經過這麼個過程來“訓練”?
我們會發現我們之前生成模型過程中很多不變的部分其實都是可以變換調節的,這些也是新的參數,比如訓練次數、梯度下降過程的步長、規範化參數、學習回合數、minibatch 值等等,我們把他們叫做超參數。超參數是影響所求參數最終取值的參數,是機器學習模型裡面的架構參數,可以了解成參數的參數,它們通常是手工設定,不斷試錯調整的,或者對一系列窮舉出來的參數組合一通進行枚舉(網格搜尋)來确定。但無論如何,這也是基于同樣一個資料集反複驗證優化的結果。在這個資料集上最後的結果并不一定在新的資料繼續有效。是以為了評估這個模型的識别效果,就需要用新的測試集對模型進行考核,得出的測試結果作為對模型的評價。這個新的測試集我們就直接叫“測試集”,之前那個用于篩選超參數的測試集,我們就叫做“交叉驗證集”。篩選模型的過程其實就是交叉驗證的過程。
是以,規範的方法的是将資料集拆分成三個集合:訓練集、交叉驗證集、測試集,然後依次訓練參數、超參數,最終得到最優的模型。
是以,上圖可以進一步細化成下圖:
或者下圖:
可見機器學習過程是一個反複疊代不斷優化的過程。其中很大一部分工作是在調整參數和超參數。
michael nielsen的代碼封裝得很好,隻需以下5行指令就可以生成神經網絡并測試結果,并達到94.76%的正确率!
第一個指令的功能是:将資料集拆分成三個集合:訓練集、交叉驗證集、測試集。
第二個指令的功能是:生成神經網絡對象,神經網絡結構為三層,每層節點數依次為(784, 30, 10)。
第三個指令的功能是:用(mini-batch)梯度下降法訓練神經網絡(權重與偏移),并生成測試結果。
該指令設定了三個超參數:訓練回合數=30, 用于随機梯度下降法的最小樣本數(mini-batch-size)=10,步長=3.0。
總共的輸出結果如下:
首先,我們解釋一下神經網絡每層的功能。
第一層是輸入層。因為mnist資料集中每一個手寫數字樣本是一個28*28像素的圖像,是以對于每一個樣本,其輸入的資訊就是每一個像素對應的灰階,總共有28*28=784個像素,故這一層有784個節點。
第三層是輸出層。因為阿拉伯數字總共有10個,我們就要将樣本分成10個類别,是以輸出層我們采用10個節點。當樣本屬于某一類(某個數字)的時候,則該類(該數字)對應的節點為1,而剩下9個節點為0,如[0,0,0,1,0,0,0,0,0,0]。
是以,我們每一個樣本(手寫數字的圖像)可以用一個超長的784維的向量表示其特征,而用一個10維向量表示該樣本所屬的類别(代表的真實數字),或者叫做标簽。
mnist的資料就是這樣表示的。是以,如果你想看訓練集中第n個樣本的784維特征向量,直接看training_data[n][0]就可以找到,而要看其所屬的标簽,看training_data[n][1]就夠了。
那麼,第二層神經網絡所代表的意義怎麼了解?這其實是很難的。但是我們可以有一個啟發式地了解,比如用中間層的某一個節點表示圖像中的某一個小區域的特定圖像。這樣,我們可以假設中間層的頭4個節點依次用來識别圖像左上、右上、左下、右下4個區域是否存在這樣的特征的。
如果這四個節點的值都很高,說明這四個區域同時滿足這些特征。将以上的四個部分拼接起來,我們會發現,輸入樣本很可能就是一個手寫“0”!
是以,同一層的幾個神經元同時被激活了意味着輸入樣本很可能是某個數字。
當然,這隻是對神經網絡作用機制的一個啟發式了解。真實的過程卻并不一定是這樣。但通過啟發式了解,我們可以對神經網絡作用機制有一個更加直覺的認識。
由此可見,神經網絡能夠識别手寫數字的關鍵是它有能夠對特定的圖像激發特定的節點。而神經網絡之是以能夠針對性地激發這些節點,關鍵是它具有能夠适應相關問題場景的權重和偏移。那這些權重和偏移如何訓練呢?
上文已經圖解的方式介紹了機器學習解決問題的一般思路,但是具體到神經網絡将是如何訓練呢?
其實最快的方式是直接閱讀代碼。我們将代碼的結構用下圖展示出來,運用其内置函數名表示基本過程,發現與我們上文分析的思路一模一樣:
簡單解釋一下,在神經網絡模型中:
所需要求的關鍵參數就是:神經網絡的權重(self.weights)和偏移(self.biases)。
超參數是:隐藏層的節點數=30,訓練回合數(epochs)=30, 用于随機梯度下降法的最小樣本數(mini_batch_size)=10,步長(eta)=3.0。
用随機梯度下降法調整參數:
用反向傳播法求出随機梯度下降法所需要的梯度(偏導數): backprop()
用輸出向量減去标簽向量衡量訓練誤差:cost_derivative() = output_activations-y
全部代碼如下(去掉注釋之後,隻有74行):
由以上分析可知,神經網絡隻需要74行代碼就可以完成程式設計,可見機器學習真正困難的地方并不在程式設計,而在你對數學過程本身,和對它與現實問題的對應關系有深入的了解。了解深入後,你才能寫出這樣的程式,并對其進行精微的調優。
我們初步的結果已經是94.76%的正确率了。但如果要将準确率提得更高怎麼辦?
這其實是一個開放的問題,有許多方法都可以嘗試。我們這裡僅僅是抛磚引玉。
首先,隐藏層隻有30個節點。由我們之前對隐藏層的啟發式了解可以猜測,神經網絡的識别能力其實與隐藏層對一些細節的識别能力正相關。如果隐藏層的節點更多的話,其識别能力應該會更強的。那麼我們設定100個隐藏層節點試試?
發現結果如下:
發現,我們隻是改了一個超參數,準确率就從94.76%提升到96.72%!
這裡強調一下,更加規範的模型調優方法是将多個模型用交叉驗證集的結果來橫向比較,選出最優模型後再用一個新的測試集來最終評估該模型。本文為了與之前的結果比較,才采用了測試集而不是交叉驗證集。讀者千萬不要學部落客這樣做哈,因為這很有可能會過拟合。這是工程實踐中資料挖掘人員經常犯的錯誤,我們之後會專門寫篇博文探讨。
我們現在回來繼續調優我們的模型。那麼還有其他的隐藏節點數更合适嗎?這個我們也不知道。常見的方法是用幾何級數增長的數列(如:10,100,1000,……)去嘗試,然後不斷确定合适的區間,最終确定一個相對最優的值。
但是即便如此,我們也隻嘗試了一個超參數,還有其他的超參數沒有調優呢。我們于是嘗試另一個超參數:步長。之前的步長是3.0,但是我們可能覺得學習速率太慢了。那麼嘗試一個更大的步長試試?比如100?
發現準确率低得不忍直視,看來步長設得太長了。根本跑不到最低點。那麼我們設定一個小的步長試試?比如0.01。
結果如下:
呃,發現準确率同樣低得不忍直視。但是有一個優點,準确率是穩步提升的。說明模型在大方向上應該還是對的。如果在調試模型的時候忽視了這個細節,你可能真的找不到合适的參數。
可見,我們第一次嘗試的神經網絡結構的超參數設定還是比較不錯的。但是真實的應用場景中,基本沒有這樣好的運氣,很可能剛開始測試出來的結果全是奇葩生物,長得違反常理,就像來自另一個次元似的。這是資料挖掘工程師常見的情況。此時最應該做的,就是遏制住心中數萬草泥馬的咆哮奔騰,靜靜地觀察測試結果的分布規律,嘗試找到些原因,再繼續将模型試着調優下去,與此同時,做好從一個坑跳入下一個坑的心理準備。當然,在機器學習工程師前赴後繼的填坑過程中,還是總結出了一些調優規律。我們會在接下來專門寫博文分析。
當然,以上的調優都沒有逃出神經網絡模型本身的範圍。但是可不可能其他的模型效果更好?比如傳說中的支援向量機?關于支援向量機的解讀已經超越了本文的篇幅,我們也考慮專門撰寫博文分析。但是在這裡我們隻是引用一下在scikit-learn中提供好的接口,底層是用性能更好的c語言封裝的著名的libsvm。
相關代碼也在michael nielsen的檔案中。直接引入,并運作一個方法即可。
我們看看結果:
94.35%,好像比我們的神經網絡低一點啊。看來我們的神經網絡模型還是更優秀一些?
然而,實際情況并非如此。因為我們用的隻是scikit-learn給支援向量機的設好的預設參數。支援向量機同樣有一大堆可調的超參數,以提升模型的效果。 跟據 andreas mueller的這篇博文,調整好超參數的支援向量機能夠達到98.5%的準确度!比我們剛才最好的神經網絡提高了1.8個百分點!
然而,故事并沒有結束。2013年,通過深度神經網絡,研究者可以達到99.79%的準确度!而且,他們并沒有運用很多高深的技術。很多技術在我們接下來的博文中都可以繼續介紹。
是以,從目前的準确度來看:
簡單的支援向量機<淺層神經網絡<調優的支援向量機<深度神經網絡
但還是要提醒一下,炫酷的算法固然重要,但是良好的資料集有時候比算法更重要。michael nielsen專門寫了一個公式來來表達他們的關系:
精緻的算法 ≤ 簡單的算法 + 良好的訓練資料
sophisticated algorithm ≤ simple learning algorithm + good training data.
是以為了調優模型,往往要溯源到資料本身,好的資料真的會有好的結果。
原文釋出時間為:2015-12-29
本文來自雲栖社群合作夥伴“大資料文摘”,了解相關資訊可以關注“bigdatadigest”微信公衆号