這次主要說說神經網絡的一些主要思想,包括介紹兩種人工神經元(perceptron neuron和sigmoid neuron)以及神經網絡的標准學習算法,隨機梯度下降法。神經網絡可以認為是一種不同於使用普通計算機語言編程的那種基於規則的編程范型,它從生物中受到啟發(人腦),讓計算機可以從大量的數據中自動推導和發現規律。這些大量的數據就被稱為訓練樣本,從訓練樣本中自動推導的過程被稱為神經網絡的學習。神經網絡並不一定是機器學習,但是本文將其看成機器學習的一種方法。
感知機
要說神經網絡,首先還是得從感知機說起。感知機由科學家Frank Rosenblatt在1950s和1960s時候開發出來,主要建立在Warren McCulloch和Walter Pitts早期的工作基礎上。但是現在感知機神經元已經用得少了,更多的是用其他的神經元模型來代替,比如sigmoid 神經元。
感知機模型如圖1所示,輸入是二元(只有0,1)的變量,然后輸出也是一個二元變量。
圖1
那么它的計算規則是啥呢?Rosenblatt引進了權重(weights)的概念,它們是表示相關輸入對於輸出值的重要性程度的一些實數。感知機神經元的輸出(0或者1)取決於權重的和
是小於還是大於某個閥值。這里的閥值同樣是一個實數,也是神經元的一個參數。用數學來表示就是:
上面是基礎的數學模型。為了理解它,我們可以舉一個通俗地例子。(1)式中決定輸出的機制主要是通過權衡所有的"證據"。比如周末即將到來,你想要去參加一個活動,所以你需要決定是去還是不去。那么我們可以通過列舉一下影響你去還是不去的主要因素,比如說:
-
天氣是不是很好?2. 你的男朋友或女朋友是不是願意陪伴你?3. 交通是不是便利?
然后,我們可以通過給這些因素相應的二元變量和
來表示。例如,
=1表示天氣是好的,
=0表示天氣不好。類似地,
和
也是同樣的道理。
如果你非常想去參加這個活動,所以你不管你男朋友或女朋友感不感興趣,也不管交通多么不便利,你唯一在意的因素是天氣。所以我們可以為這三個因素分別選擇一個權重,天氣的=6,其他兩個因素分別是,
=2,
=2。最后,假設你選擇5作為感知機的閥值。有了這些值,你就可以計算,最終判斷你是去還是不去參加活動。你可能發現了,改變權值和閥值我們會得到不同的決策的模型。例如,假設我們選擇3作為閥值(剛才是5),那么感知機將決定你應當去參加這個活動,而不管天氣多好或者其他兩個因素的影響。換句話說就是模型已經變了。"丟棄"閥值意味着你更願意去參加這個活動。
顯然,感知機並不是人類決策系統的一個完美模型。但是例子表明了一個感知機是如何權衡不同類型證據來做決策的。把這些神經元組合起來看起來貌似可以作出十分微妙的決策:
這個網絡的第一列感知機神經元,我們稱之為第一層感知機神經元,它們通過權衡輸入"證據作出了3個不同的簡單決策,。第二層的感知機神經元權衡了第一層決策的結果的證據。可以看到,第二層的神經元作出的決策比第一層的神經元更加復雜和抽象。
下面將簡化描述感知機的方式。條件> threshold看起來是累贅的,並且我們能夠改變兩個符號從而簡化它。第一個變化是把
寫成點積的形式,
其中w和x是兩個向量,它們的組成(components)分別是權值和輸入。第二個變化是把閥值(threshold)移動到不等式的另一邊,以及用稱為感知機的偏置,b=-threshold來代替。使用偏置來代替閥值,感知機的規則可寫成:
你可以把偏置想成是度量感知機神經元的輸出是如何容易地接近1。或者用生物學的術語來說,偏置是度量感知機神經元有多容易被激活。對於一個有很大偏置的感知機,它是極其容易使得神經元輸出1。但是,如果偏置是一個非常大的負數,那么它的輸出很難是1。引進偏置僅僅是在描述感知機時的一個小的變化,但是它將導致更多的符號上的簡化。
我們能夠設計學習算法來自動地調整網絡中的人工神經元的權值和偏置。這個調整是對外部刺激的回應,而不是一個程序員直接地干預改變。實際上感知機實現的功能與邏輯門一樣,但引入學習算法使得我們能夠用與傳統邏輯門徹底不同的人工神經元。
Sigmoid 神經元
學習算法(learning algorithm)聽起來不錯。但是怎樣為一個神經網絡設計這樣的算法呢?假設我們使用一個感知機的網絡來解決一些問題,例如,網絡的輸入可能是一個掃描過的,一個手寫數字的圖像的原生像素數據。我們想要網絡學習權值和偏置使得輸出能夠正確地分類數字。為了看到學習算法(你可以先把學習簡單地理解為算法怎樣找到好的參數的機制)是如何工作地,假設網絡中的某些權重(或偏置)發生了一個很小的改變。我們想要的是權重中的這個很小的變化也能夠引起輸出的相應的一個小的變化。待會我們將看到,這個性質將使得學習成為可能。可以看看下面的圖:
如果這個是真的,即在一個權值(或偏置)上的一個小的變化會引起輸出的一個小的變化,那么我們就能夠使用這個因素來修改權值和偏置來得到我們想要的神經網絡的輸出。舉個例子,假設網絡錯誤地把本應該是一個"9"的圖像分類成了一個"8"的圖像。我們能夠計算出怎樣改變權值和偏置使得網絡能夠更接近分類圖像為一個"9"。然后我們將重復這個過程,不斷地改變權值和偏置產生越來越好的輸出。這就是神經網絡的學習。
問題是當我們的網絡包含感知機神經元時,結果並不是像這樣。事實上,當任何一個神經元上的權值和偏置上的一個小的變化有時候能夠引起那個神經元的輸出發生巨大的變化,比如說使得輸出直接從0一下子突變成了1。這種大的變化可能會引起網絡的其他神經元的行為以一種很復雜的方式完全發生改變。因此,當你的"9"可能現在是分類正確的,但是網絡在所有其他圖像上的行為可能以一種難以控制的方式改變。這使得很難通過逐漸修改(微調)權值和偏置使得網絡越來越接近期望的輸出結果。
於是就引進了一種新的神經元類型-sigmoid神經元。它和感知機很類似,但是對權值和偏置的一點小變化能引起輸出的一點小變化。而不是像感知機那樣使得輸出發生了突變(原因在於感知機實際上是一個躍階函數)。
區別在於sigmoid神經元的輸出不是0或者1,而是0和1之間的任何值。比如說,0.638…。同樣地,sigmoid神經元的每個輸入x也是有權重w和偏置b。那么現在輸出變成了,稱為sigmoid函數(還有個名字叫logistic函數),定義為:
所以,輸入為,權重為
以及偏置為b的sigmoid的神經元的輸出為:
讓我們來看看它和感知機神經元的區別吧。現在假設是一個很大的正數。那么
,因此
。換句話說就是,當
很大而且是正的,那么sigmoid神經元的輸出就接近1,就和之前說的感知機一樣。另外一方面,我們假設
是非常小的負數(絕對值大)。那么
,所以
。
這是sigmoid函數的圖像。它可看成是下面的躍階函數的光滑版本:
如果是一個躍階函數(step function),那么sigmoid神經元將是一個感知機,因為輸出將要么是1要么是0,這取決於
是正的還是負的。通過使用實際的
函數,我們得到了一個光滑版的感知機。
的光滑性意味着權重中的小的變化
以及偏置小的變化
將導致一個輸出小的變化
。由微積分知識,我們可以得到輸出變化
可以由下面的式子計算:
我們可以這樣來理解這個式子,輸出變化是權值變化
和偏置變化
的一個線性函數。這種線性關系使得選擇權值和偏置中的小的變化更容易達到任何期望的輸出變化值。
我們該如何解釋一個sigmoid神經元的輸出?顯然,在感知機和sigmoid神經元之間最大的不同是sigmoid神經元不只輸出0或者1。它們能夠使輸出為任何的實數在0和1之間,因此像0.173…和0.689這樣的值是正確的輸出值。這是很有用的,例如,如果我們想要使用輸出值來代表一幅圖像輸入中像素的平均密度。但是有時候這卻也是比較棘手的問題,假設我們想要一個輸出來表示是"輸入圖像是9"或者"輸入圖像是8"。顯然,如果輸出是0或者1,就像在感知機中那樣的,那么就很容易做到的。但是在實踐中我們能夠建立一種約定來達到這種目的,例如,通過決定把任何至少是0.5的輸出解釋為一個"9"的圖像,而不任何少於0.5的輸出值解釋為一個"不是9"的圖像。關鍵是為什么這樣可行呢?
神經網絡的結構
假設我們下面的神經網絡:
最左邊的被稱為輸入層,其中的神經元是輸入神經元。最右邊的是輸出層包含了輸出神經元。中間的被稱為一個隱藏層,稱為隱藏層是因為它既不是輸入也不是輸出,其他沒有什么原因了。可以有多個隱藏層:
網絡中的輸入和輸出層的設計通常是簡單的。例如,假設我們正在決定一個手寫圖像是不是一個"9"。一個自然的方式來設計網絡就是編碼輸入圖像像素的強度。如果圖像是一個64*64灰度級的圖像,那么我們就有4096=64 x 64輸入神經元,它們的強度級別在0和1之間。輸出層將只包含一個單神經元,輸出值小於0.5的時候表示輸入圖像不是一個9,反之是一個9。雖然,輸入層和輸出層的設計比較直觀簡單,但是隱藏層的設計就不一樣了。研究者們開發了許多設計隱藏層的啟發式方法來幫助人們獲得它們想要的網絡的行為。例如,這樣的啟發式方法能夠被用來幫助決定怎樣權衡隱藏層的個數,因為訓練網絡需要時間。后面我們將會遇到幾種這樣的設計啟發式方法。
目前為止我們討論的是前向神經網絡,因為每一層的輸入都是前一層的輸出。這意味着網絡中沒有循環,信息總是前向反饋,從不往后反饋。當然也存在其他的神經網絡結構里有前向循環,比如說recurrenct neural networks。這些模型的思想是使神經元被激活持續某段時間,在它變得靜止之前。這個激活能夠刺激其他的神經元,它們將在之后會被激活,也是一段時間。循環在這種模型中沒有導致問題,因為一個神經元輸出僅僅影響了它的輸入在某個后續時間,而不是即刻被激活。
使用梯度下降來學習
我們用來分類數字的算法首先是要找到使得對於所有的訓練輸入x,神經網絡的輸出都接近於y(x)(它是已標記過的手寫數字的類別)的那些權值和偏置。為了量化所有訓練輸入x的輸出有多接近y(x),我們定義了一個損失函數:
(y(x)是實際正確的標記過的手寫數字的類別,a是神經網絡計算出來的類別)。我們把C稱為二次損失函數,也有個別名叫均方誤差MSE(mean squared error)。現在的目標就是要最小化C,並求出相應的w,b就可以了。有個著名的方法叫梯度下降(gradient descent)。
現在假設我們正在最小化某個函數C(v)。v是個向量,現在我們考慮兩個變量的函數C,我們稱之為
和
。這是一個連續多元函數的最小值問題。大家首先會想到積分的方法,我們可以計算偏導數,然后找出C的所有極值點,最后找出最小值點。運氣好的話,你遇到的C可能是只有1個或2個變量。但是當我們有許許多多地變量呢?(神經網絡中幾十億個權值和偏置都是有可能的!)所以使用積分的方法是不可行的。
於是前輩們提出了梯度下降的方法。可以舉一個通俗的例子來理解這個算法,我們把這個需要尋找最小值的函數想象成一個山谷,想象有一個球從斜坡上滾下來,經驗告訴我們這個球最終會滾到山谷的底部。我們可以借助這個思想來尋找函數的最小值。隨機地選擇一個點,讓小球從這個點往下滾,但是我們的算法並不是嚴格按照這個模型來進行,因為小球在現實生活中是受重力影響往下滾的,而我們的算法並不考慮這個。所以它是忽略了一些條件,假設把小球沿着方向移動一個很小的量
,在
方向上移動另一個很小的量
。微積分的知識告訴我們損失函數C(含有二個變量v1和v2)的變化如下:
記住我們的目的是使C值最小,所以C的變化如果是負的,就說明C在減小,也就是這個球正在往山谷下面滾。關鍵是如何找到選擇
和
的方式呢?為了方便描述,我們引進一個新的變量
,然后定義C的梯度為偏導數向量,
。我們用
表示梯度,也就是:
所以我們重寫損失函數C的變化為:
問題變成了如何選擇來使得
為負數。前輩們是這樣構造的,令:
這里的是較小的,正的參數(它有個好聽的名字叫學習率)。等式(9)告訴我們:
現在我們發現,這就保證了
,也就是C將總是在減小,從不會增加,到現在我們也就知道了怎樣選擇v,即,我們使用等式(10)來計算
的一個值,然后沿着v方向把小球移動一個量:
接下來再次使用這個更新規則來繼續小球的另外一個增量的移動,我們不斷地保持這樣做,那么將保持減少C直到我們達到一個全局最小值。總的來說,梯度下降算法的工作原理就是重復地計算梯度,然后在其相反的方向移動,使得小球沿着山谷向下走。用圖形來看就是:
為了使梯度下降正確地工作,我們需要選擇足夠小地學習率使得等式(9)是一個好的近似。如果我們沒有這樣做,可能最終會以
結束(不要懷疑這一點),這顯然不是我們想要的結果。同時,我們也不希望選擇太小的學習率,因為這將獲得一個很小的
變化,將導致梯度下降算法工作地非常慢。在實踐中,
通常是不一樣的,使得等式(9)保持一個好的近似,但是算法也不能太慢。后面我們將看到怎么選取學習率
。剛才已經解釋了當C是一個只有2個變量的函數的梯度下降,事實上對於多個變量也是差不多的,只是把v向量的維數增加一下。梯度下降算法的更新規則總是這樣的:
但是,這個規則並不總是正確工作,有一些情況會導致錯誤以及阻止梯度下降找到C的全局最小值,后面會提到這個問題。不過,不用擔心,梯度下降通常在實踐中都工作得很好,在神經網絡中我們將發現它是最有力的最小化損失函數的一種來幫助網絡學習的方式。
實際上,梯度下降是一種尋找最小值的策略。假設我們嘗試在某個方向上移動來盡可能多地減小C。這就等價於最小化
。我們將限制移動的步長大小為:
對於某個小的固定的
。換句話說,我們想要一個是固定步長的移動,以及我們嘗試找到盡可能多地減小C的移動方向。可以被證明最小化
的
是
。
接下來的問題就是如何把梯度下降應用到一個神經網絡中呢?目的還是找到使得等式(6)中的損失函數最小的權值和偏置
。也就是把剛才的
換成權值
和偏置
,換句話說就是現在梯度向量變成了:
和
。所以梯度下降規則就變成了:
只要重復應用這個規則就能使"小球往山下移動"以及找到損失函數的一個最小值。應用梯度下降規則存在很多挑戰,后面會深入分析這個問題。現在只提一個問題。我們注意到損失函數有形式,即它是單個訓練樣本的損失
的一個平均值。在實踐中,為了計算梯度
我們需要單獨地為每個訓練輸入x計算梯度,然后算出它們的平均值,
。不幸地是,當訓練輸入的個數很大的時候,這將十分耗費時間,這會導致學習地很慢。
於是出現了另外一種想法,叫隨機梯度下降,它是用來加速學習的。主要想法就是通過計算
隨機選擇出來的訓練輸入的這個小樣本的,這將幫助加速梯度下降,從而提高算法的學習速度。更精確地說,隨機梯度下降通過隨機挑選出m(相對小的)個隨機選擇的訓練輸入。我們標記這些隨機訓練輸入為
並且給它們取個新的名字叫mini-batch。問題是這個m我們選多少最為合適呢?我們期望
的平均值能夠粗糙地等於全部所有的
的平均,即:
其中第2個和是訓練數據的全部集合。交換一下順序就會得到:
我們能夠通過計算僅僅是隨機選擇的mini-batch的梯度來估計全部的梯度。所以現在我們的基於隨機梯度下降方法的神經網絡的梯度更新規則就變成了:
其中的和是當前mini-batch中的所有訓練樣本。然后我們挑選出另外一個隨機選擇的mini-batch並訓練它們,等等,直到我們已經使用完所有的訓練輸入,我們把這個說成是完成了一個epoch的訓練。在這個時候,我們就開始一個新的訓練epoch。
實現分類手寫數字的神經網絡
接下來我們以MNIST手寫數字庫來實現我們的神經網絡。所有版權歸《神經網絡與深度學習》的作者,本文在翻譯的基礎上增加自己的理解。
關於MNIST數據集的詳細信息見其官網。它被分成60,000幅訓練圖片和10,000幅測試圖片。現在我們再將其訓練圖片分成2部分:50,000幅圖片的集合用來訓練我們的神經網絡,以及一個單獨的10,000幅圖像為驗證集。在這里我們先不使用驗證數據,但是我們會發現在計算怎樣設置某些神經網絡的超參數是很有用的-像學習率這樣的東西,等等,因為它們並不能直接地被學習算法選擇出來。盡管驗證數據不是原始MNIST指定的,許多人以這種方式使用MNIST,並且驗證數據的使用在神經網絡中是很普遍的。從現在開始,當我說"MNIST訓練數據"的時候,我們是指我們的50,000圖像數據集,並不是原來的60,000圖像數據數據集。
除了使用MNIST數據,我們也需要一個叫Numpy的Python庫,為了快速線性代數的計算。我們使用一個Network類來表示一個神經網絡。下面是我們用來初始化一個Network對象的代碼:
class Network():
def __init__(self,sizes):
self.num_layers=len(sizes)
self.sizes=sizes
self.biases=[np.random.randn(y,1) for y in sizes[1:]]
self.weights=[np.random.randn(y,x)
for x,y in zip(sizes[:-1],sizes[1:])]
sizes是一個列表,它包含了相關層的神經元個數。例如,如果我們想要創建一個第一層含有2個神經元,第二層含有3個神經元,最后一層含有1個神經元的Network對象,可以這樣寫:
net=Network([2,3,1])
Network對象中的偏置和權值都是被隨機地初始化的,使用Numpy的np.random.randn函數來生成均值為0,標准差為1的高斯分布,np.random.randn(y,1)表示產生均值為0,標准差為1的y行1列的數組的高斯分布。這里權值是shape=(y,x)的數組,我們在初始化權值的時候,除了輸入層,其他每一層都有一個權值向量,並且這一層的權值向量的性質由該層神經元個數和其前一層神經元個數決定,舉個例子,sizes=[2,3,1]的網絡結構的權值向量有2個,一個是第1層和第2層之間的連接以及第2層和第3層之間的連接。表示為w(1)和w(2),其中w(1)的形狀為(3,2),w(2)的形狀為(1,3)。作者寫的代碼很巧妙,由於分別要得到y和x的值,利用zip(sizes[:-1],sizes[1:])並行了2個列表的值。同理偏置只有1層的神經元個數決定(從第2層開始,因為第一層是輸入層),對應的代碼是for y in sizes[1:]。這個隨機化初始給了我們的隨機梯度下降算法一個開始的地方。后面我們會介紹更好地初始化權重和偏置的方式。
然后我們定義一個sigmoid函數,並使用Numpy來定義一個那個函數的一個向量化形式:
def sigmoid(z):
return 1.0/(1.0+np.exp(-z))
sigmoid_vec=np.vectorize(sigmoid) #使函數矢量化
接下來,為Network類增加一個feedforward方法,即給定網絡的一個輸入a,返回相應的輸出。所有方法要做的就是對每一層應用等式(22):
a是第二層的神經元的激活的向量。為了得到a'我們用權值矩陣w乘以a,然后加上偏置向量。然后應用函數。
def feedforward(self,a):
for b,w in zip(self.biases,self.weights):
a=sigmoid_vec(np.dot(w,a)+b)
return a
下面是stochastic gradient descent的代碼:
def SGD(self,training_data,epochs,mini_batch_size,eta,test_data=None):
if test_data:
n_test=len(test_data)
n=len(training_data)
for j in xrange(epochs):
random.shuffle(training_data)
mini_batches=[
training_data[k:k+mini_batch_size]
for k in xrange(0,n,mini_batch_size)
]
for mini_batch in mini_batches:
self.update_mini_batch(mini_batch,eta)
if test_data:
print "Epoch {0}:{1} / {2}".format(j,self.evaluate(test_data),n_test)
else:
print "Epoch {0} complete".format(j)
training_data是元組(x,y)的一個列表,它代表了訓練輸入以及相應的期望輸出。變量epochs表示用來訓練的epochs,以及當取樣本時mini-batches的大小。eta是學習率,如果提供了可選參數test_data,那么程序將評估這個網絡在每個訓練的epoch之后,並且打印出局部的進展。這對於追蹤過程是特別有用的,但是實質上會減慢速度。
在每個epoch中,它通過隨機地shuffling訓練數據,然后把它分成合適大小的mini-batches。這是一種隨機抽樣訓練數據的簡單方式。然后,對於每個mini_batch,我們應用一次梯度下降算法,這是通過代碼self.update_mini_batch(mini_batch,eta)來實現的,它根據梯度下降算法的一次迭代更新權值和偏置,僅僅在mini_batch中使用訓練數據。下面是update_mini_batch方法的代碼:
def update_mini_batch(self,mini_batch,eta):
nabla_b=[np.zeros(b.shape) for b in self.biases] #因為我們要計算梯度的平均值
nabla_w=[np.zeros(w.shape) for w in self.weights] #梯度就是損失函數對權值和偏置分別的偏導
for x,y in mini_batch:
delta_nabla_b,delta_nabla_w=self.backprop(x,y)#利用反向傳播算法求梯度(偏導),對於每一個訓練輸入對(x,y)
nabla_b=[nb+ dnb for nb,dnb in zip(nabla_b,delta_nabla_b)] #計算所有的梯度(對偏置b)的和
nabla_w=[nw+dnw for nw,dnw in zip(nabla_w,delta_nabla_w)] #計算所有的另外一個梯度(對權值w)的和
self.weights=[w-(eta/len(mini_batch)) * nw
for w,nw in zip(self.weights,nabla_w)] #根據更新規則更新所有的權值
self.biases=[b-(eta/len(mini_batch)) * nb
for b,nb in zip(self.biases,nabla_b)] #根據更新規則更新所有的偏置值
關鍵的代碼在於:
delta_nabla_b,delta_nabla_w=self.backprop(x,y)
這就要引出著名的BP算法了,它是一種快速計算損失函數的梯度的一種方法。update_mini_batch是通過為mini_batch里的每個訓練樣本計算這些梯度,然后更新self.weights以及self.biases。這里關於BP算法暫時先不探討,只要知道self.backprop的代碼返回訓練樣本x關聯的損失的相應的梯度值。
>>>import network
>>>net=network.Network([784,30,10])
最終,我們將使用隨機梯度下降來學習MNIST的training_data 30 epochs,其中mini-batch的大小為10,以及一個學習率=3.0。
>>>net.SGD(training_data,30,10,3.0,test_data=test_data)
詳細代碼在這里。
參考文獻《neural networks and deep learing》
轉載 http://www.gumpcs.com/index.php/archives/799