看了幾天的BP神經網絡,總算是對它有一點點的理解了。今天就用python搭建了一個模型來實現手寫數字的識別。
一、BP神經網絡簡介
BP(back propagation)神經網絡是一種按照誤差逆向傳播算法訓練的多層前饋神經網絡,是應用最廣泛的一種神經網絡。BP神經網絡算法的基本思想是學習過程由信號正向傳播和誤差反向傳播兩個過程組成。
正向傳播時,把樣本的特征從輸入層進行輸入,信號經過各個隱藏層逐層處理之后,由輸出層傳出,對於網絡的輸出值與樣本真實標簽之間的誤差,從最后一層逐層往前反向傳播,計算出各層的學習信號,再根據學習信號來調整各層的權值參數。這種信號的正向傳播和誤差的反向傳播是反復進行的,網絡中權值調整的過程也就是模型訓練的過程,反復訓練模型,直到模型的代價函數小於某個預先設定的值,或者訓練次數達到預先設置的最大訓練次數為止。
二、手寫數字數據集介紹
我用的手寫數字數據集是sklearn.datasets
中的一個數據集,使用load_digits()
命令就可以載入數據集,數據集包含了1797個樣本,也就是有1797張手寫數字的圖片,每個樣本包含了64個特征,實際上每個樣本就是一張8x8的圖片,對應着0-9中的一個數字。看一下第一個樣本長什么樣子:
from matplotlib import pyplot as plt
from sklearn.datasets import load_digits
# 載入數據集
digits = load_digits()
# 展示第一張圖片,
plt.imshow(digits.images[0])
plt.show()
結果如下圖:
從結果也可以看出,是一張8x8的圖片,這張圖片顯實的應該是數字0。
三、網絡的介紹以及搭建
1、網絡的介紹
我搭建的是一個2層的神經網絡,包含一個輸入層(注意:輸入層一般不計入網絡的層數里面),一個隱藏層和一個輸出層。由於每個樣本包含64個特征,所以輸入層設置了64個神經元,輸出層設置了10個神經元,因為我將標簽進行了獨熱化處理(樣本有10種標簽,獨熱化處理就會將每種標簽轉化成一個只包含0和1,長度為10的數組,例如:數字0的標簽就為[1,0,0,0,0,0,0,0,0,0],數字1的標簽為[0,1,0,0,0,0,0,0,0,0],數字2的標簽為[0,0,1,0,0,0,0,0,0,0],以此類推),隱藏層的神經元數量可以隨便設置,我設置的是100個神經元。對於神經網絡的輸出,也是一個長度為10的數組,只需要取出數組中最大數字對應的索引,即為預測的結果(例如:輸出為[1,0,0,0,0,0,0,0,0,0],最大數字的索引為0,即預測結果為0;輸出為[0,0,0,0,0,0,0,0,0,1],最大數字對應的索引為9,即預測結果為9)。網絡中使用的激活函數為sigmoid函數。
2、網絡搭建
import numpy as np
from matplotlib import pyplot as plt
from sklearn.datasets import load_digits
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, confusion_matrix
class NeuralNetwork:
def __init__(self, layers):
# 初始化隱藏層權值
self.w1 = np.random.random([layers[0], layers[1]]) * 2 - 1
# 初始化輸出層權值
self.w2 = np.random.random([layers[1], layers[2]]) * 2 - 1
# 初始化隱藏層的偏置值
self.b1 = np.zeros([layers[1]])
# 初始化輸出層的偏置值
self.b2 = np.zeros([layers[2]])
# 定義激活函數
@staticmethod
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# 定義激活函數的導函數
@staticmethod
def dsigmoid(x):
return x * (1 - x)
def train(self, x_data, y_data, lr=0.1, batch=50):
"""
模型的訓練函數
:param x_data: 訓練數據的特征
:param y_data: 訓練數據的標簽
:param lr: 學習率
:param batch: 每次要訓練的樣本數量
:return:
"""
# 隨機選擇一定批次的數據進行訓練
index = np.random.randint(0, x_data.shape[0], batch)
x = x_data[index]
t = y_data[index]
# 計算隱藏層的輸出
l1 = self.sigmoid(np.dot(x, self.w1) + self.b1)
# 計算輸出層的輸出
l2 = self.sigmoid(np.dot(l1, self.w2) + self.b2)
# 計算輸出層的學習信號
delta_l2 = (t - l2) * self.dsigmoid(l2)
# 計算隱藏層的學習信號
delta_l1 = delta_l2.dot(self.w2.T) * self.dsigmoid(l1)
# 計算隱藏層的權值變化
delta_w1 = lr * x.T.dot(delta_l1) / x.shape[0]
# 計算輸出層的權值變化
delta_w2 = lr * l1.T.dot(delta_l2) / x.shape[0]
# 改變權值
self.w1 += delta_w1
self.w2 += delta_w2
# 改變偏置值
self.b1 += lr * np.mean(delta_l1, axis=0)
self.b2 += lr * np.mean(delta_l2, axis=0)
def predict(self, x):
"""
模型的預測函數
:param x: 測試數據的特征
:return: 返回一個包含10個0-1之間數字的numpy.array對象
"""
l1 = self.sigmoid(np.dot(x, self.w1) + self.b1)
l2 = self.sigmoid(np.dot(l1, self.w2) + self.b2)
return l2
# 載入數據集
digits = load_digits()
X = digits.data
T = digits.target
# 數據歸一化
X = (X - X.min()) / (X.max() - X.min())
# 將數據拆分成訓練集和測試集
x_train, x_test, y_train, y_test = train_test_split(X, T)
# 將訓練數據標簽化為獨熱編碼
labels = LabelBinarizer().fit_transform(y_train)
# 定義一個2層的網絡模型:64-100-10
nn = NeuralNetwork([64, 100, 10])
# 訓練周期
epoch = 20001
# 測試周期
test = 400
# 用來保存測試時產生的代價函數的值
loss = []
# 用來保存測試過程中的准確率
accuracy = []
for n in range(epoch):
nn.train(x_train, labels)
# 每訓練一定的次數后,進行一次測試
if n % test == 0:
# 用測試集測試模型,返回結果為獨熱編碼的標簽
predictions = nn.predict(x_test)
# 取返回結果最大值的索引,即為預測數據
y2 = np.argmax(predictions, axis=1)
# np.equal用來比較數據是否相等,相等返回True,不相等返回False
# 比較的結果求平均值,即為模型的准確率
acc = np.mean(np.equal(y_test, y2))
# 計算代價函數
cost = np.mean(np.square(y_test - y2) / 2)
# 將准確率添加到列表
accuracy.append(acc)
# 將代價函數添加到列表
loss.append(cost)
print('epoch:', n, 'accuracy:', acc, 'loss:', loss)
# 訓練完成之后,使用測試數據對模型進行測試
pred = nn.predict(x_test)
y_pred = np.argmax(pred, axis=1)
# 查看模型預測結果與真實標簽之間的報告
print(classification_report(y_test, y_pred))
# 查看模型預測結果與真實標簽之間的混淆矩陣
print(confusion_matrix(y_test, y_pred))
plt.subplot(2, 1, 1)
plt.plot(range(0, epoch, test), loss)
plt.ylabel('loss')
plt.subplot(2, 1, 2)
plt.plot(range(0, epoch, test), accuracy)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()
執行以上代碼,可以看到代價函數和預測准確率隨着模型的訓練周期的變化,隨着模型訓練次數的增加,代價函數逐漸減小,然后趨於穩定,而准確率則是逐漸的增加,最后穩定在95%左右,畫出圖像如下圖所示:
經過200001次的訓練之后,模型的准確率會穩定在95%左右,對於這個數據集來說,應該可以算是還不錯的模型了。