曾梓龍 材料班 2014301020006
前言
最近的研究課題是神經(jīng)網(wǎng)絡(luò),偶然在知乎上看到了一篇關(guān)于入門深度學(xué)習(xí)的文章,介紹了一下從數(shù)學(xué)知識(shí)到機(jī)器學(xué)習(xí),再到神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí)的資源,因此開始通過看Andrew Ng的機(jī)器學(xué)習(xí)課程入門機(jī)器學(xué)習(xí),并在網(wǎng)上在線看Neural Networks and Deep Learning這份線上教程學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí)。
在花了幾天時(shí)間看線上教程,我開始著手操作識(shí)別手寫數(shù)字圖像的任務(wù),為了簡(jiǎn)化代碼,我使用python構(gòu)建神經(jīng)網(wǎng)絡(luò)。
背景
首先觀察下面的一副圖片

我們可以很容易地辨識(shí)這些數(shù)字是504192。通過對(duì)大腦的研究,我們知道,人類大腦有大約1000億個(gè)神經(jīng)元,在這些神經(jīng)元之間有一萬億個(gè)鏈接。

而在大腦的每個(gè)半球,人類有一個(gè)初級(jí)視覺皮層V1,大概有1.4億個(gè)神經(jīng)元,以及數(shù)百億的相互連接,但是人類視覺不僅涉及V1,而且還有V2,V3,V4,V5這一系列的視覺皮層。

實(shí)際上,人類大腦就是一臺(tái)超級(jí)計(jì)算機(jī),幾百萬年來的調(diào)整和適應(yīng)使得人類能夠辨識(shí)外部世界,因此用計(jì)算機(jī)模擬書寫數(shù)字是很艱難的。
這里有一個(gè)問題值得思考。為什么我們能夠辨識(shí)手寫數(shù)字9?是因?yàn)槲覀兲焐湍軌虮孀R(shí)數(shù)字9還是因?yàn)槲覀儗W(xué)習(xí)過如何辨識(shí)數(shù)字9?
答案顯然是我們學(xué)習(xí)過如何辨識(shí)并書寫數(shù)字9.
從這個(gè)問題出發(fā)來看,我們也可以用計(jì)算機(jī)模擬人類學(xué)習(xí)的過程來獲得辨識(shí)手寫數(shù)字圖像的能力。
近年來,神經(jīng)網(wǎng)絡(luò)在這類問題的解決上取得了比較大的進(jìn)展。而神經(jīng)網(wǎng)絡(luò)模擬的就是人類大腦的工作方式——使用大量的訓(xùn)練數(shù)據(jù)訓(xùn)練神經(jīng)網(wǎng)絡(luò),使神經(jīng)網(wǎng)絡(luò)的從這些數(shù)據(jù)中獲取“經(jīng)驗(yàn)”,提高辨識(shí)手寫數(shù)字圖像的準(zhǔn)確率。
何為神經(jīng)網(wǎng)絡(luò)以及神經(jīng)網(wǎng)絡(luò)的工作原理
Perceptrons(感知器)
在介紹神經(jīng)網(wǎng)絡(luò)之前,我們首先需要理解Perceptrons(感知器)的概念。Perceptrons實(shí)際上就是一個(gè)人工模擬的神經(jīng)元,每個(gè)感知器有若干個(gè)二進(jìn)制輸入以及一個(gè)二進(jìn)制輸出。

上面的這個(gè)Perceptrons有三個(gè)輸入:x1、x2和x3,每條輸入都有相應(yīng)的權(quán)重:w1、w2和w3.
Perceptrons的輸入是由輸入的加權(quán)之和決定的,神經(jīng)網(wǎng)絡(luò)的研究人員通常用下面的這個(gè)公式來確定Perceptrons的輸出:

也就是說,當(dāng)Perceptrons的輸入加權(quán)和大于某一閾值時(shí),輸出1;小于某一閾值時(shí)輸出0.
這和大腦神經(jīng)元的工作原理比較類似,當(dāng)神經(jīng)元接收到來自其他神經(jīng)元的信號(hào)時(shí),經(jīng)過處理之后再發(fā)送給其他的神經(jīng)元。
在實(shí)際應(yīng)用中,為了計(jì)算方便,我們將上面的式子改為:

其中:

b為bias.
同時(shí)令X為輸入數(shù)據(jù)的矩陣,W為相應(yīng)的權(quán)重矩陣,即:
因此輸出表達(dá)式為:

我們?cè)谥耙舱f過,人類大腦識(shí)別圖像是通過多層的視覺皮層來解析的信息,因此我們這里開始構(gòu)建一個(gè)三層的神經(jīng)網(wǎng)絡(luò):

第一層稱為輸入層,最后一層稱為輸出層,第一層和最后一層之間的層被稱為隱藏層。
我們首先考慮一下這樣一個(gè)Perceptrons,有兩個(gè)輸入端,每個(gè)輸入端的權(quán)重為-2,結(jié)點(diǎn)bias為3:

如果我們輸入00,則有:
(-2)0+(-2)0+3 = 3>0,類似的,輸入為01或者10時(shí)輸出都為1,但是當(dāng)輸入為11時(shí),輸出為0,因此這樣的一個(gè)Perceptrons可以看作一個(gè)與非門。
在數(shù)字邏輯中,我們知道,邏輯門的組合可以組成一定的邏輯開關(guān),如下圖所示:

將上圖中的與非門換成之前討論的Perceptrons,則有:

減少一些線條,并將輸入端轉(zhuǎn)換為Perceptrons,則有:

從上面的例子可以看到,不同的Perceptrons的組合網(wǎng)絡(luò)可以具有一定的邏輯功能,利用神經(jīng)網(wǎng)絡(luò)識(shí)別手寫數(shù)字也是一樣的原理。
Sigmod 神經(jīng)元
在實(shí)際應(yīng)用中,并不會(huì)使用之前介紹的Perceptrons組成的神經(jīng)網(wǎng)絡(luò),那樣構(gòu)建的神經(jīng)網(wǎng)絡(luò)不夠穩(wěn)定,在使用訓(xùn)練集訓(xùn)練網(wǎng)絡(luò)的時(shí)候網(wǎng)絡(luò)權(quán)重、bias甚至網(wǎng)絡(luò)的輸出的變化非常大。因此,往往使用Sigmod神經(jīng)元。
Sigmod神經(jīng)元里的Perceptrons和之前的Perceptrons并無兩樣,但是Perceptrons的輸出output的值并非0或1,相反,output 為:
)
其中σ稱為sigmoid函數(shù),sigmoid函數(shù)定義如下:
\equiv \frac{1}{1+e^{-z}}.)
因此對(duì)于輸入為x1,x2,...,輸入權(quán)重為w1,w2,...,且bias為b時(shí),sigmoid神經(jīng)元的輸出為:
sigmoid函數(shù)σ(z)的圖像如下圖所示:

當(dāng)z大于0時(shí),sigmoid函數(shù)小于1大于0.5,當(dāng)z小于0時(shí),sigmoid函數(shù)大于0小于0.5.

這是之前剛開始介紹的Perceptrons的輸出函數(shù)(Step function)的圖像
sigmoid function與step function相比更為平滑,當(dāng)權(quán)重w、bias b有微小變化時(shí),sigmoid函數(shù)的輸出的變化也很微小。因此一般采用sigmoid函數(shù)作為輸出函數(shù)。
從sigmoid函數(shù)圖像可以看到,sigmoid函數(shù)的輸出是連續(xù)變化的,可能是0.173或者0.689,那么如何界定sigmoid的輸出值以確定output值?
我們一般界定,當(dāng)sigmoid函數(shù)大于0.5時(shí)output為1,當(dāng)sigmoid函數(shù)小于0.5時(shí),output為0
識(shí)別手寫數(shù)字的神經(jīng)網(wǎng)絡(luò)搭建
首先,我們識(shí)別一個(gè)手寫數(shù)字:

為了識(shí)別單個(gè)數(shù)字,我們使用一個(gè)三層的神經(jīng)網(wǎng)絡(luò):

由于我們的手寫數(shù)字是一個(gè)28*28的像素的圖像,因此手寫數(shù)字圖像中一共有784個(gè)像素,這些像素塊經(jīng)過編碼后輸入到神經(jīng)網(wǎng)絡(luò)中,因此這個(gè)三層神經(jīng)網(wǎng)絡(luò)的輸入端有784個(gè)神經(jīng)元。
輸入端值為1時(shí)代表像素塊為1,值為0時(shí)代表像素塊為白色,當(dāng)輸入端值介于0到1之間時(shí),代表深度不同的灰色。
第二層也就是隱藏層,在圖中僅有15個(gè)神經(jīng)元,不過在實(shí)際情況中有可能不只15個(gè)神經(jīng)元。
第三層也就是輸出層,有十個(gè)神經(jīng)元,如果哪個(gè)神經(jīng)元輸出為1,則代表神經(jīng)網(wǎng)絡(luò)認(rèn)定輸入的圖像為相應(yīng)的數(shù)字。例如,如果第一個(gè)神經(jīng)元輸出為1,則神經(jīng)網(wǎng)絡(luò)認(rèn)定輸入的手寫數(shù)字為0.
梯度下降算法
之前我們說過,神經(jīng)網(wǎng)絡(luò)可以通過訓(xùn)練數(shù)據(jù)集來獲得辨識(shí)圖像的能力,其模擬的就是人類的學(xué)習(xí)能力,在這里我們提出一個(gè)梯度下降學(xué)習(xí)算法模擬學(xué)習(xí)的過程。
我們使用向量x表示輸入的值,則x是一個(gè)784維度的向量,輸入端用y表示,則有
)
其中,y是一個(gè)10維的向量。
舉個(gè)例子來說,如果一個(gè)訓(xùn)練圖像被神經(jīng)網(wǎng)絡(luò)認(rèn)定為6,則
^T)
就是神經(jīng)網(wǎng)絡(luò)的輸出向量。
為了不斷調(diào)整神經(jīng)網(wǎng)絡(luò)內(nèi)的權(quán)重和bias,我們提出梯度下降算法,首先定義一個(gè)代價(jià)函數(shù):
 \equiv \frac{1}{2n}\sum_x ||y(x)-a||^2)
其中w為神經(jīng)網(wǎng)絡(luò)中所有的權(quán)重值,b為所有的bias,n為輸入的訓(xùn)練集的數(shù)量,a是當(dāng)輸入為x時(shí)神經(jīng)網(wǎng)絡(luò)的輸出向量。由此可見,w、b和n為a的自變量。

如上圖為代價(jià)函數(shù)C(w,b)的等勢(shì)圖,中心處為代價(jià)函數(shù)圖像的最低點(diǎn),越往外,點(diǎn)所處的位置越高。因此為了求得代價(jià)函數(shù)C的極小值,我們需要找到相應(yīng)代價(jià)函數(shù)的圖像的最低點(diǎn)。

更直觀一點(diǎn)就像上面這幅圖所示,小球處于某一位置,總有向最低點(diǎn)運(yùn)動(dòng)的趨勢(shì)。為了在數(shù)學(xué)語言上描述這一過程,我們提出梯度下降算法:

令:
^T)
)
有:

因?yàn)榇鷥r(jià)函數(shù)需要沿梯度下降,所以應(yīng)當(dāng)有

令:

其中,η是一個(gè)很小的正參數(shù):

更新參數(shù)v,有:

因此,分別更新權(quán)重w和偏重b:


但是在實(shí)際情況中,使用梯度下降學(xué)習(xí)算法訓(xùn)練神經(jīng)網(wǎng)絡(luò)效率比較低,因此我們采用隨機(jī)梯度下降學(xué)習(xí)算法。
隨機(jī)梯度下降學(xué)習(xí)算法的基本思想是在所有訓(xùn)練集中隨機(jī)挑選m個(gè)訓(xùn)練集輸入到神經(jīng)網(wǎng)絡(luò)中進(jìn)行訓(xùn)練,m足夠大以至于可以認(rèn)為這m個(gè)訓(xùn)練集的訓(xùn)練效果相當(dāng)于所有訓(xùn)練集訓(xùn)練神經(jīng)網(wǎng)絡(luò)的效果,用數(shù)學(xué)公式表達(dá)就是:

也就是說,w與b的更新函數(shù)可以改為:


編程實(shí)現(xiàn)
先建立一個(gè)Network類:
class Network(self):
def __init__(self,sizes):
self.num_layers = len(sizes)
self.sizes = sizes
# if we want to create a Network object with 2 neurons
# in the first layer,3 neurons in the second layer,and
# 1 neurons in the final layer,we can do this in this
# code:
# net = Network([2,3,1])
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:])]
我們建立的是一個(gè)三層的神經(jīng)網(wǎng)絡(luò),傳入一個(gè)三維的向量,例如:
如果我們想在輸入層放入2個(gè)神經(jīng)元,隱藏層放入3個(gè)神經(jīng)元,輸出層放入1個(gè)神經(jīng)元,則將[2,3,1]傳入神經(jīng)網(wǎng)絡(luò)中。
net = Network([2,3,1])
biases和weight則先用random函數(shù)初始化,隨機(jī)設(shè)置。
定義sigmoid函數(shù):
def sigmod(z):
return 1.0/(1.0+np.exp(-z))
每一層神經(jīng)元的輸出由上一層神經(jīng)元的輸入決定,由數(shù)學(xué)公式表達(dá)就是:
其中,a是上一層神經(jīng)元的輸出,即這一神經(jīng)元的輸入,w是相應(yīng)兩層神經(jīng)元之間的權(quán)重向量。
同時(shí)在Network類中定義feedforward方法:
def feedforward(self,a):
for b,w in zip(self.biases,self.weights):
a = sigmod(np.dot(w,a)+b)
return a
同時(shí)在Network類中定義SGD方法,SGD方法使神經(jīng)網(wǎng)絡(luò)能夠使用隨機(jī)梯度下降算法進(jìn)行學(xué)習(xí):
def SGD(self,training_data,epochs,mini_batch_size,eta,test_data=None):
# epochs:the number of epochs to train for
# mini_batch_size:the size of the mini-batches to use
# when sampling.eta is the learning rate /eta.
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)]
# catch k in every 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))
在之前討論隨機(jī)梯度下降學(xué)習(xí)算法的時(shí)候,我們談到w與b要隨神經(jīng)網(wǎng)絡(luò)的學(xué)習(xí)不斷更新,在這里定義一個(gè)update_mini_batch方法:
def update_mini_batch(self,mini_batch,eta):
# update the network's weights and biases by applying gradient
# descent using backpropagation to a single mini batch
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)
nabla_b = [nb+dnb for nb,dnb in zip(nabla_b,delta_nabla_b)]
nabla_w = [nw+dnw for nw,dnw in zip(nabla_w,delta_nabla_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)]
完整代碼已經(jīng)托管到github上了。
在使用神經(jīng)網(wǎng)絡(luò)的時(shí)候,還需要一個(gè)python文件將訓(xùn)練集輸入到神經(jīng)網(wǎng)絡(luò)中,我使用了國(guó)外某大佬的mnist_loader.py文件,也托管在github上了。
運(yùn)行操作
打開python shell,切換到程序文件目錄,輸入下列代碼:

在進(jìn)入python交互環(huán)境之后,第一行和第二行代碼導(dǎo)入了訓(xùn)練集,第三行和第四行代碼創(chuàng)建了一個(gè)第一層為784個(gè)神經(jīng)元 第二層為30個(gè)神經(jīng)元 第三層為10個(gè)神經(jīng)元的神經(jīng)網(wǎng)絡(luò)。
再輸入下列這行代碼啟動(dòng)神經(jīng)網(wǎng)絡(luò):
net.SGD(training_data,30,10,3.0,test_data=test_data)
這行代碼的意思是訓(xùn)練30次,同時(shí)挑選10組訓(xùn)練集作為隨機(jī)梯度下降算法的訓(xùn)練集,學(xué)習(xí)速率η為3.0,運(yùn)行一段時(shí)間后,得到:

經(jīng)過30次訓(xùn)練之后,神經(jīng)網(wǎng)絡(luò)辨識(shí)圖像的準(zhǔn)確度可以達(dá)到94.89%,實(shí)際上在第28次訓(xùn)練的時(shí)候準(zhǔn)確度已經(jīng)達(dá)到了94.99%.
如果我們將這個(gè)神經(jīng)網(wǎng)絡(luò)的第二層改為100個(gè)神經(jīng)元,神經(jīng)網(wǎng)絡(luò)的辨識(shí)度可以達(dá)到多少?
輸入下列代碼:
net = network.Network([784,100,10])
啟動(dòng)神經(jīng)網(wǎng)絡(luò),我們可以將神經(jīng)網(wǎng)絡(luò)的辨識(shí)率提高到96%以上。
重新回到第二層為30個(gè)神經(jīng)元的情況上,如果將學(xué)習(xí)速率η提高到20的話,將程序運(yùn)行一遍得到:

可以發(fā)現(xiàn),神經(jīng)網(wǎng)絡(luò)辨識(shí)圖像的準(zhǔn)確率有所下降。
總結(jié)
訓(xùn)練神經(jīng)網(wǎng)絡(luò)所需的計(jì)算量十分巨大,用普通的筆記本運(yùn)行整個(gè)程序需要花費(fèi)比較長(zhǎng)的一段時(shí)間,因此,在實(shí)際應(yīng)用中一般采用GPU來訓(xùn)練神經(jīng)網(wǎng)絡(luò)。在實(shí)際模擬的過程中,我們可以發(fā)現(xiàn),神經(jīng)網(wǎng)絡(luò)的辨識(shí)率雖然可以提高到95%以上,但是在這之后其辨識(shí)率很難得到提高,這迫使我們不斷發(fā)展神經(jīng)網(wǎng)絡(luò)模型。