Python純手動搭建BP神經(jīng)網(wǎng)絡(luò)(手寫數(shù)字識別)
來源:投稿?作者:張宇?
編輯:學(xué)姐
實(shí)驗(yàn)介紹
實(shí)驗(yàn)要求:
實(shí)現(xiàn)一個手寫數(shù)字識別程序,如下圖所示,要求神經(jīng)網(wǎng)絡(luò)包含一個隱層,隱層的神經(jīng)元個數(shù)為15。

整體思路:
主要參考西瓜書第五章神經(jīng)網(wǎng)絡(luò)部分的介紹,使用批量梯度下降對神經(jīng)網(wǎng)絡(luò)進(jìn)行訓(xùn)練。
Tip: 整體代碼及數(shù)據(jù)集
關(guān)注公眾號
回復(fù)“新年快樂”領(lǐng)取

讀取并處理數(shù)據(jù)
數(shù)據(jù)讀取思路:
數(shù)據(jù)集中給出的圖片為28*28的灰度圖,利用 plt.imread() 函數(shù)將圖片讀取出來后為 28x28 的數(shù)組,如果使用神經(jīng)網(wǎng)絡(luò)進(jìn)行訓(xùn)練的話,我們可以將每一個像素視為一個特征,所以將圖片利用numpy.reshape 方法將圖片化為 1x784 的數(shù)組。讀取全部數(shù)據(jù)并將其添加進(jìn)入數(shù)據(jù)集 data 內(nèi),將對應(yīng)的標(biāo)簽按同樣的順序添加進(jìn)labels內(nèi)。
然后使用np.random.permutation方法將索引打亂,利用傳入的測試數(shù)據(jù)所占比例test_ratio將數(shù)據(jù)劃分為測試集與訓(xùn)練集。
使用訓(xùn)練樣本的均值和標(biāo)準(zhǔn)差對訓(xùn)練數(shù)據(jù)、測試數(shù)據(jù)進(jìn)行標(biāo)準(zhǔn)化。標(biāo)準(zhǔn)化這一步很重要,開始忽視了標(biāo)準(zhǔn)化的環(huán)節(jié),神經(jīng)網(wǎng)絡(luò)精度一直達(dá)不到效果。
返回數(shù)據(jù)集
這部分代碼如下:
OneHot編碼:
由于神經(jīng)網(wǎng)絡(luò)的特性,在進(jìn)行多分類任務(wù)時,每一個神經(jīng)元的輸出對應(yīng)于一個類,所以將每一個訓(xùn)練樣本的標(biāo)簽轉(zhuǎn)化為OneHot形式(將0-9的數(shù)據(jù)映射在長度為10的向量中,每個樣本向量對應(yīng)位置的值為1其余為0)。
訓(xùn)練神經(jīng)網(wǎng)絡(luò)
激活函數(shù):
激活函數(shù)使用sigmoid函數(shù),但是使用定義式在訓(xùn)練過程中總是出現(xiàn) overflow 警告,所以將函數(shù)進(jìn)行了如下形式的轉(zhuǎn)化。
損失函數(shù):
使用均方誤差損失函數(shù)。其實(shí)我感覺在這個題目里面直接使用預(yù)測精度也是可以的。
訓(xùn)練:
終于來到了緊張而又刺激的訓(xùn)練環(huán)節(jié)。神經(jīng)網(wǎng)絡(luò)從輸入層通過隱層傳遞的輸出層進(jìn)而獲得網(wǎng)絡(luò)的輸出叫做正向傳播,而從輸出層根據(jù)梯度下降的方法進(jìn)行調(diào)整權(quán)重及偏置的過程叫做反向傳播。
前向傳播每層之間將每個結(jié)點(diǎn)的輸出值乘對應(yīng)的權(quán)重$\omega$(對應(yīng)函數(shù)中 omega1 omega2 )傳輸給下一層結(jié)點(diǎn),下一層結(jié)點(diǎn)將上一層所有傳來的數(shù)據(jù)進(jìn)行求和,并減去偏置 theta (此時數(shù)據(jù)對應(yīng)函數(shù)中 h_in o_in )。最后通過激活函數(shù)輸出下一層(對應(yīng)函數(shù)中 h_out o_out )。在前向傳播完成后計算了一次損失,方便后面進(jìn)行分析。
反向傳播是用梯度下降法對權(quán)重和偏置進(jìn)行更新,這里是最主要的部分。根據(jù)西瓜書可以推出輸出層權(quán)重的調(diào)節(jié)量為$\eta\hat{y_i}(1-\hat{y_i})(y_i - \hat{y_i})b_h$ ,其中 $\eta$ 為學(xué)習(xí)率, $y_i\space\hat{y_i}$ 分別為對應(yīng)數(shù)據(jù)的真實(shí)值以及網(wǎng)絡(luò)的輸出, $b_h$ 為隱層的輸出值。這里還要一個重要的地方在于如果將公式向量化的話需要重點(diǎn)關(guān)注輸出矩陣形狀以及每個矩陣數(shù)據(jù)之間的關(guān)系。代碼中d2 對應(yīng)于公式中$\hat{y_i}(1-\hat{y_i})(y_i - \hat{y_i})$ 這一部分,這里需要對應(yīng)值相乘。最后將這一部分與$b_h$進(jìn)行矩陣乘法,在乘學(xué)習(xí)率$\eta$得到權(quán)重調(diào)節(jié)量,與權(quán)重相加即可(代碼中除了訓(xùn)練集樣本個數(shù)m是因?yàn)?d2 與 h_out 的乘積將所有訓(xùn)練樣本進(jìn)行了累加,所以需要求平均)。
對于其他權(quán)重及偏置的調(diào)節(jié)方式與此類似,不做過多介紹(其實(shí)是因?yàn)槊魈煲险n,太晚了得睡覺,有空補(bǔ)上這里和其他不詳細(xì)的地方),詳見西瓜書。
關(guān)注公眾號,
回復(fù)“西瓜書”即可獲取pdf版

網(wǎng)絡(luò)測試
預(yù)測函數(shù):
這里比較簡單,前向傳播的部分和前面一樣,因?yàn)樽詈缶W(wǎng)絡(luò)輸出的為樣本x為每一類的概率,所以僅需要利用 np.argmax 函數(shù)求出概率值最大的下標(biāo)即可,下標(biāo)0-9正好對應(yīng)數(shù)字的值。
準(zhǔn)確率計算:?
這里將所有測試數(shù)據(jù) X 進(jìn)行預(yù)測,計算預(yù)測標(biāo)簽 y-hat 與真實(shí)標(biāo)簽 y 一致個數(shù)的均值得出準(zhǔn)確率。
主函數(shù):?
在主函數(shù)中通過調(diào)用上面的函數(shù)對網(wǎng)絡(luò)訓(xùn)練預(yù)測精度,并使用 loss_list acc_list 兩個list保存訓(xùn)練過程中每一輪的精度與誤差,利用 acc_max 跟蹤最大精度的模型,并使用 pickle 將模型(其實(shí)就是神經(jīng)網(wǎng)絡(luò)的參數(shù))進(jìn)行保存,后面用到時可以讀取。同樣在訓(xùn)練完成時我也將 loss_listacc_list 進(jìn)行了保存 (想的是可以利用訓(xùn)練的數(shù)據(jù)做出一點(diǎn)好看的圖)。
最后部分將損失以及準(zhǔn)確率隨著訓(xùn)練次數(shù)的趨勢進(jìn)行了繪制。
結(jié)果如下:

可以看出,模型只是跑通了,效果并不好,訓(xùn)練好幾個小時準(zhǔn)確率只有91%。原因可能是因?yàn)闆]有對模型進(jìn)行正則化、學(xué)習(xí)率沒有做動態(tài)調(diào)節(jié)等。
相反使用SVM模型準(zhǔn)確率輕松可以達(dá)到97%以上。

瑟瑟發(fā)抖~~~
最后將全部代碼貼上:
獲取代碼及數(shù)據(jù)集
請關(guān)注公眾號
回復(fù)“新年快樂”即可

一鍵三連「分享」、「點(diǎn)贊」和「在看」
點(diǎn)贊的情誼學(xué)姐銘記在心~