Numpy實現簡單BP神經網絡識別手寫數字

  • 2022 年 1 月 12 日
  • 筆記

       本文將用Numpy實現簡單BP神經網絡完成對手寫數字圖片的識別,數據集為42000張帶標籤的28×28像素手寫數字圖像。在計算機完成對手寫數字圖片的識別過程中,代表圖片的28×28=764個像素的特徵數據值將會被作為神經網絡的輸入,經過網絡的正向傳播,得到可以粗略作為0~9每個數字的概率的輸出(輸出層第一個神經元節點的輸出看成是圖片數字是0的概率,其餘9個神經元節點以此類推),取概率最大的數字即為識別結果。神經網絡的輸出神經元節點有10個,假設待識別數字為1,就可以定義label為[0,1,0,0,0,0,0,0,0,0],將網絡的10個節點的輸出組成的向量(預測值)與label(真實值)進行比較,定義均方誤差損失E衡量網絡預測值和真實值的差距程度,根據梯度下降法完成對網絡權值和閾值的參數更新,即對網絡的訓練。在對模型的評估中,簡單地使用測試樣本中預測正確的樣本數佔全部測試樣本總數的比例作為模型的識別正確率。

       網絡隱層的層數以及神經元的個數是可以人為自由調整的,但需注意合理設置。文中設置的隱層有兩層,每層神經元的節點數都設置為50,在對數據集進行訓練集、測試集約為1:1劃分,訓練輪數為5輪的情況下,達到了92%以上不錯的正確預測率。需要提醒的是,訓練輪數過大以及隱層節點數設置過大,極有可能發生過擬合的情況,導致正確率不如人意。
       數據集地址:
       鏈接://pan.baidu.com/s/1XzexzXGYhUdFpM4UaIb3yA
       提取碼:9wjm

 實現代碼如下:

import numpy as np
import csv
from tqdm import trange

def sigmoid(x):
return 1.0 / (1.0 + np.exp(-x))

def sigmoid_derivative(x):
return sigmoid(x) * (1 - sigmoid(x))

class Net:

def __init__(self, layers):
self.active = sigmoid # 激活函數
self.active_d = sigmoid_derivative # 激活函數的求導
self.weights = [] # 權值參數
self.bias = [] # 閾值參數
for i in range(1, len(layers)): # 參數初始化
self.weights.append(np.random.randn(layers[i - 1], layers[i]))
self.bias.append(np.random.randn(layers[i]))

def core(self, x, label, learning_rate):
y = [x] # 保存每層激活後的輸出值

# 正向傳播
for i in range(len(self.weights)):
y.append(self.active(np.dot(y[-1], self.weights[i]) + self.bias[i]))

# 反向傳播
e = y[-1] - label
deltas = [e * y[-1] * (1 - y[-1])] # 輸出層Delta值
# 各隱藏層Delta值
for i in range(1, len(self.weights)):
deltas.append(np.dot(deltas[-1], self.weights[-i].T) * y[-i - 1] * (1 - y[-i - 1]))
# 誤差項倒置
deltas.reverse()
# 更新參數w和b
for i in range(len(self.weights)):
y_2d = np.atleast_2d(y[i])
deltas_2d = np.atleast_2d(deltas[i])
self.weights[i] -= learning_rate * np.dot(y_2d.T, deltas_2d)
self.bias[i] -= learning_rate * deltas[i]

def predict(self, x):

# 正向傳播預測輸出值
y = x
for i in range(len(self.weights)):
y = self.active(np.dot(y, self.weights[i]) + self.bias[i])
_ = [str(round((i / y.sum()) * 100, 2)) + "%" for i in y] # 將輸出結果以概率的形式展現
out = list(y)
result = out.index(max(out))
return result, _

if __name__ == '__main__':

Net = Net([784, 50, 50, 10])
# 讀取數據
file_name = "data/train.csv" # 數據集為42000張帶標籤的28x28手寫數字圖像
x_train = [] # 訓練集樣本數據特徵
y_train = [] # 訓練集樣本標籤
'''
測試集用來測試模型質量必不可少。
驗證集用來調節超參數,如神經網絡層數,各層神經節點數、學習率等人為設置而不是模型通過學習得到的參數,可省略,僅用訓練集即可
'''
x_test = [] # 測試集樣本數據特徵
y_test = [] # 測試集樣本數據標籤
with open(file_name, 'r') as f:
reader = csv.reader(f)
header_row = next(reader)
for row in reader:
data = np.array(row[1:], dtype=np.float_)
if np.random.random() < 0.5: # 劃分數據集 訓練集:測試集 ≈ 1:1
x_train.append(data / ((data ** 2).sum() ** 0.5)) # 每個樣本對應的784個特徵數值歸一化
y_train.append(int(row[0]))
else:
x_test.append(data / ((data ** 2).sum() ** 0.5))
y_test.append(int(row[0]))

print("訓練樣本數:{},測試樣本數:{}".format(len(y_train), len(y_test)))

# 訓練
learning_rate = 0.5 # 初始學習率
epochs = 5 # 訓練輪數
for i in trange(len(y_train) * epochs):
if (i + 1) % len(y_test) == 0:
learning_rate *= 0.5 # 每訓練一輪 學習率減半
label = np.zeros(10)
label[y_train[i % len(y_train)]] = 1 # 設置label
Net.core(x_train[i % len(y_train)], label, learning_rate) # 更新網絡參數

# 預測
count = 0
for i in trange(len(y_test)):
print("-------第{}個測試樣本-----------".format(i + 1))
predict, _ = Net.predict(x_test[i])
print("預測值:{},真實值:{}".format(predict, y_test[i]))
if predict == y_test[i]:
count += 1
print("正確率:{}".format(count / len(y_test)))

   運行結果: