1. 程式人生 > >從零開始:用Python搭建神經網路

從零開始:用Python搭建神經網路

在這篇部落格裡,我們將從零開始搭建一個三層的神經網路。我們不會對用到的數學原理一一贅述,但我保證你可以直觀地瞭解到我們在做什麼。另外,你也可以通過文章內的連結來獲取更詳細的資訊。
這兒我就假定你已經熟悉基礎的微積分和機器學習的一些概念,比如分類和規範化,最好還能懂得一些優化神經網路的技術,比如梯度下降法。當然,即便你對以上提到的這些都不是很瞭解,我相信你還是能從這篇文章找到樂子的;-)
那從頭來做到底有什麼意義?我想,即便是以後使用神經網路庫來做開發,比如
PyBrain,一次(甚至多次)從頭開始搭建網路的經歷仍然可以作為極其寶貴的練習經驗。它能夠幫助你更好地瞭解神經網路的運作機制。而這也是設計高效的模型必經之路。
本文所展示的例項程式碼追求簡單易懂,因此在效率上會打折扣。在我的下一篇推文中,我將展示如何通過

Theano來實現一個高效率的神經網路。連結

生成資料集

讓我們從生成所需要的資料集開始吧。幸運的是,scikit-learn提供了一些很有用的資料集生成器,讓我們不必為之再造輪子,我們先試試make_moons

捕獲.jpg   

nn-from-scratch-dataset.png  

生成了兩類資料集,分別用紅點和藍點表示。你可以把藍點想象成男性病人,紅點想象成女性病人,把x軸和y軸想象成藥物治療劑量。
我們希望通過訓練使得機器學習分類器能夠在給定的x軸y軸座標上預測正確的分類情況。我們無法用直線就把資料劃分,可見這些資料樣本呈非線性。那麼,除非你手動構造非線性功能(例如多項式),否則,諸如邏輯迴歸(Logistic Regression)這類線性分類器將無法適用於這個案例。
事實上,這也正是神經網路的一大主要優勢。神經網路的隱藏層會為你去學習特徵,所以你不需要為構造特徵這件事去操心

邏輯迴歸

為了證明(學習特徵)這點,讓我們來訓練一個邏輯迴歸分類器吧。以x軸,y軸的值為輸入,它將輸出預測的類(0或1)。為了簡單起見,這兒我們將直接使用scikit-learn裡面的邏輯迴歸分類器。

捕獲.jpg

nn-from-scratch-lr-decision-boundary.png

圖表向我們展示了邏輯迴歸分類器經過學習最終得到的決策邊界。儘管它儘可能地將資料區分為兩類,卻不能捕獲到資料呈“月亮形狀”的特性。

訓練一個神經網路

現在,我們搭建由一個輸入層,一個隱藏層,一個輸出層組成的三層神經網路。輸入層中的節點數由資料的維度來決定,也就是2個。相應的,輸出層的節點數則是由類的數量來決定,也是2個。(因為我們只有一個預測0和1的輸出節點,所以我們只有兩類輸出,實際中,兩個輸出節點將更易於在後期進行擴充套件從而獲得更多類別的輸出)。以x,y座標作為輸入,輸出的則是兩種概率,一種是0(代表女),另一種是1(代表男)。結果如下:

nn-from-scratch-3-layer-network-1024x693.png

我們可以選擇隱藏層的維度。放進去的節點越多,實現的功能就可以越複雜。但是維度過高也是會有代價的。首先,更多的預測以及學習網路引數意味著更高的計算強度,更多的引數也會帶來過擬合的風險。
那麼該如何判斷隱藏層的規模呢?儘管總會有許多通用性很好的引導和推薦,但問題的差異性也不該被忽視。在我看來,選擇規模這件事絕不僅僅是門科學,它更像是一門藝術。通過待會兒的演示,我們可以看到隱藏層裡的節點數是怎麼影響我們的輸出的。
另外,我們還需要為隱藏層選擇啟用函式(activation function)。啟用函式會將輸入轉化成輸出。非線性的啟用函式可以幫助我們處理非線性的假設。通常選用的啟用函式有tanh, the sigmoid function, ReLUs。在這裡我們將使用tanh這樣一個適用性很好地函式。這些函式有一個優點,就是通過原始的函式值便可以計算出它們的導數。例如tanh的導數就是1-tanh2x。這讓我們可以在推算出tanh⁡x一次後就重複利用這個得到導數值。
鑑於我們希望我們的網路輸出的值為概率,所以我們將使用softmax作為輸出層的啟用函式,這個函式可以將原始的數值轉化為概率。如果你很熟悉邏輯迴歸函式,你可以把它當做是邏輯迴歸的一般形式。

我們的網路是如何做出預測的呢?

神經網路通過前向傳播做出預測。前向傳播僅僅是做了一堆矩陣乘法並使用了我們之前定義的啟用函式。如果該網路的輸入x是二維的,那麼我們可以通過以下方法來計算其預測值 預測值.png

latex.png

zi是第i層的輸入,ai是該層應用啟用函式後的輸出i,Wi,bi是需要我們通過訓練資料來獲取的神經網路引數,你可以把它們當作在網路的層與層之間用於轉化資料的矩陣。這些矩陣的維度可以通過上面的矩陣乘法看出來。如果我們在隱藏層上使用500個節點,那麼就有 latex_012.pnglatex_017.pnglatex_016.pnglatex_019.png 。可以看出,隱藏層的規模與可以用在隱藏層的節點數是呈正相關的。

研究引數

研究引數是為了找到能夠使我們的訓練資料集錯誤率最小化的引數(latex_007.png )。但該如何定義錯誤呢?我們在這裡會用損失函式(loss function)來檢測錯誤。通常對softmax的輸出,我們會選擇明確的交叉熵損失(cross-entropy loss)(或者叫負對數似然)。如果我們有 latex_004.png個訓練示例,latex_020.png 個類別,那麼預測latex_013.png 相對於真實的有標籤資料的損失則可以通過如下方法來計算獲得:

latex_018.png

這個公式看起來很複雜,它的功能就是對我們的訓練示例進行求和,並加上預測值錯誤造成的損失。所以,標籤值latex_003.png 與預測值latex_013.png 相差越大,損失就越大。通過尋找降低錯誤率的引數,我們可以實現最大似然。
我們可以使用梯度下降法來找到這些引數,這裡,我會使用一種最普遍的方法:批量梯度下降法。這種方法的學習速率是固定的。它的衍化版例如SGD(隨機梯度下降stochastic gradient descent)或者最小批量梯度下降(minibatch gradient descent)通常在實際使用中會有更好的效果。所以,如果您真的想要學習或者使用,那麼請選擇這些方法,最好還要能夠實現逐漸衰減學習速率
作為輸入,梯度下降法需要各引數對應損失函式的梯度(導數的引數)latex_023.pnglatex_008.pnglatex_015.pnglatex_005.png 。為了計算這些梯度,我們使用著名的後向傳播演算法。這種方法在通過輸出計算梯度方面效率很高。您可以通過這些連結(連結1連結2)來了解它,在這裡我就不詳細介紹它的原理了。
通過後向傳播公式,我們找到了下面這些資料:

latex_024.png

實現

接下來我們就要實現這個三層的神經網路了。首先,我們需要定義一些對用於梯度下降法的變數和引數。

捕獲.jpg

首先,我們先實現之前定義的損失函式,這將用來評估我們的模型。

捕獲.jpg

我們還要實現一個用於計算輸出的輔助函式。它會通過定義好的前向傳播方法來返回擁有最大概率的類別。

捕獲.jpg

最後是訓練神經網路的函式。它會使用我們之前找到的後向傳播導數來進行批量梯度下降運算。

捕獲.jpg

捕獲.jpg

一個隱藏層規模為3的網路

讓我們看看訓練一個隱藏層規模為3的網路會發生什麼。

捕獲.jpg

nn-from-scratch-h3.png

喔~這看起來相當不錯。我們的神經網路能夠成功地找到區分不同類別的決策邊界了。

變更隱藏層規模

在剛剛的示例中,我們選擇了一個隱藏層規模為3的網路,現在我們來看看不同規模的隱藏層會帶來什麼樣的效果。

捕獲.jpg

nn-from-scratch-hidden-layer-varying.png

我們可以看到,低維度的隱藏層很好地抓住了資料的整體趨勢。高維度的隱藏層則顯現出過擬合的狀態。相對於整體性適應,它們更傾向於精確記錄各個點。如果我們要在一個分散的資料集上進行測試(你也應該這麼做),那麼隱藏層規模較小的模型會因為更好的通用性從而獲得更好的表現。雖然我們可以通過強化規範化來抵消過擬合,但選擇正確的隱藏層規模相對來說會更“經濟實惠”一點。

小練習

這兒有些可以幫助你更進一步理解程式碼的乾貨:
1.    用最小批量梯度下降法替換梯度下降法來訓練網路,這會幫助你的模型在實戰中獲得更好的表現。
2.    我們使用的是固定學習速率來進行梯度下降。試著去實現學習速率逐步衰減的方法
3.    本文中,我們用tanh作為啟用函式。試試其他的啟用函式(有些我們之前已經提到過)。記住,改變啟用函式也就意味著改變了後向傳播導數。
4.    將網路從兩個類別拓展到三個類別。您將需要為此準備一個合適的資料集。
5.    將網路拓展到四層,對不同層的規模進行測試。增加新的隱藏層意味著你的前向傳播和後向傳播程式碼都需要調整。
所有的程式碼都可以在Github的iPython notebook上找到。歡迎您提出問題或者反饋:-)

以上內容來源自python部落:http://python.freelycode.com/contribution/detail/300

英文原文:http://www.wildml.com/2015/09/implementing-a-neural-network-from-scratch/