原書網(wǎng)址:http://neuralnetworksanddeeplearning.com/index.html
我們將通過解決一個(gè)具體問題來學(xué)習(xí)神經(jīng)網(wǎng)絡(luò)和深度學(xué)習(xí)背后的核心原理:教計(jì)算機(jī)識(shí)別手寫數(shù)字的問題。
源碼下載:https://github.com/mnielsen/neural-networks-and-deep-learning
第1章 使用神經(jīng)網(wǎng)絡(luò)識(shí)別手寫數(shù)字
http://neuralnetworksanddeeplearning.com/chap1.html
首先思考一個(gè)問題瓷耙,就是如果我們沒有接觸過神經(jīng)網(wǎng)絡(luò)的話剿另,我們應(yīng)該怎么用計(jì)算機(jī)識(shí)別下面的手寫數(shù)字?
(我想到的是去計(jì)算和已知的圖片的相似度逛钻,看看和誰最接近)
那神經(jīng)網(wǎng)絡(luò)會(huì)怎么做僚焦?
神經(jīng)網(wǎng)絡(luò)會(huì)使用訓(xùn)練集(大量的手寫數(shù)字)自動(dòng)推斷手寫數(shù)字的識(shí)別規(guī)則,并且通過增大訓(xùn)練集往往能夠進(jìn)一步提高識(shí)別的準(zhǔn)確性
感知器(Perceptrons):
要了解神經(jīng)網(wǎng)絡(luò)的內(nèi)部結(jié)構(gòu)曙痘,需要先了解感知器芳悲,感知器就是一種人工的神經(jīng)元,那他是怎么工作的呢边坤?
一個(gè)神經(jīng)元通常具有多個(gè)輸入和一個(gè)輸出名扛,這里引入一個(gè)新的名字weights
輸入?yún)?shù):x1,x2茧痒,x3
輸入權(quán)重:w1肮韧,w2,w3
不同的權(quán)重(weights)決定了不同的條件對結(jié)果的影響程度旺订,不同的閾值(threshold)決定了最終觸發(fā)動(dòng)作的難易程度弄企;如果閾值較低,就較容易觸發(fā)(1)区拳,否則就較難(0)拘领。通過修改權(quán)重(weights)和閾值(threshold),我們就能夠得到不同的決策模型劳闹。
一個(gè)神經(jīng)網(wǎng)絡(luò)有多個(gè)神經(jīng)元構(gòu)成:
該神經(jīng)網(wǎng)絡(luò)由三層構(gòu)成院究,其中第一層有三個(gè)比較簡單的決策神經(jīng)元構(gòu)成,簡單理解為可以做簡單的決定本涕,第二成的4個(gè)神經(jīng)元可以理解為在第一層的輸出中做出更復(fù)雜的決定业汰,直到最后做出復(fù)雜的決策。
對上面感知器進(jìn)行簡化:
b稱為偏置項(xiàng)(bias)菩颖,和公式(1)中的threshold的意義差不多样漆。你可以把偏置項(xiàng)(bias)看作是感知器輸出1的容易程度的度量,或是用來衡量感知器觸發(fā)的難易程度的度量晦闰。
簡單示例:
下面展示了如果實(shí)現(xiàn)一個(gè)NAND門:
計(jì)算公式:
輸出:
x1 | x2 | out |
---|---|---|
0 | 0 | 3 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | -1 |
sigmoid神經(jīng)元:
主要使用的神經(jīng)元模型是一種sigmoid神經(jīng)元模型放祟,sigmoid和之前的感知機(jī)基本相同,下面介紹一下sigmoid神經(jīng)元模型呻右。
和感知機(jī)一樣跪妥,有多個(gè)輸入和一個(gè)輸出,但是輸出不再是0或1声滥,而是介于(0, 1)之間的值
其中σ 稱為sigmoid函數(shù)
其中:
所以,sigmoid理解上可以理解為一個(gè)階躍函數(shù),sigmoid函數(shù)纽疟,像是一個(gè)平滑版的階躍函數(shù)罐韩;如果使用的是階躍函數(shù)的話,那么神經(jīng)元就是前面的感知器的實(shí)現(xiàn)污朽;由于sigmoid函數(shù)是一個(gè)平滑的函數(shù)散吵,所以weight或者bias的一個(gè)微小的改動(dòng),會(huì)導(dǎo)致結(jié)果產(chǎn)生一個(gè)小的改動(dòng)蟆肆。
在神經(jīng)網(wǎng)絡(luò)中矾睦,我們就是通過不斷的調(diào)整weight或者bias,使得最終整個(gè)網(wǎng)絡(luò)向我們預(yù)期的結(jié)果移動(dòng)颓芭。
通過微積分的只是來看一下weight或bias的微小改變是如何影響結(jié)果的:
神經(jīng)網(wǎng)絡(luò)的結(jié)構(gòu)
神經(jīng)網(wǎng)絡(luò)通常包含輸入層顷锰,輸出層柬赐,和若干個(gè)隱藏層亡问;輸入層和輸出層通常來說是非常簡單的,從手寫數(shù)字識(shí)別的例子中肛宋,如果輸入是28 * 28 的灰度圖像州藕,那我們就將會(huì)有 784 = 28 * 28的輸入神經(jīng)元;我們要預(yù)測輸入的時(shí)0 ~ 9之間的那個(gè)數(shù)組酝陈,那么輸出就有10個(gè)神經(jīng)元床玻,分別表示輸入是對應(yīng)圖片的可能性。應(yīng)該包含多少個(gè)隱藏層沉帮,以及每個(gè)隱藏層應(yīng)該包含多少個(gè)神經(jīng)元锈死,這些將會(huì)在以后涉及到。
到目前為止的我們稱整個(gè)網(wǎng)絡(luò)為前向傳播網(wǎng)絡(luò)(Feedforward neural networks)穆壕。網(wǎng)絡(luò)中既沒有環(huán)待牵,也沒有回退。
擴(kuò)展閱讀:(recurrent neural networks)
一個(gè)簡單的手寫數(shù)字識(shí)別神經(jīng)網(wǎng)絡(luò)
輸入層是由784 = 28 * 28個(gè)神經(jīng)元組成喇勋,輸入值是有0.0 ~ 1.0表示的像素的灰度值缨该。
本例中只包含一個(gè)隱藏層,有15個(gè)神經(jīng)元組成川背。
輸出層包含10個(gè)神經(jīng)元贰拿,編號從 0~9,可以理解為輸入圖片為對應(yīng)數(shù)字的可能性熄云,比如膨更,所有的輸出中編號為6的神經(jīng)元的值最高,那么就認(rèn)為輸入的圖片是6缴允。
通過梯度下降訓(xùn)練網(wǎng)絡(luò)
要想訓(xùn)練神經(jīng)網(wǎng)絡(luò)荚守,首先我們得有訓(xùn)練數(shù)據(jù)。在本例中我們使用MNIST data set作為訓(xùn)練集,該數(shù)據(jù)集包含兩部分健蕊,第一部分包含了60000張來自250個(gè)人的手寫數(shù)字菱阵,所有的都是28*28的灰度圖片。第二部分包含10000張用作測試集缩功。
輸入是784維的向量晴及,輸出是一個(gè)10維的向量。
識(shí)別結(jié)果為6:
代價(jià)函數(shù)(cost function):我們需要有一個(gè)公式嫡锌,來量化網(wǎng)絡(luò)的輸出和訓(xùn)練集真實(shí)結(jié)果之間的差異虑稼,從而直觀的了解整個(gè)模型在訓(xùn)練集上的表現(xiàn)。
二次成本函數(shù):均方誤差
其中n表示的是訓(xùn)練集的個(gè)數(shù)势木,a表示是輸入數(shù)據(jù)的真實(shí)結(jié)果蛛倦。
可以看出二次成本函數(shù)是非負(fù)數(shù),當(dāng)網(wǎng)絡(luò)的輸出結(jié)果接近于真實(shí)結(jié)果的時(shí)候啦桌,成本函數(shù)較小溯壶,相反則成本函數(shù)較大。
所以我們的目標(biāo)就是找到一組weight和bias甫男,使成本函數(shù)達(dá)到最小且改,這個(gè)過程我們將使用梯度下降來實(shí)現(xiàn)。
(為什么要引入二次成本函數(shù)開衡量板驳,而不是直接使用圖片正確識(shí)別的數(shù)量呢又跛?因?yàn)檎_識(shí)別圖片的數(shù)量對于weight和bias不是一個(gè)平滑的函數(shù),改變weight或bias可能不會(huì)引起正確識(shí)別數(shù)量的改變)
那么梯度下降是如何實(shí)現(xiàn)使代價(jià)方程取到最小值呢若治?
(為什么不直接計(jì)算極值位置慨蓝?因?yàn)槊鎸Τ汕先f的參數(shù),通過計(jì)算極值位置是不現(xiàn)實(shí)的)
梯度下降可以直觀的理解為一個(gè)球放在一個(gè)山谷中端幼,首先隨機(jī)的選擇一個(gè)起點(diǎn)礼烈,那么球每次都移動(dòng)一小段,那么球?qū)⒆詈舐涞揭粋€(gè)最值點(diǎn)上静暂。
假設(shè)只有兩個(gè)變量济丘,v1和v2
v1和v2的改變對代價(jià)方程的影響:
接下來就需要找到Δv1和Δv2,是ΔC為負(fù)數(shù)
其中
Δv表示移動(dòng)向量洽蛀,?C表示代價(jià)方程的梯度向量摹迷,?C反映了v的變化對C的影響,告訴了我們應(yīng)該怎樣選擇Δv使得ΔC是負(fù)的郊供。
(?:常用來表示梯度)
重寫公式(7):
其中η是一個(gè)小的正數(shù)
最終移動(dòng)小球的位置:
η的選取應(yīng)該足夠小峡碉,以免造成ΔC>0的情況出現(xiàn),但是η又不能夠太小驮审,這樣的話小球的移動(dòng)速度就會(huì)很慢
繼續(xù)回到神經(jīng)網(wǎng)絡(luò)中鲫寄,我們的weight以及bias往往會(huì)很多
那如何應(yīng)用梯度下降到我們的神經(jīng)網(wǎng)絡(luò)中呢吉执?
就是通過尋找weight和bias使得代價(jià)方程公式(6)最小
在實(shí)際的應(yīng)用中,如果每次都是使用全部的訓(xùn)練集數(shù)據(jù)來進(jìn)行計(jì)算地来,那么這樣的話將會(huì)有很大的計(jì)算量戳玫。
所以一般會(huì)采用隨機(jī)梯度下降方法,即每次只是隨機(jī)挑選部分(小批量)訓(xùn)練集數(shù)據(jù)進(jìn)行計(jì)算未斑,這樣能夠加快計(jì)算速度咕宿。
其中m表示選取的訓(xùn)練集部分的數(shù)量,通過上面的兩個(gè)公式說明蜡秽,隨機(jī)選取部分訓(xùn)練集數(shù)據(jù)進(jìn)行計(jì)算是可行的府阀。
隨機(jī)梯度下降的工作原理就是隨機(jī)選擇一個(gè)小批量(mini-batch)的訓(xùn)練輸入,并對這些輸入進(jìn)行訓(xùn)練芽突,其中求和部分只是對當(dāng)前批次的所有訓(xùn)練集樣本進(jìn)行试浙。
在當(dāng)前小批量的訓(xùn)練集完成后,再次選取另一個(gè)小批量的訓(xùn)練集重復(fù)上面的過程寞蚌。
直到我們使用完了全部的訓(xùn)練集樣本田巴,我們稱為完成了一個(gè)epoch。這是我們重新開始新epoch睬澡。
(擴(kuò)展閱讀 https://blog.csdn.net/qq_18668137/article/details/80883350)
https://mathoverflow.net/questions/25983/intuitive-crutches-for-higher-dimensional-thinking
https://en.wikipedia.org/wiki/Cauchy%E2%80%93Schwarz_inequality
實(shí)現(xiàn)手寫數(shù)字實(shí)現(xiàn)算法
使用的數(shù)據(jù)集為MNIST的數(shù)據(jù)集固额,前面提過講到的其中包含60000訓(xùn)練集圖片和10000張測試集圖片。
這里只是用60000張訓(xùn)練集圖片煞聪,將其中的50000作為訓(xùn)練集,10000作為驗(yàn)證集逝慧。
源碼獲任舾:git clone https://github.com/mnielsen/neural-networks-and-deep-learning.git
class Network(object):
def __init__(self, sizes):
"""神經(jīng)網(wǎng)絡(luò)的層數(shù) = len(sizes),從第0層開始算笛臣,每一層的神經(jīng)元個(gè)數(shù) = sizes[i]"""
self.num_layers = len(sizes)
self.sizes = sizes
#隨機(jī)初始化偏置項(xiàng)和權(quán)重
#第一層是輸入層云稚,沒有偏置項(xiàng)
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:])]
為什么權(quán)重和偏置項(xiàng)要使用隨機(jī)初始化,而不是全部初始化為0沈堡?首先要明確的就是在初始化的時(shí)候是不能夠全部都初始化為固定值的静陈,因?yàn)槟菢拥脑挘恳粚訉W(xué)習(xí)到的東西都將是相同
>>> import mnist_loader
>>> training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
獲取數(shù)據(jù)集诞丽,其中training_data包含50000個(gè)訓(xùn)練樣本鲸拥,每個(gè)樣本包含兩個(gè)向量,一個(gè)是輸入僧免,為7841的矩陣刑赶,一個(gè)結(jié)果樣本,為101的矩陣懂衩;
validation_data包含10000個(gè)樣本撞叨,每個(gè)樣本包含一個(gè)784*1的輸入矩陣金踪,一個(gè)0-9之間的數(shù)字表明圖片對應(yīng)的數(shù)字。
test_data包含10000個(gè)樣本牵敷,數(shù)據(jù)結(jié)構(gòu)和validation_data相同胡岔。
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 range(epochs): #每一次循環(huán)將是一個(gè)epoch
random.shuffle(training_data)
mini_batches = [ #將訓(xùn)練集分成多個(gè)小的批次
training_data[k:k+mini_batch_size]
for k in range(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))
參數(shù)說明:其中epochs表示進(jìn)行多少輪訓(xùn)練耐版,每輪訓(xùn)練會(huì)將全部的訓(xùn)練集分成多個(gè)小批次進(jìn)行迭代抬闯,每個(gè)小批次中訓(xùn)練樣本的數(shù)量由mini_batch_size決定榨呆;eta決定了學(xué)習(xí)速率火邓;test_data對應(yīng)了驗(yàn)證集數(shù)據(jù)细睡。
主要的工作就是將訓(xùn)練集分成了多個(gè)批次颈将,挤茄,然后每個(gè)批次分別調(diào)用self.update_mini_batch(mini_batch, eta)
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)
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)]
# 對應(yīng)公式(20)
self.weights = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)]
# 對應(yīng)公式(21)
self.biases = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.biases, nabla_b)] 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)
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)]
# 對應(yīng)公式(20)
self.weights = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)]
# 對應(yīng)公式(21)
self.biases = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.biases, nabla_b)]
其中大部分的工作是由self.backprop(x, y)完成的逞刷,稍后會(huì)介紹如何反向傳播算法村生。
>>> import mnist_loader
>>> import network_test
>>>
>>> sizes = [784, 30, 10]
>>> training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
>>> net = network_test.Network(sizes)
>>> net.SGD(training_data, 30, 10, 3.0, test_data=test_data)
Epoch 0: 9032 / 10000
Epoch 1: 9221 / 10000
Epoch 2: 9288 / 10000
Epoch 3: 9354 / 10000
Epoch 4: 9391 / 10000
Epoch 5: 9415 / 10000
Epoch 6: 9412 / 10000
Epoch 7: 9427 / 10000
Epoch 15: 9516 / 10000
Epoch 16: 9477 / 10000
Epoch 17: 9509 / 10000
Epoch 18: 9490 / 10000
Epoch 19: 9507 / 10000
Epoch 20: 9506 / 10000
Epoch 21: 9500 / 10000
Epoch 22: 9527 / 10000
Epoch 23: 9492 / 10000
Epoch 24: 9520 / 10000
Epoch 25: 9508 / 10000
Epoch 26: 9490 / 10000
Epoch 27: 9510 / 10000
Epoch 28: 9516 / 10000
Epoch 29: 9504 / 10000
選擇最高的準(zhǔn)確率為95.27%惊暴,由于依賴于不同的初始化,以及訓(xùn)練集的分批次訓(xùn)練趁桃,會(huì)導(dǎo)致每次訓(xùn)練出生不同的結(jié)果辽话,經(jīng)過多次調(diào)用,總體結(jié)果大致相同卫病。
可以嘗試修改學(xué)習(xí)速率油啤,每個(gè)批次訓(xùn)練樣本個(gè)數(shù),以及訓(xùn)練次數(shù)蟀苛,隱藏層包含神經(jīng)元的個(gè)數(shù)益咬,看一下對訓(xùn)練結(jié)果以及訓(xùn)練速度的影響。