接下来,我们来训练神经网络模型,但在此之前,需要介绍一下反向传播算法。
反向传播(backpropagation),简称为BP算法,是一种能够计算网络梯度的算法,使用求导的链式法则计算神经网络相对于权重的损失梯度,它工作的原理可以理解为是逐层求导,就是链式法则求导。我们使用BP算法就是用来计算神经网络中权重的梯度,从而进行模型的训练。但是,这与我们之前所用的梯度下降法是有区别的。
我们知道,正向传播的方向是从输入到输出,反向传播的方向是从损失回到权重,通过链式法则积累局部梯度。
接下,我们编写训练模型的代码,我们需要初始化权重,使用随机数值,破坏对称性,否则的权重都是一样的;采用较小的数值,加速训练,要避免模型训练的速度异常的缓慢,将会在代码中注释出来。
import numpy as np
import mnist as mi
def sigmoid(z):
return 1 / (1 + np.exp(-z))
def softmax(logits):
exponentials = np.exp(logits)
return exponentials / np.sum(exponentials, axis=1).reshape(-1, 1)
# 从S型函数输出计算S型函数的梯度,帮助计算w1与w2
def sigmoid_gradient(sigmoid):
return np.multiply(sigmoid, (1 - sigmoid))
def loss(Y, y_hat):
return -np.sum(Y * np.log(y_hat)) / Y.shape[0]
def prepend_bias(X):
return np.insert(X, 0, 1, axis=1)
def forward(X, w1, w2):
h = sigmoid(np.matmul(prepend_bias(X), w1))
y_hat = softmax(np.matmul(prepend_bias(h), w2))
return (y_hat, h)
# 反向传播算法
def back(X, Y, y_hat, w2, h):
w2_gradient = np.matmul(prepend_bias(h).T, (y_hat - Y)) / X.shape[0]
w1_gradient = np.matmul(prepend_bias(X).T, np.matmul(y_hat - Y, w2[1:].T)
* sigmoid_gradient(h)) / X.shape[0]
return w1_gradient, w2_gradient
def classify(X, w1, w2):
y_hat, _ = forward(X, w1, w2)
labels = np.argmax(y_hat, axis=1)
return labels.reshape(-1, 1)
# 初始化权重,采用w=正负根号下r分之一,r是权重矩阵的行数
# n_hidden_nodes是隐藏节点
def initialize_weights(n_input_variables, n_hidden_nodes, n_classes):
w1_rows = n_input_variables + 1
w1 = np.random.randn(w1_rows, n_hidden_nodes) * np.sqrt(1 / w1_rows) # 从标准正态分布中抽取一个随机数矩阵
w2_rows = n_hidden_nodes + 1
w2 = np.random.randn(w2_rows, n_classes) * np.sqrt(1 / w2_rows)
return w1, w2
def report(iteration, X_train, Y_train, X_test, Y_test, w1, w2):
y_hat, _ = forward(X_train, w1, w2)
training_loss = loss(Y_train, y_hat)
classifications = classify(X_test, w1, w2)
accuracy = np.average(classifications == Y_test) * 100.0
print("Iteration: %5d, Loss: %.8f, Accuracy: %.2f%%" %
(iteration, training_loss, accuracy))
def train(X_train, Y_train, X_test, Y_test, n_hidden_nodes, iterations, lr):
n_input_variables = X_train.shape[1]
n_classes = Y_train.shape[1]
w1, w2 = initialize_weights(n_input_variables, n_hidden_nodes, n_classes)
for iteration in range(iterations):
y_hat, h = forward(X_train, w1, w2)
w1_gradient, w2_gradient = back(X_train, Y_train, y_hat, w2, h)
w1 = w1 - (w1_gradient * lr)
w2 = w2 - (w2_gradient * lr)
report(iteration, X_train, Y_train, X_test, Y_test, w1, w2)
return (w1, w2)
w1, w2 = train(mi.X_train, mi.Y_train,
mi.X_test, mi.Y_test,
n_hidden_nodes=200, iterations=10000, lr=0.01)
至此,我们的训练模型已经完成,但需要说明的是权重是随机的,这种随机性不要担心,因为我们已经使它的随机性的值变得足够小了。可能最让人头疼的是原理的理解以及代码地编写,还有最令人费解的是超参数的选取,这些我们以后定能够处理!
超参数是不断试出来的!
预测可能需要耗费些时间,大约三个小时,我们来看下预测的结果:
这样的预测结果比我们之前处理的有所提高,但依旧还能改进。比如我们可以使用多层全连接神经网络以及卷积神经网络等,这就过渡到了深度学习,后续我们就可以把训练模型放到GPU上进行训练。
参考文献:
Programming Machine Learning: Form Coding to Deep Learning.[M],Paolo Perrotta,2021.6.