構(gòu)建神經(jīng)網(wǎng)絡(luò)來識(shí)別手寫數(shù)字

曾梓龍 材料班 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的輸出:

![](http://latex.codecogs.com/png.latex?output={{1,if \sum{j}w_jx_j>threhold}^{0,if \sum_{j}w_jx_j \leq threhold})

也就是說,當(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ì)算方便,我們將上面的式子改為:

![](http://latex.codecogs.com/png.latex?output={{1,if \sum{j}w_jx_j+b>0}^{0,if \sum_{j}w_jx_j+b \leq 0})

其中:
![](http://latex.codecogs.com/png.latex?b\equiv -threhold)

b為bias.

同時(shí)令X為輸入數(shù)據(jù)的矩陣,W為相應(yīng)的權(quán)重矩陣,即:

因此輸出表達(dá)式為:

![](http://latex.codecogs.com/png.latex?output={_{1,if\ \vec{w}\cdot\vec{x}+b>0}^{0,if\ \vec{w}\cdot\vec{x}+b \leq 0})

我們?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 為:

![](http://latex.codecogs.com/png.latex?\sigma(w\cdot x+b))
其中σ稱為sigmoid函數(shù),sigmoid函數(shù)定義如下:
![](http://latex.codecogs.com/png.latex?\sigma(z)\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表示,則有

![](http://latex.codecogs.com/png.latex? y = y(x))

其中,y是一個(gè)10維的向量。
舉個(gè)例子來說,如果一個(gè)訓(xùn)練圖像被神經(jīng)網(wǎng)絡(luò)認(rèn)定為6,則

![](http://latex.codecogs.com/png.latex? y = (0,0,0,0,0,0,1,0,0,0)^T)
就是神經(jīng)網(wǎng)絡(luò)的輸出向量。

為了不斷調(diào)整神經(jīng)網(wǎng)絡(luò)內(nèi)的權(quán)重和bias,我們提出梯度下降算法,首先定義一個(gè)代價(jià)函數(shù):
![](http://latex.codecogs.com/png.latex? C(w,b) \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é)語言上描述這一過程,我們提出梯度下降算法:
![](http://latex.codecogs.com/png.latex?\Delta C\approx \frac{\partial C}{\partial v_1}\Delta v_1+\frac{\partial C}{\partial v_2}\Delta v_2)
令:
![](http://latex.codecogs.com/png.latex?\Delta \vec{v} = (\Delta v_1,\Delta v_2)^T)

![](http://latex.codecogs.com/png.latex?\bigtriangledown C=(\frac{\partial C}{\partial v_1},\frac{\partial C}{\partial v_2}))
有:
![](http://latex.codecogs.com/png.latex?\Delta C\approx \bigtriangledown C\cdot \Delta v)

因?yàn)榇鷥r(jià)函數(shù)需要沿梯度下降,所以應(yīng)當(dāng)有
![](http://latex.codecogs.com/png.latex?\Delta C <0)
令:
![](http://latex.codecogs.com/png.latex?\Delta v=-\eta \bigtriangledown C)
其中,η是一個(gè)很小的正參數(shù):

![](http://latex.codecogs.com/png.latex?\Delta C \approx -\eta \bigtriangledown C\cdot \bigtriangledown C=-\eta||\bigtriangledown C||^2)

更新參數(shù)v,有:
![](http://latex.codecogs.com/png.latex?v\to v^{'}=v-\eta \bigtriangledown C)

因此,分別更新權(quán)重w和偏重b:
![](http://latex.codecogs.com/png.latex?w_k \to w_k^{'} = w_k-\eta \frac{\partial C}{\partial w_k})
![](http://latex.codecogs.com/png.latex?b_l \to b_l^{'}=b_l-\eta \frac{\partial C}{\partial b_l})

但是在實(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á)就是:
![](http://latex.codecogs.com/png.latex?\frac{\sum^m_{j=1}\bigtriangledown C_{X_j}}{m}\approx \frac{\sum_x \bigtriangledown C_x}{n} = \bigtriangledown C)
也就是說,w與b的更新函數(shù)可以改為:
![](http://latex.codecogs.com/png.latex?w_k \to w_k^{'}=w_k-\frac{\eta}{m}\sum_j \frac{\partial C_X_j}{\partial w_k})
![](http://latex.codecogs.com/png.latex?b_l\to b_l^{'}=b_l-\frac{\eta}{m}\sum_j \frac{\partial C_X_j}{\partial b_l})

編程實(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ò)模型。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容