在前面兩篇文章介紹了深度學習的一些基本概念,本文則使用Python實現一個簡單的深度神經網絡,並使用MNIST數據庫進行測試。
神經網絡的實現,包括以下內容:
- 神經網絡權值的初始化
- 正向傳播
- 誤差評估
- 反向傳播
- 更新權值
主要是根據反向傳播的4個基本方程,利用Python實現神經網絡的反向傳播。
初始化
首先定義代表神經網絡的類NeuralNetwork
,
class NeuralNetwork:
def __init__(self,layers,alpha=0.1):
self.W = []
self.layers = layers
self.alpha = alpha
有三個屬性,
W
存儲各個層之間的權值矩陣,也是神經網絡要更新學習的layers
神經網絡的結構,例如:[2,2,1]
表示輸入層有2個神經元,隱藏層2個神經元,輸出層只有1個神經元。alpha
學習速率
接下來初始化各個層之間的權值矩陣
for i in np.arange(0,len(layers) - 2):
w = np.random.randn(layers[i] + 1,layers[i + 1] + 1)
self.W.append(w / np.sqrt(layers[i]))
注意上面生成權值矩陣的大小layers[i] + 1,layers[i + 1] + 1
,都加了1。 這是將神經元的偏置和權值統一的放到了權值矩陣里面。
可以將上式寫成齊次的形式
使用統一的矩陣運算,在正向反向傳播的時候更方便。
在輸出層的神經元並沒有偏置,所以要單獨初始化輸出層的權值矩陣
w = np.random.randn(layers[-2] + 1,layers[-1])
self.W.append(w / np.sqrt(layers[-2]))
下面實現Python的magic function __repr__
輸出神經網絡結構
def __repr__(self):
return "NeuralNetWork:{}".format("-".join(str(l) for l in self.layers))
激活函數
在神經網絡中使用sigmoid
作為激活函數,實現sigmoid
及其導數
def sigmoid(self,x):
return 1.0 / (1 + np.exp(-x))
def sigmoid_deriv(self,x):
return x * (1 - x)
正向反向傳播
這一部分是神經的網絡的核心了。下面實現fit
方法,在方法中完成神經網絡權值更新(訓練)的過程。
def fit(self,X,y,epochs=1000,displayUpdate=100):
X = np.c_[X,np.ones((X.shape[0]))]
for epoch in np.arange(0,epochs):
for(x,target) in zip(X,y):
self.fit_partial(x,target)
# check to see if we should display a training update
if epoch == 0 or (epoch + 1) % displayUpdate == 0:
loss = self.calculate_loss(X,y)
print("[INFO] epoch={},loss={:.7f}".format(epoch + 1,loss))
該函數有4個參數:
X
是輸入的樣本數據y
是樣本的真是值epochs
訓練的輪數displayUpdate
輸出訓練的loss
值。
X = np.c_[X,np.ones((X.shape[0]))]
將輸入訓練的樣本表示為齊次向量(也就是在末尾添1)。fit_partial
是對輸入的每個樣本進行訓練,包括正向傳播,反向傳播以及權值的更新。
def fit_partial(self,x,y):
A = [np.atleast_2d(x)]
# 正向傳播
# 層層之間的數據傳遞
for layer in np.arange(0,len(self.W)):
# 輸入經過加權以及偏置后的值
net = A[layer].dot(self.W[layer])
# 神經元的輸出
out = self.sigmoid(net)
# 保存下來,反向傳播的時候使用
A.append(out)
上面完成了神經玩過的正向傳播過程,下面根據反向傳播的4個基本方程進行反向傳播。
首先根據\(BP1\),
計算輸出層的誤差\(\delta^L\)
error = A[-1] - y # 輸出層的誤差,均值方差作為損失函數
D = [error * self.sigmoid_deriv(A[-1])]
得到輸出層的誤差D
后,根據\(BP2\)計算各個層的誤差
for layer in np.arange(len(A) - 2,0 ,-1):
delta = D[-1].dot(self.W[layer].T)
delta = delta * self.sigmoid_deriv(A[layer])
D.append(delta)
D = D[::-1]
將D
反轉,和各個層的索引對應起來,下面根據\(BP3,BP4\)計算權值矩陣和偏置的導數
for layer in np.arange(0,len(self.W)):
self.W[layer] += -self.alpha * A[layer].T.dot(D[layer])
首先求得權值和偏置的導數(權值和偏置統一到同一個矩陣中)A[layer].T.dot(D[layer]
,然后將梯度乘以學習速率alpha
每次權值減小的步長。
上述就完成利用反向傳播算法更新權值的過程。 關於反向傳播四個基本方程的推導過程,可以參考文章深度學習與計算機視覺: 搞懂反向傳播算法的四個基本方程
誤差評估
上面代碼已經實現了深度學習的訓練過程,下面實現predict
輸出使用訓練好的模型預測的結果,calculate_loss
評估訓練后模型的評估
def predict(self,X,addBias=True):
p = np.atleast_2d(X)
if addBias:
p = np.c_[p,np.ones((p.shape[0]))]
for layer in np.arange(0,len(self.W)):
p = self.sigmoid(np.dot(p,self.W[layer]))
return p
def calculate_loss(self,X,targets):
targets = np.atleast_2d(targets)
predictions = self.predict(X,addBias=False)
loss = 0.5 * np.sum((predictions - targets) ** 2)
return loss
MNIST分類識別
使用上面實現的深度神經網絡對MNIST手寫體進行識別,首先導入必要的包
import NeuralNetwork
from sklearn.preprocessing import LabelBinarizer
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn import datasets
需要使用sklearn包中的一些工具,進行數據的處理。
# load MNIST數據集,並使用min/max對數據進行歸一化
digits = datasets.load_digits()
data = digits.data.astype("float")
data = (data - data.min()) / (data.max() - data.min())
print("[INFO] samples: {}, dim: {}".format(data.shape[0], data.shape[1]))
將數據拆分為訓練集和測試集,並對MNIST的類別進行編碼
(trainX, testX, trainY, testY) = train_test_split(data, digits.target, test_size=0.25)
# convert the labels from integers to vectors
trainY = LabelBinarizer().fit_transform(trainY)
testY = LabelBinarizer().fit_transform(testY)
下面構建神經網絡結構,並使用訓練集進行訓練
nn = NeuralNetwork([data.shape[1], 32,16, 10])
print ("[INFO] {}".format(nn))
nn.fit(trainX, trainY, epochs=1000)
神經網絡結構為:64-32-16-10,其中64為輸入數據的大小,10輸出類別的個數。
最后評估訓練得到的模型
predictions = nn.predict(testX)
print(classification_report(testY.argmax(axis=1), predictions.argmax(axis=1)))
最終的輸出結果:
[INFO] loading MNIST (sample) dataset...
[INFO] samples: 1797, dim: 64
[INFO] training network...
[INFO] NeuralNetWork:64-32-16-10
[INFO] epoch=1,loss=607.1711647
[INFO] epoch=100,loss=7.1082795
[INFO] epoch=200,loss=4.0731690
[INFO] epoch=300,loss=3.1401868
[INFO] epoch=400,loss=2.8801101
[INFO] epoch=500,loss=1.8738122
[INFO] epoch=600,loss=1.7461474
[INFO] epoch=700,loss=1.6624043
[INFO] epoch=800,loss=1.1852884
[INFO] epoch=900,loss=0.6710255
[INFO] epoch=1000,loss=0.6336826
[INFO] evaluating network...
precision recall f1-score support
0 1.00 0.95 0.97 39
1 0.84 1.00 0.92 38
2 1.00 0.98 0.99 41
3 0.93 0.98 0.95 52
4 0.91 0.97 0.94 40
5 0.98 0.98 0.98 41
6 1.00 0.96 0.98 51
7 1.00 0.98 0.99 48
8 0.98 0.89 0.93 55
9 0.98 0.93 0.95 45
micro avg 0.96 0.96 0.96 450
macro avg 0.96 0.96 0.96 450
weighted avg 0.96 0.96 0.96 450
如上測試結果,在測試集的上表現還算不錯。
總結
本文使用Python簡單的實現了一個神經網絡。 主要是利用反向傳播的4個基本方程,實現反向傳播算法,更新各個神經元的權值。 最后使用該網絡,對MNIST數據進行識別分類。
上面實現的神經網絡只是“玩具”,用以加深對深度學習的訓練過程以及反向傳播算法的理解。后面將使用Keras和PyTorch來構建神經網絡。