使用python实现深度神经网络 4
使用淺層神經(jīng)網(wǎng)絡(luò)識(shí)別圖片中的英文字母
一、實(shí)驗(yàn)介紹
1.1 實(shí)驗(yàn)內(nèi)容
本次實(shí)驗(yàn)我們正式開始我們的項(xiàng)目:使用神經(jīng)網(wǎng)絡(luò)識(shí)別圖片中的英文字母。
激動(dòng)人心的時(shí)刻到了,我們將運(yùn)用神經(jīng)網(wǎng)絡(luò)的魔力,解決一個(gè)無法使用手工編程解決的問題。如果你(自認(rèn)為)是一個(gè)程序員,本次實(shí)驗(yàn)結(jié)束后,你將變得與其他只會(huì)手工編寫程序的程序員不同。
1.2 實(shí)驗(yàn)知識(shí)點(diǎn)
- “淺層”與“深度”的區(qū)別
- 泛化性能
- 隨機(jī)梯度下降算法
- 如何對(duì)矩陣求導(dǎo)
- 編寫我們的損失層
1.3 實(shí)驗(yàn)環(huán)境
- python 2.7
- numpy 1.12.1
- scipy 0.19.0
二、實(shí)驗(yàn)步驟
2.1 是“淺層”好還是“深度”好?
2.1.1 神經(jīng)網(wǎng)絡(luò)的潛能
這里先插入一個(gè)問題,我們一開始直接把神經(jīng)網(wǎng)絡(luò)的模型結(jié)構(gòu)告訴了大家,但有一個(gè)問題似乎被忽視掉了:神經(jīng)網(wǎng)絡(luò)是萬能的嗎?或者說,對(duì)于神經(jīng)網(wǎng)絡(luò)來說,會(huì)不會(huì)存在其無法表示的問題?這個(gè)問題不是很好回答,但可以告訴大家的一點(diǎn)是,數(shù)學(xué)上可以證明,滿足一定條件的神經(jīng)網(wǎng)絡(luò),可以以任意精度逼近任何函數(shù)。這里給出了一個(gè)直觀的解釋為什么神經(jīng)網(wǎng)絡(luò)有這樣的能力。所以,神經(jīng)網(wǎng)絡(luò)確實(shí)是非常強(qiáng)大。
2.1.2 為什么“深度”更好
界定多"深"才算深度學(xué)習(xí)的標(biāo)準(zhǔn)不一,一種較常見的界定方法是,我們將神經(jīng)網(wǎng)絡(luò)除輸入和輸出層之外的層叫做隱層(hidden layer),當(dāng)隱層的數(shù)量大于1時(shí),就可以稱之為深度學(xué)習(xí)。我們第一次實(shí)驗(yàn)所放的第一張神經(jīng)網(wǎng)絡(luò)結(jié)構(gòu)圖,只有一個(gè)隱層,可以稱之為“淺層神經(jīng)網(wǎng)絡(luò)”,本次實(shí)驗(yàn)將會(huì)實(shí)現(xiàn)的神經(jīng)網(wǎng)絡(luò)模型就會(huì)是類似的結(jié)構(gòu)。
“深度”神經(jīng)網(wǎng)絡(luò)要比“淺層”神經(jīng)網(wǎng)絡(luò)更好,這里面的原因有很多,其中最重要的一點(diǎn)是,深度神經(jīng)網(wǎng)絡(luò)可以利用“層次化”的信息表達(dá)減少網(wǎng)絡(luò)中的參數(shù)數(shù)量,而且能夠提高模型的表達(dá)能力,即靠后的網(wǎng)絡(luò)層可以利用靠前的網(wǎng)絡(luò)層中提取的較低層次的信息組合成更高層次或者更加抽象的信息。
2.2 準(zhǔn)備訓(xùn)練數(shù)據(jù)
2.2.1 獲取訓(xùn)練數(shù)據(jù)
為了完成我們的項(xiàng)目,我們需要準(zhǔn)備足夠的訓(xùn)練數(shù)據(jù)data, 構(gòu)建一個(gè)淺層神經(jīng)網(wǎng)絡(luò)模型model, 并且使用梯度下降算法learn去優(yōu)化我們的模型。
我們先來解決訓(xùn)練數(shù)據(jù)的問題,我已經(jīng)事先準(zhǔn)備好了一些帶有標(biāo)簽(label,代表圖片上的字母是什么,0代表A,1代表B,依次類推)的訓(xùn)練圖片,你可以直接運(yùn)行以下命令下載并解壓它們:
解壓之后,我們得到了一個(gè)文件夾pic和三個(gè)分別名為train、validate、test的txt格式文件,pic文件夾下一共有60000張圖片,每張圖片的尺寸為17*17,包含一個(gè)不等寬的大寫英文字母。train.txt文件有40000行,每行的格式為"圖片路徑 標(biāo)簽",代表一張有標(biāo)簽訓(xùn)練圖片,validate.txt和test.txt文件格式與train.txt類似,且都包含10000行。
你可以使用cat命令查看這三個(gè)文件中的內(nèi)容:
cat train.txt2.2.2 訓(xùn)練、驗(yàn)證和測(cè)試 & 泛化性能
train.txt、validate.txt和test.txt將我們的數(shù)據(jù)劃分成了三個(gè)部分。進(jìn)行這樣的劃分是有原因的,在實(shí)際運(yùn)用深度學(xué)習(xí)解決分類問題的過程中,我們總是將數(shù)據(jù)劃分為訓(xùn)練集、驗(yàn)證集和測(cè)試集。
我們的學(xué)習(xí)算法learn利用訓(xùn)練集來對(duì)模型中的參數(shù)進(jìn)行優(yōu)化,為了檢驗(yàn)這些參數(shù)是否足夠“好”,可以通過觀察訓(xùn)練過程中的損失函數(shù)值來判斷,但通過損失函數(shù)值來判斷有一個(gè)問題,就是我們的模型可能只是“記住”了所有的訓(xùn)練數(shù)據(jù),而不是真正的學(xué)會(huì)了訓(xùn)練數(shù)據(jù)中所包含的問題本身的性質(zhì)。就像是如果我們考試時(shí)總是出原題,那笨學(xué)生只要把所有題目都記住也一樣可以取得高分。
所以為了檢驗(yàn)我們的模型是在“學(xué)習(xí)”而不是在“死記硬背”,我們?cè)偈褂门c訓(xùn)練集不同的驗(yàn)證集對(duì)模型進(jìn)行測(cè)試,當(dāng)模型對(duì)驗(yàn)證集的分類準(zhǔn)確率也比較高時(shí),就可以認(rèn)為我們的模型是真正的在“學(xué)習(xí)”,此時(shí)我們稱我們的模型擁有較好的泛化性能(generalization)--能夠正確的對(duì)未曾見過的測(cè)試樣例做出正確的預(yù)測(cè)。
然而這里還是有一個(gè)問題,別忘了除了模型里的參數(shù),我們還手動(dòng)設(shè)置了超參數(shù),我們的超參數(shù)也有可能只能適應(yīng)一部分?jǐn)?shù)據(jù),所以為了避免這種情況,需要再設(shè)置一個(gè)與訓(xùn)練集和驗(yàn)證集都不同的測(cè)試集,測(cè)試在當(dāng)前超參數(shù)的設(shè)置下,我們的模型具有良好的泛化性能。
2.2.3 預(yù)處理訓(xùn)練數(shù)據(jù)
對(duì)于圖片數(shù)據(jù),我們首先需要將它們轉(zhuǎn)換成輸入向量的形式,并且由于我們是有監(jiān)督學(xué)習(xí),每張圖片的標(biāo)簽也必須與對(duì)應(yīng)的圖片向量一一對(duì)應(yīng)。
編寫數(shù)據(jù)預(yù)處理腳本preprocess.py如下:
讀入圖片數(shù)據(jù)需要scipy模塊,使用以下命令安裝:
sudo pip install scipy我們的預(yù)處理腳本接收兩個(gè)參數(shù),第一個(gè)參數(shù)src對(duì)應(yīng)之前我們提到的train.txt、validate.txt和test.txt,我們從src中讀取圖片的路徑和它的標(biāo)簽。第二個(gè)參數(shù)dst代表我們將預(yù)處理好的圖片數(shù)據(jù)保存到哪里,我們直接使用np.save()函數(shù)將數(shù)組保存到npy文件。
注意原始圖片中只有0和255兩種灰度值,我們的代碼對(duì)圖片灰度值除以了255,將圖片矩陣轉(zhuǎn)換成了只包含0-1值的矩陣。同時(shí)我們將圖片矩陣轉(zhuǎn)換成了列向量,注意這里的列向量的尺寸是img.sizex1而不是img.size,即我們其實(shí)是使用矩陣的形式表示向量,這樣可以方便我們之后的運(yùn)算。
我們可以使用以下命令將圖片轉(zhuǎn)換成npy文件:
python preprocess.py train.txt train.npy python preprocess.py validate.txt validate.npy python preprocess.py test.txt test.npy然后你會(huì)發(fā)現(xiàn)生成了3個(gè)文件
2.3 編寫數(shù)據(jù)層 & 隨機(jī)梯度下降算法
預(yù)處理好了訓(xùn)練數(shù)據(jù)之后,我們還需要將數(shù)據(jù)讀入我們的神經(jīng)網(wǎng)絡(luò),為了一致性,我們將讀入數(shù)據(jù)的操作放到一個(gè)數(shù)據(jù)層里面。創(chuàng)建layers.py文件,數(shù)據(jù)層代碼如下:
import numpy as npclass Data:def __init__(self, name, batch_size): # 數(shù)據(jù)所在的文件名name和batch中圖片的數(shù)量batch_sizewith open(name, 'rb') as f:data = np.load(f)self.x = data[0] # 輸入xself.y = data[1] # 預(yù)期正確輸出yself.l = len(self.x)self.batch_size = batch_sizeself.pos = 0 # pos用來記錄數(shù)據(jù)讀取的位置def forward(self):pos = self.pos bat = self.batch_sizel = self.lif pos + bat >= l: # 已經(jīng)是最后一個(gè)batch時(shí),返回剩余的數(shù)據(jù),并設(shè)置pos為開始位置0ret = (self.x[pos:l], self.y[pos:l])self.pos = 0index = range(l)np.random.shuffle(index) # 將訓(xùn)練數(shù)據(jù)打亂self.x = self.x[index]self.y = self.y[index]else: # 不是最后一個(gè)batch, pos直接加上batch_sizeret = (self.x[pos:pos + bat], self.y[pos:pos + bat])self.pos += self.batch_sizereturn ret, self.pos # 返回的pos為0時(shí)代表一個(gè)epoch已經(jīng)結(jié)束def backward(self, d): # 數(shù)據(jù)層無backward操作pass這里先要介紹梯度下降算法的實(shí)際運(yùn)用版本:隨機(jī)梯度下降算法(stochastic gradient descent)。在實(shí)際的深度學(xué)習(xí)訓(xùn)練過程當(dāng)中,我們每次計(jì)算梯度并更新參數(shù)值時(shí),總是一次性計(jì)算多個(gè)輸入數(shù)據(jù)的梯度,并將這些梯度求平均值,再使用這個(gè)平均值對(duì)參數(shù)進(jìn)行更新。這樣做可以利用并行計(jì)算來提高訓(xùn)練速度。我們將一次性一起計(jì)算的一組數(shù)據(jù)稱為一個(gè)batch。同時(shí),我們稱所有訓(xùn)練圖片都已參與一遍訓(xùn)練的一個(gè)周期稱為一個(gè)epoch。每個(gè)epoch結(jié)束時(shí),我們會(huì)將訓(xùn)練數(shù)據(jù)重新打亂,這樣可以獲得更好的訓(xùn)練效果。我們通常會(huì)訓(xùn)練多個(gè)epoch。
2.3 編寫一次處理一個(gè)batch的全連接層 & 對(duì)矩陣求導(dǎo)的竅門
在上次實(shí)驗(yàn)中,我們實(shí)現(xiàn)了一個(gè)全連接FullyConnect層,但是那段代碼只能處理輸出是一個(gè)標(biāo)量的情況,對(duì)于輸出是多個(gè)節(jié)點(diǎn)的情況無法處理。而且當(dāng)一個(gè)batch中包含多個(gè)訓(xùn)練圖片數(shù)據(jù)時(shí),那段代碼更是無法正常工作。
所以我們需要重新編寫我們的全連接層,由于batch的引入,這時(shí)的全連接層要難了很多:
class FullyConnect:def __init__(self, l_x, l_y): # 兩個(gè)參數(shù)分別為輸入層的長度和輸出層的長度self.weights = np.random.randn(l_y, l_x) / np.sqrt(l_x) # 使用隨機(jī)數(shù)初始化參數(shù),請(qǐng)暫時(shí)忽略這里為什么多了np.sqrt(l_x)self.bias = np.random.randn(l_y, 1) # 使用隨機(jī)數(shù)初始化參數(shù)self.lr = 0 # 先將學(xué)習(xí)速率初始化為0,最后統(tǒng)一設(shè)置學(xué)習(xí)速率def forward(self, x):self.x = x # 把中間結(jié)果保存下來,以備反向傳播時(shí)使用self.y = np.array([np.dot(self.weights, xx) + self.bias for xx in x]) # 計(jì)算全連接層的輸出return self.y # 將這一層計(jì)算的結(jié)果向前傳遞def backward(self, d):ddw = [np.dot(dd, xx.T) for dd, xx in zip(d, self.x)] # 根據(jù)鏈?zhǔn)椒▌t,將反向傳遞回來的導(dǎo)數(shù)值乘以x,得到對(duì)參數(shù)的梯度self.dw = np.sum(ddw, axis=0) / self.x.shape[0]self.db = np.sum(d, axis=0) / self.x.shape[0]self.dx = np.array([np.dot(self.weights.T, dd) for dd in d])# 更新參數(shù)self.weights -= self.lr * self.dwself.bias -= self.lr * self.dbreturn self.dx # 反向傳播梯度 為了理解上面的代碼,我們以一個(gè)包含100個(gè)訓(xùn)練輸入數(shù)據(jù)的batch為例,分析一下具體執(zhí)行流程:
我們的l_x為輸入單個(gè)數(shù)據(jù)向量的長度,在這里是17*17=289,l_y代表全連接層輸出的節(jié)點(diǎn)數(shù)量,由于大寫英文字母有26個(gè),所以這里的l_y=26。
所以,我們的self.weights的尺寸為26*289, self.bias的尺寸為26*1(self.bias也是通過矩陣形式表示的向量)。forward()函數(shù)的輸入x在這里的尺寸就是100*289*1(batch_size*向量長度*1)。backward()函數(shù)的輸入d代表從前面的網(wǎng)絡(luò)層反向傳遞回來的“部分梯度值”,其尺寸為100*26*1(batch_size*輸出層節(jié)點(diǎn)數(shù)l_y*1)。
forward()函數(shù)里的代碼比較好理解,由于這里的x包含了多組數(shù)據(jù),所以要對(duì)每組數(shù)據(jù)分別進(jìn)行計(jì)算。
backward()函數(shù)里的代碼就不太好理解了,ddw保存的是對(duì)于每組輸入數(shù)據(jù),損失函數(shù)對(duì)于參數(shù)的梯度。由于這里的參數(shù)是一個(gè)26*289的矩陣,所以,我們需要求損失函數(shù)對(duì)矩陣的導(dǎo)數(shù)。(對(duì)矩陣求導(dǎo)可能大部分本科生都不會(huì)。但其實(shí)也不難,如果你線性代數(shù)功底可以,可以嘗試推導(dǎo)矩陣求導(dǎo)公式。)不過這里有一個(gè)簡便的方法去推斷對(duì)矩陣求導(dǎo)時(shí)應(yīng)該如何計(jì)算:由于這里的參數(shù)矩陣本身是26*289的,那損失函數(shù)對(duì)于它的梯度(即損失函數(shù)對(duì)參數(shù)矩陣求導(dǎo)的結(jié)果)的尺寸也一定是26*289的。而這里每組輸入數(shù)據(jù)的尺寸是289*1,每組數(shù)據(jù)對(duì)應(yīng)的部分梯度尺寸為26*1,要得到一個(gè)26*289尺寸的梯度矩陣,就只能是一個(gè)26*1尺寸的矩陣乘以一個(gè)1*289尺寸的矩陣,需要對(duì)輸入數(shù)據(jù)進(jìn)行轉(zhuǎn)置。所以這里計(jì)算的是np.dot(dd,xx.T)。
對(duì)一個(gè)batch里的數(shù)據(jù)分別求得梯度之后,按照隨機(jī)梯度下降算法的要求,我們需要對(duì)所有梯度求平均值,得到self.dw, 其尺寸為26*289,剛好與我們的self.weights匹配。
由于全連接層對(duì)bias的部分導(dǎo)數(shù)為1,所以這里對(duì)于bias的梯度self.bias就直接等于從之前的層反向傳回來的梯度的平均值。
損失函數(shù)對(duì)于輸入x的梯度值self.dx的求解與self.dw類似。由于輸入數(shù)據(jù)self.x中的一個(gè)數(shù)據(jù)的尺寸為289*1,self.weights的尺寸為26*289, dd的尺寸為26*1, 所以需要對(duì)self.weights進(jìn)行轉(zhuǎn)置。即“289*1=(289*26)*(26*1)”。
最后是使用梯度更新參數(shù),注意這里的self.lr即為前面我們提到過的學(xué)習(xí)速率alpha,它是一個(gè)需要我們手工設(shè)定的超參數(shù)。
這里的矩陣求導(dǎo)確實(shí)不太好處理,容易出錯(cuò),請(qǐng)你仔細(xì)分析每一個(gè)變量代表的含義,如果對(duì)一個(gè)地方不清楚,請(qǐng)回到前面看看相關(guān)的概念是如何定義的。
2.4 激活函數(shù)層
由于numpy能夠同時(shí)處理標(biāo)量和矩陣的情況,所以我們之前寫的激活函數(shù)sigmoid層可以不用修改直接使用:
class Sigmoid:def __init__(self): # 無參數(shù),不需初始化passdef sigmoid(self, x):return 1 / (1 + np.exp(-x))def forward(self, x):self.x = xself.y = self.sigmoid(x)return self.ydef backward(self, d):sig = self.sigmoid(self.x)self.dx = d * sig * (1 - sig)return self.dx # 反向傳遞梯度sigmoid函數(shù)將輸出限制在0到1之間,剛好可以作為概率看待。這里我們有26個(gè)輸入節(jié)點(diǎn),經(jīng)過sigmoid層計(jì)算之后,哪個(gè)輸出節(jié)點(diǎn)的數(shù)值最大,就認(rèn)為圖片上最有可能是該節(jié)點(diǎn)代表的字母。比如如果輸出層第0個(gè)節(jié)點(diǎn)值最大,就認(rèn)為圖片上的字母是“A”, 如果第25個(gè)節(jié)點(diǎn)的值最大,就認(rèn)為圖片上的字母是“Z”。
注意一般在計(jì)算神經(jīng)網(wǎng)絡(luò)的深度時(shí)我們一般不把激活層算進(jìn)去,但這里為了編程方便,也將激活函數(shù)視為單獨(dú)的一層?! ?/p>
2.5 損失函數(shù)層
之前我們講解過二次損失函數(shù)quadratic loss的定義,這里我們來實(shí)現(xiàn)它:
class QuadraticLoss:def __init__(self):passdef forward(self, x, label):self.x = xself.label = np.zeros_like(x) # 由于我們的label本身只包含一個(gè)數(shù)字,我們需要將其轉(zhuǎn)換成和模型輸出值尺寸相匹配的向量形式for a, b in zip(self.label, label):a[b] = 1.0 # 只有正確標(biāo)簽所代表的位置概率為1,其他為0self.loss = np.sum(np.square(x - self.label)) / self.x.shape[0] / 2 # 求平均后再除以2是為了表示方便return self.lossdef backward(self):self.dx = (self.x - self.label) / self.x.shape[0] # 2被抵消掉了return self.dx在隨機(jī)梯度下降算法里,每次前向計(jì)算和反向傳播都會(huì)計(jì)算包含多個(gè)輸入數(shù)據(jù)的一個(gè)batch。所以損失函數(shù)值在隨后也要除以batch中包含的數(shù)據(jù)數(shù)量, 即self.x.shape[0],同時(shí)這里除以了2, 這個(gè)地方的2可以和對(duì)二次損失函數(shù)求導(dǎo)后多出來的系數(shù)2抵消掉。所以,我們的損失函數(shù)變成了:
2.6 準(zhǔn)確率層
前面我們提到過,為了判斷經(jīng)過訓(xùn)練的模型是否具有良好的泛化性能,需要使用驗(yàn)證集和測(cè)試集對(duì)模型的效果進(jìn)行檢驗(yàn)。所以我們還需要一個(gè)計(jì)算準(zhǔn)確率的層:
class Accuracy:def __init__(self):passdef forward(self, x, label): # 只需forwardself.accuracy = np.sum([np.argmax(xx) == ll for xx, ll in zip(x, label)]) # 對(duì)預(yù)測(cè)正確的實(shí)例數(shù)求和self.accuracy = 1.0 * self.accuracy / x.shape[0]return self.accuracy如果我們的神經(jīng)網(wǎng)絡(luò)的輸出層中,概率最大的節(jié)點(diǎn)的下標(biāo)與實(shí)際的標(biāo)簽label相等,則預(yù)測(cè)正確。預(yù)測(cè)正確的數(shù)量除以總的數(shù)量,就得到了正確率。
2.7 構(gòu)建神經(jīng)網(wǎng)絡(luò)
我們已經(jīng)寫好了所有必須的網(wǎng)絡(luò)層,并所有網(wǎng)絡(luò)層都放到一個(gè)layers.py文件里?! ?/p>
接下來我們要使用這些層構(gòu)建出一個(gè)完整的神經(jīng)網(wǎng)絡(luò),方法很簡單,按順序把它們“堆疊”起來就可以了,就像搭積木一樣,創(chuàng)建shallow.py文件:
# encoding=utf-8 from layers import *def main():datalayer1 = Data('train.npy', 1024) # 用于訓(xùn)練,batch_size設(shè)置為1024datalayer2 = Data('validate.npy', 10000) # 用于驗(yàn)證,所以設(shè)置batch_size為10000,一次性計(jì)算所有的樣例inner_layers = []inner_layers.append(FullyConnect(17 * 17, 26))inner_layers.append(Sigmoid())losslayer = QuadraticLoss()accuracy = Accuracy()for layer in inner_layers:layer.lr = 1000.0 # 為所有中間層設(shè)置學(xué)習(xí)速率epochs = 20for i in range(epochs):print 'epochs:', ilosssum = 0iters = 0while True:data, pos = datalayer1.forward() # 從數(shù)據(jù)層取出數(shù)據(jù)x, label = datafor layer in inner_layers: # 前向計(jì)算x = layer.forward(x)loss = losslayer.forward(x, label) # 調(diào)用損失層forward函數(shù)計(jì)算損失函數(shù)值losssum += lossiters += 1d = losslayer.backward() # 調(diào)用損失層backward函數(shù)層計(jì)算將要反向傳播的梯度for layer in inner_layers[::-1]: # 反向傳播d = layer.backward(d)if pos == 0: # 一個(gè)epoch完成后進(jìn)行準(zhǔn)確率測(cè)試data, _ = datalayer2.forward()x, label = datafor layer in inner_layers:x = layer.forward(x)accu = accuracy.forward(x, label) # 調(diào)用準(zhǔn)確率層forward()函數(shù)求出準(zhǔn)確率print 'loss:', losssum / itersprint 'accuracy:', accubreakif __name__ == '__main__':main()由于FullyConnect層和Sigmoid層在網(wǎng)絡(luò)中的調(diào)用方式一模一樣,所以把它們存到一個(gè)列表里,使用循環(huán)的方式調(diào)用。同時(shí)由于Sigmoid層一般不計(jì)入神經(jīng)網(wǎng)絡(luò)的深度,所以我們將這個(gè)列表命名為inner_layers而不是hidden_layers以免混淆?! ?/p>
datalayer1數(shù)據(jù)層用來輸出訓(xùn)練集數(shù)據(jù),datalayer2數(shù)據(jù)層用來輸出驗(yàn)證集數(shù)據(jù)。accuracy層用來在每個(gè)epoch結(jié)束時(shí)計(jì)算驗(yàn)證集上的準(zhǔn)確率?! ?/p>
上面的代碼里只有一個(gè)隱層,構(gòu)建的神經(jīng)網(wǎng)絡(luò)屬于淺層神經(jīng)網(wǎng)絡(luò),所以我們把這段代碼存儲(chǔ)在shallow.py文件里。
preprocess.py layers.py shallow.py三個(gè)文件可以使用以下命令獲取:
wget http://labfile.oss.aliyuncs.com/courses/814/code.tar.gz tar zxvf code.tar.gz2.8 訓(xùn)練神經(jīng)網(wǎng)絡(luò)
終于,我們排除萬難,準(zhǔn)備好了訓(xùn)練數(shù)據(jù),構(gòu)建好了我們的淺層神經(jīng)網(wǎng)絡(luò),也寫好了訓(xùn)練算法,終于可以開始訓(xùn)練了!在terminal里輸入:
python shallow.py
這里設(shè)置學(xué)習(xí)速率為1000(實(shí)際當(dāng)中很少看到大于1的學(xué)習(xí)速率,下次實(shí)驗(yàn)我們會(huì)解釋為什么這里的學(xué)習(xí)速率需要這么大),你可以嘗試將學(xué)習(xí)速率改變成其他的值,觀察損失函數(shù)值和準(zhǔn)確率的變化情況。
我們看到每個(gè)epoch結(jié)束時(shí),會(huì)先輸出在訓(xùn)練集上的損失函數(shù)值,再輸出在驗(yàn)證集上的準(zhǔn)確率。
20個(gè)epoch結(jié)束時(shí),準(zhǔn)確率大概會(huì)在0.9左右(為了節(jié)省時(shí)間這里只訓(xùn)練了20個(gè)epoch,你可以加大epochs的數(shù)值,看看最高能到多少,我這里測(cè)試大概是在0.93),這非常令人振奮不是嗎!一個(gè)原本通過手工編程不可解的圖片分類問題,(幾乎)被我們解決了,0.9的準(zhǔn)確率已經(jīng)可以應(yīng)用在一些實(shí)際的項(xiàng)目中了(比如這里),而且我們模型中的參數(shù)都是自動(dòng)設(shè)定的,我們只是編寫了模型和訓(xùn)練算法部分的代碼。
而且,我們的代碼具有很好的可擴(kuò)展性,一方面我們可以很方便的向神經(jīng)網(wǎng)絡(luò)中添加更多的網(wǎng)絡(luò)層使之成為真真的“深度神經(jīng)網(wǎng)絡(luò)”,另一方面我們也可以很方便的將我們的模型運(yùn)用到其他圖片分類問題當(dāng)中,我們只編寫了一次代碼,就有可能能夠解決多種問題!
不過,我要告訴你的是,我們的神經(jīng)網(wǎng)絡(luò)的性能還沒有被完全發(fā)掘出來,我們的準(zhǔn)確率還可以更高!這次實(shí)驗(yàn)的最開始我們提到過,深度神經(jīng)網(wǎng)絡(luò)會(huì)比淺層神經(jīng)網(wǎng)絡(luò)擁有更好的性能,下次實(shí)驗(yàn),我們會(huì)嘗試使用深度神經(jīng)網(wǎng)絡(luò)來提高我們的模型性能,進(jìn)行真正的深度學(xué)習(xí)!
三、實(shí)驗(yàn)總結(jié)
這次實(shí)驗(yàn)我們編寫了數(shù)據(jù)預(yù)處理腳本、數(shù)據(jù)輸入網(wǎng)絡(luò)層、能夠處理批量數(shù)據(jù)的FullyConnect層、損失函數(shù)層和準(zhǔn)確率層,使用這些層構(gòu)建出了只有一個(gè)隱層的淺層神經(jīng)網(wǎng)絡(luò),并使用這個(gè)神經(jīng)網(wǎng)絡(luò)訓(xùn)練得到了一個(gè)效果已經(jīng)很不錯(cuò)的模型。
在此課程的一開始,我就強(qiáng)調(diào)本課程不要求很高的數(shù)學(xué)水平,但是我相信你在實(shí)驗(yàn)的過程中還是逐漸的體會(huì)到了(尤其是編寫FullyConnect層對(duì)矩陣求導(dǎo)數(shù)的時(shí)候),要想理解深度學(xué)習(xí)的原理,必須要具備一定的數(shù)學(xué)基礎(chǔ),數(shù)學(xué)就像是一把強(qiáng)大的戰(zhàn)斧,幫你掃清一個(gè)個(gè)障礙,使原本不可解的問題變得可解。所以如果你想從事深度學(xué)習(xí)相關(guān)的工作,甚至進(jìn)行深度學(xué)習(xí)領(lǐng)域的研究的話,請(qǐng)務(wù)必要重視學(xué)習(xí)相關(guān)數(shù)學(xué)知識(shí)。
本次實(shí)驗(yàn),我們學(xué)習(xí)了:
四、課后作業(yè)
總結(jié)
以上是生活随笔為你收集整理的使用python实现深度神经网络 4的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【VBA自用常用模板2】WORD/WPS
- 下一篇: FFmpeg进阶:编码YUV视频数据