1. 說(shuō)明
現(xiàn)在使用深度學(xué)習(xí)算法都以調(diào)庫(kù)為主,但在使用庫(kù)之前嚷闭,先用python寫(xiě)一個(gè)最基本的神經(jīng)網(wǎng)絡(luò)的程序,也非常必要,它讓我們對(duì)一些關(guān)鍵參數(shù):學(xué)習(xí)率货矮,批尺寸,激活函數(shù)尘喝,代價(jià)函數(shù)的功能和用法有一個(gè)直觀的了解压昼。
2. 原理
1) BP神經(jīng)網(wǎng)絡(luò)
BP神經(jīng)網(wǎng)絡(luò)是一種按照誤差逆向傳播算法訓(xùn)練的多層前饋神經(jīng)網(wǎng)絡(luò).這又前饋又逆向的把人繞暈了.先看看什么是前饋神經(jīng)網(wǎng)絡(luò),回顧一下《深度學(xué)習(xí)_簡(jiǎn)介》中的圖示:
這是一個(gè)典型的前饋神經(jīng)網(wǎng)絡(luò)钧大,數(shù)據(jù)按箭頭方向數(shù)據(jù)從輸入層經(jīng)過(guò)隱藏層流入輸出層翰撑,因此叫做前饋.前饋網(wǎng)絡(luò)在模型的輸出和模型之間沒(méi)有反饋,如果也包含反饋拓型,則是循環(huán)神經(jīng)網(wǎng)絡(luò)额嘿,將在后續(xù)的RNN部分介紹.
前向網(wǎng)絡(luò)和循環(huán)網(wǎng)絡(luò)的用途不同瘸恼,舉個(gè)例子,比如做玩具狗册养,前饋是用不同材料和規(guī)格訓(xùn)練N次东帅,各次訓(xùn)練之間都沒(méi)什么關(guān)系,只是隨著訓(xùn)練球拦,工人越來(lái)越熟練.而循環(huán)網(wǎng)絡(luò)中靠闭,要求每次做出來(lái)的狗都是前次的加強(qiáng)版,因此前次的結(jié)果也作為一種輸入?yún)⑴c到本次的訓(xùn)練之中.可把循環(huán)網(wǎng)絡(luò)理解成前饋網(wǎng)絡(luò)的升級(jí)版.本篇講到的BP神經(jīng)網(wǎng)絡(luò)坎炼,以及處理圖像常用的卷積神經(jīng)網(wǎng)絡(luò)都是前饋網(wǎng)絡(luò)愧膀,而處理自然語(yǔ)言常用的RNN則是循環(huán)網(wǎng)絡(luò).
誤差逆向傳播是指通過(guò)工人做出的狗(預(yù)測(cè)結(jié)果)與玩具狗規(guī)格(實(shí)際結(jié)果)的誤差來(lái)調(diào)整各個(gè)工人的操作(權(quán)重w),這個(gè)例子具體見(jiàn)前篇《簡(jiǎn)介》谣光,?由于誤差的傳播的順序是:輸出層->隱藏層2->隱藏層1檩淋,所以叫逆向傳播.
綜上,前饋指的是數(shù)據(jù)流向萄金,逆向指的是誤差流向.
2) 訓(xùn)練過(guò)程
簡(jiǎn)單回憶一下(詳見(jiàn)《簡(jiǎn)介》篇)訓(xùn)練過(guò)程:對(duì)于每個(gè)訓(xùn)練樣本蟀悦,BP算法先將輸入樣例提供給給輸入神經(jīng)元,然后逐層將信號(hào)向前傳播氧敢,直到產(chǎn)生輸出層的結(jié)果日戈,然后對(duì)照實(shí)際結(jié)果計(jì)算輸出層的誤差,再將誤差逆向傳播到隱層神經(jīng)元孙乖,然后根據(jù)神經(jīng)元的誤差來(lái)對(duì)連接權(quán)值和與偏置進(jìn)行調(diào)整優(yōu)化浙炼。向前傳數(shù)據(jù)很簡(jiǎn)單,只包含加法乘法和激活函數(shù)(具體計(jì)算見(jiàn)代碼)唯袄,相對(duì)的難點(diǎn)在于逆向傳誤差弯屈,當(dāng)?shù)玫搅溯敵鰧拥恼`差后,調(diào)整w3中各個(gè)w的具體方法是什么呢恋拷?這里用到了梯度下降算法.此處也是代碼中的最理解的部分.
下面先看一下代碼季俩,梯度下降算法見(jiàn)之后的"關(guān)鍵概念"部分.
3. 代碼分析
1) 程序說(shuō)明
程序?qū)崿F(xiàn)了通過(guò)MNIST數(shù)據(jù)集中60000個(gè)實(shí)例訓(xùn)練對(duì)手寫(xiě)數(shù)字的識(shí)別,使用一個(gè)輸入層梅掠,一個(gè)隱藏層酌住,一個(gè)輸出層的方式構(gòu)建BP神經(jīng)網(wǎng)絡(luò).
因代碼較長(zhǎng),把它分成兩塊:算法實(shí)現(xiàn)和處部調(diào)用(運(yùn)行程序時(shí)把它們粘在一起即可)阎抒。注釋有點(diǎn)多哈:p
2) 算法實(shí)現(xiàn)
# -*- coding: utf-8 -*-
import numpy as np
import random
import os, struct
from array import array as pyarray
from numpy import append, array, int8, uint8, zeros
from keras.datasets import mnist
class NeuralNet(object):
# 初始化神經(jīng)網(wǎng)絡(luò)酪我,sizes包含了神經(jīng)網(wǎng)絡(luò)的層數(shù)和每層神經(jīng)元個(gè)數(shù)
def __init__(self, sizes):
self.sizes_ = sizes
self.num_layers_ = len(sizes) # 三層:輸入層,一個(gè)隱藏層(8個(gè)節(jié)點(diǎn)), 輸出層
# zip 函數(shù)同時(shí)遍歷兩個(gè)等長(zhǎng)數(shù)組的方法
self.w_ = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])] # w_且叁、b_初始化為隨機(jī)數(shù)
self.b_ = [np.random.randn(y, 1) for y in sizes[1:]]
# w_是二維數(shù)組都哭,w_[0].shape=(8,784), w_[1].shape=(10, 8),權(quán)值, 供矩陣乘
# b_是二維數(shù)組,b_[0].shape=(8, 1), b_[1].shape=(10, 1),偏移, 每層間轉(zhuǎn)換的偏移
# Sigmoid函數(shù),激活函數(shù)的一種, 把正負(fù)無(wú)窮間的值映射到0-1之間
def sigmoid(self, z):
return 1.0/(1.0+np.exp(-z))
# Sigmoid函數(shù)的導(dǎo)函數(shù), 不同激活函數(shù)導(dǎo)函數(shù)不同
def sigmoid_prime(self, z):
return self.sigmoid(z)*(1-self.sigmoid(z))
# 向前傳播:已知input欺矫,根據(jù)w,b算output纱新,用于預(yù)測(cè)
def feedforward(self, x):
for b, w in zip(self.b_, self.w_):
x = self.sigmoid(np.dot(w, x)+b)
return x # 此處的x是0-9每個(gè)數(shù)字的可能性
# 單次訓(xùn)練函數(shù),x是本次訓(xùn)練的輸入穆趴,y是本次訓(xùn)練的實(shí)際輸出
# 返回的是需調(diào)整的w,b值
def backprop(self, x, y):
# 存放待調(diào)整的w,b值脸爱,nabla是微分算符
nabla_b = [np.zeros(b.shape) for b in self.b_] # 與b_大小一樣,初值為0
nabla_w = [np.zeros(w.shape) for w in self.w_] # 與w_大小一樣未妹,初值為0
activation = x # 存放層的具體值, 供下層計(jì)算
activations = [x] # 存儲(chǔ)每層激活函數(shù)之后的值
zs = [] # 存放每層激活函數(shù)之前的值
for b, w in zip(self.b_, self.w_):
z = np.dot(w, activation)+b # dot是矩陣乘法, w是權(quán)值簿废,b是偏移
zs.append(z)
activation = self.sigmoid(z) # 激活函數(shù)
activations.append(activation)
# 計(jì)算輸出層的誤差,cost_derivative為代價(jià)函數(shù)的導(dǎo)數(shù)
delta = self.cost_derivative(activations[-1], y) * \
self.sigmoid_prime(zs[-1]) #原理見(jiàn)梯度下降部分
nabla_b[-1] = delta
nabla_w[-1] = np.dot(delta, activations[-2].transpose())
# 計(jì)算隱藏層的誤差
for l in range(2, self.num_layers_):
z = zs[-l]
sp = self.sigmoid_prime(z)
delta = np.dot(self.w_[-l+1].transpose(), delta) * sp
nabla_b[-l] = delta
nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
return (nabla_b, nabla_w)
# 對(duì)每批中的len(mini_batch)個(gè)實(shí)例络它,按學(xué)習(xí)率eta調(diào)整一次w,b
def update_mini_batch(self, mini_batch, eta):
# 累計(jì)調(diào)整值
nabla_b = [np.zeros(b.shape) for b in self.b_] # 與b_大小一樣族檬,值為0
nabla_w = [np.zeros(w.shape) for w in self.w_] # 與w_大小一樣,值為0
for x, y in mini_batch: # 100個(gè)值,分別訓(xùn)練
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)]
# eta是預(yù)設(shè)的學(xué)習(xí)率(learning rate),用來(lái)調(diào)節(jié)學(xué)習(xí)的速度. eta越大化戳,調(diào)整越大
# 用新計(jì)算出的nable_w調(diào)整舊的w_, b_同理
self.w_ = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.w_, nabla_w)]
self.b_ = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.b_, nabla_b)]
# 訓(xùn)練的接口函數(shù)
# training_data是訓(xùn)練數(shù)據(jù)(x, y);epochs是訓(xùn)練次數(shù);
# mini_batch_size是每次訓(xùn)練樣本數(shù); eta是學(xué)習(xí)率learning rate
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): # 用同樣數(shù)據(jù)单料,訓(xùn)練多次
random.shuffle(training_data) # 打亂順序
mini_batches = [training_data[k:k+mini_batch_size] for k in range(0, n, mini_batch_size)]
# 把所有訓(xùn)練數(shù)據(jù)60000個(gè)分成每100個(gè)/組(mini_batch_size=100)
for mini_batch in mini_batches:
self.update_mini_batch(mini_batch, eta) # 分批訓(xùn)練
if test_data:
print("Epoch {0}: {1} / {2}".format(j, self.evaluate(test_data), n_test))
else:
print("Epoch {0} complete".format(j))
# 計(jì)算預(yù)測(cè)的正確率
def evaluate(self, test_data):
# argmax(f(x))是使得 f(x)取得最大值所對(duì)應(yīng)的變量x
test_results = [(np.argmax(self.feedforward(x)), y) for (x, y) in test_data]
return sum(int(x == y) for (x, y) in test_results)
# 代價(jià)函數(shù)的導(dǎo)數(shù), 對(duì)比實(shí)際輸出與模擬輸出的差異, 此時(shí)y也是個(gè)數(shù)組
def cost_derivative(self, output_activations, y):
return (output_activations-y)
# 預(yù)測(cè)
def predict(self, data):
value = self.feedforward(data)
return value.tolist().index(max(value))
3) 外部調(diào)用
外部調(diào)用主要實(shí)現(xiàn)了主函數(shù),load數(shù)據(jù)点楼,以及調(diào)用神經(jīng)網(wǎng)絡(luò)的接口
# 將輸入數(shù)據(jù)轉(zhuǎn)換為神經(jīng)網(wǎng)絡(luò)能處理的格式
def load_samples(image, label, dataset="training_data"):
X = [np.reshape(x,(28*28, 1)) for x in image] # 手寫(xiě)圖分辨率28x28
X = [x/255.0 for x in X] # 灰度值范圍(0-255)看尼,轉(zhuǎn)換為(0-1)
# 把y從一個(gè)值轉(zhuǎn)成一個(gè)數(shù)組,對(duì)應(yīng)輸出層0-9每個(gè)數(shù)字出現(xiàn)的概率
# 5 -> [0,0,0,0,0,1.0,0,0,0]; 1 -> [0,1.0,0,0,0,0,0,0,0]
def vectorized_Y(y):
e = np.zeros((10, 1))
e[y] = 1.0
return e
if dataset == "training_data":
Y = [vectorized_Y(y) for y in label]
pair = list(zip(X, Y))
return pair
elif dataset == 'testing_data':
pair = list(zip(X, label))
return pair
else:
print('Something wrong')
if __name__ == '__main__':
INPUT = 28*28 # 每張圖像28x28個(gè)像素
OUTPUT = 10 # 0-9十個(gè)分類(lèi)
net = NeuralNet([INPUT, 8, OUTPUT])
# 從mnist提供的庫(kù)中裝載數(shù)據(jù)
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 格式轉(zhuǎn)換
test_set = load_samples(x_test, y_test, dataset='testing_data')
train_set = load_samples(x_train, y_train, dataset='training_data')
#訓(xùn)練
net.SGD(train_set, 13, 100, 3.0, test_data=test_set)
#計(jì)算準(zhǔn)確率
correct = 0;
for test_feature in test_set:
if net.predict(test_feature[0]) == test_feature[1]:
correct += 1
print("percent: ", correct/len(test_set))
4. 關(guān)鍵概念
1) 誤差函數(shù)
誤差函數(shù)也叫代價(jià)函數(shù)或損失函數(shù)盟步,它計(jì)算的是實(shí)際結(jié)果和預(yù)測(cè)結(jié)果之間的差異,誤差函數(shù)記作L(Y, f(x)).
上例中代價(jià)函數(shù)用的是方差再取二分之一的方法(SSE):(1/2)*(o-y)^2躏结,它的導(dǎo)數(shù)是o-y却盘,即output_activations-y,其中output_activations為預(yù)測(cè)結(jié)果媳拴,y為實(shí)際結(jié)果黄橘。上面沒(méi)有直接寫(xiě)誤差函數(shù),而是給出了它的導(dǎo)數(shù)(cost_derivative).
誤差函數(shù)還有均方誤差屈溉,絕對(duì)值均差等塞关,具體請(qǐng)見(jiàn)參考中的《目標(biāo)函數(shù)objectives》。
2) 梯度下降
回顧一下導(dǎo)數(shù)的概念子巾,假設(shè)我們有一個(gè)函數(shù) y = f (x)帆赢,這個(gè)函數(shù)的導(dǎo)數(shù)記為f’(x) ,它描述了如何改變x线梗,能在輸出獲得相應(yīng)的變化:
f (x +ε) ≈ f (x) +εf’(x)
此時(shí)我們希望f()向小的方向變化(等號(hào)左側(cè))椰于,則需要對(duì)f(x)加一個(gè)負(fù)數(shù),即εf’(x)<=0仪搔,那么ε與f’(x)符號(hào)不同瘾婿。換言之,可以將 x 往導(dǎo)數(shù)的反方向移動(dòng)一小步ε來(lái)減小 f (x)。這種技術(shù)被稱(chēng)為梯度下降.可通過(guò)下圖偏陪,獲得更直觀的理解.
梯度下降算法在上例的backprop()部分實(shí)現(xiàn)抢呆,我們想知道如何改變權(quán)重w,能使誤差函數(shù)L變小笛谦,于是求L對(duì)于w的導(dǎo)數(shù)抱虐,然后將w向?qū)?shù)的反方向移動(dòng)一小步,即可使L變芯竞薄.
損失函數(shù)的計(jì)算由下式得出:
L = (1/2)*(g(wx+b) – y)^2梯码,其中y是實(shí)際結(jié)果,g()是激活函數(shù)好啰,wx+b是對(duì)上一步x的線性變換轩娶,對(duì)L求導(dǎo)用到了復(fù)合函數(shù)的鏈試法則,因此有程序中分別使用了激活函數(shù)的導(dǎo)數(shù)(sigmoid_prime)框往,損失函數(shù)的導(dǎo)數(shù)(cost_derivative)鳄抒,再乘以上一步的x(activations).以上就是求權(quán)重w變化的原理,偏置b同理.
另外椰弊,需要注意的是這里求出的nable_w是權(quán)重的梯度许溅,并不是具體的權(quán)重值.
3) 批尺寸
批尺寸是每訓(xùn)練多少個(gè)數(shù)據(jù)調(diào)整一次權(quán)重.上例中由mini_batch指定為每次100個(gè)實(shí)例;如果每訓(xùn)練一次就調(diào)整一次秉版,不但會(huì)增加運(yùn)算量贤重,還會(huì)使w變化波動(dòng)加俱,使其難以收斂清焕;如果一次訓(xùn)練太多并蝗,則會(huì)占用較大內(nèi)存,有時(shí)正負(fù)波相互抵消秸妥,最終使w無(wú)法改進(jìn).因此選擇批尺寸時(shí)滚停,需要在考慮內(nèi)存和運(yùn)算量的情況下盡量加大批尺寸.
4) 學(xué)習(xí)率
學(xué)習(xí)率也叫學(xué)習(xí)因子,簡(jiǎn)單地說(shuō)粥惧,就是每次算出來(lái)的梯度對(duì)權(quán)值的影響的大小键畴。學(xué)習(xí)率大,影響就大突雪。學(xué)習(xí)率決定了參數(shù)移動(dòng)到最優(yōu)值的速度快慢起惕。如果學(xué)習(xí)率過(guò)大,很可能會(huì)越過(guò)最優(yōu)值咏删;反而如果學(xué)習(xí)率過(guò)小疤祭,優(yōu)化的效率可能過(guò)低,使得長(zhǎng)時(shí)間算法無(wú)法收斂饵婆。
學(xué)習(xí)率在上例中是eta數(shù)值.
學(xué)習(xí)率的選擇與誤差函數(shù)相關(guān)勺馆,上例中使用SSE作為誤差函數(shù)戏售,批尺寸越大,梯度數(shù)據(jù)累加后越大草穆,因此在計(jì)算時(shí)除以了批尺寸大泄嘣帧.我們可以選擇一個(gè)不被訓(xùn)練集樣本個(gè)數(shù)影響的誤差函數(shù)(比如MSE).另外,輸入特征的大小也對(duì)學(xué)習(xí)率有影響悲柱,所以處理前最好先歸一化.
還可以在學(xué)習(xí)中動(dòng)態(tài)地調(diào)整學(xué)習(xí)率锋喜,常用的有學(xué)習(xí)率有sgd, adadelta等.具體見(jiàn)參考中的《各種優(yōu)化方法總結(jié)比較》
5) 激活函數(shù)
激活函數(shù)也叫激勵(lì)函數(shù)豌鸡,它的功能是將線性變換轉(zhuǎn)成非線性變換嘿般,以提供非線性問(wèn)題的解決方法.
上例中使用了sigmoid函數(shù)作為激活函數(shù).常用的激活函數(shù)還有tanh,RelU等.其中ReLU是當(dāng)前流行的激活函數(shù)y=max(0,x)涯冠,它將大于0的值留下炉奴,否則一律為0。常用于處理大多數(shù)元素為0的稀疏矩陣蛇更。具體請(qǐng)見(jiàn)參考中的《神經(jīng)網(wǎng)絡(luò)之激活函數(shù)》
5. 參考
- 神經(jīng)網(wǎng)絡(luò)入門(mén)之bp算法瞻赶,梯度下降
http://blog.csdn.net/u013230651/article/details/75909596 - 使用Python實(shí)現(xiàn)神經(jīng)網(wǎng)絡(luò)
http://blog.csdn.net/u014365862/article/details/53868414 - 目標(biāo)函數(shù)objectives
http://keras-cn.readthedocs.io/en/latest/other/objectives/ - 各種優(yōu)化方法總結(jié)比較
http://blog.csdn.net/luo123n/article/details/48239963 - 機(jī)器學(xué)習(xí)中的損失函數(shù)
http://blog.csdn.net/shenxiaoming77/article/details/51614601 - Deep Learning 學(xué)習(xí)隨記(七)Convolution and Pooling --卷積和池化
http://blog.csdn.net/zhoubl668/article/details/24801103 - 神經(jīng)網(wǎng)絡(luò)之激活函數(shù)(sigmoid、tanh派任、ReLU)
http://blog.csdn.net/suixinsuiyuan33/article/details/69062894?locationNum=4&fps=1