在前面两篇文章介绍了深度学习的一些基本概念, 本文则使用 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. 这是将神经元的偏置和权值统一的放到了权值矩阵里面.
\[ \left[ \begin{array}{c}w_{11} & w_{12} \\ w_{21} & w_{22}\end{array} \right] \cdot \left[\begin{array}{c}x_1 \\ x_2\end{array}\right] + \left[\begin{array}{c}b_1 \\ b_2\end{array}\right] = \left[\begin{array}{c}w_{11}x_1 + w{12}x_2 + b_1 \\ w_{21}x_1 + w_{22}x_2 + b_2 \end{array}\right] \]
可以将上式写成齐次的形式
\[ \left[ \begin{array}{c}w_{11} & w_{12} & b_1 \\ w_{21} & w_{22} &b_2 \end{array} \right] \cdot \left[\begin{array}{c}x_1 \\ x_2 \\ 1\end{array}\right] \]
使用统一的矩阵运算, 在正向反向传播的时候更方便.
在输出层的神经元并没有偏置, 所以要单独初始化输出层的权值矩阵
- 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 = \frac{\partial e}{\partial a^L} \odot \sigma'(z^L) \tag{BP1} \]
计算输出层的误差 \(\delta^L\)
- error = A[-1] - y # 输出层的误差, 均值方差作为损失函数
- D = [error * self.sigmoid_deriv(A[-1])]
得到输出层的误差 D 后, 根据 \(BP2\)计算各个层的误差
- \[ \delta^{L-1} = (W^L)^T\delta^L \odot \sigma'(z^{L-1}) \tag{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\)计算权值矩阵和偏置的导数
- \[ \frac{\partial e}{b_j^l} = \delta_j^l \tag{BP3} \]
- \[ \frac{\partial e}{w_{jk}^l} = \delta_j^l a_k^{l-1} \tag{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 来构建神经网络.
本文代码在 Git 库 https://github.com/brookicv/machineLearningSample
来源: https://www.cnblogs.com/wangguchangqing/p/10232283.html