前言
本文基础
用numpy构建多种损失函数
结合numpy及mnist库的简单神经网络演练
友情链接
正题
本文通过对前向神经网络的基本学习,来完成一个简单的、效率并不高的mnist库的二层神经网络训练模型,方便记录神经网络的基本学习。
基本思路
- 初始化二层神经网络权重
- 通过
predict()
函数完成神经网络前向预测功能(包括sigmoid函数以及softmax函数编写) - 编写
loss()
函数来计算一次训练的损失(包括cross_entropy_error函数的编写) - 编写
numerical_gradient()
函数计算本次训练各层参数的更新梯度 - 编写
gradient_decent()
更新参数权重 - 编写
get_accuracy()
函数来计算预测正确率 - 通过mini_batch方式进行训练
下面由此循序渐进展开
一.初始化二层神经网络权重
首先二层神经网络指的是由一层输入层、一层隐藏层以及一层输出层构成的简单神经网络,我们依旧使用mnist库,则输入层大小为784,定义隐藏层大小为100,输出层大小为10(有10种状态),那么权重W1
是输入层
以及隐藏层
之间权重矩阵,大小为784*100
;W2
则为隐藏层
和输出层
之间权重矩阵,大小为100*10
,偏置b1
大小为100*1
,偏置b2
大小为10*1
。
def __init__(self, input_layer_size, hidden_layer_size, output_layer_size, weight_std=0.01):
self.params = {"W1": weight_std * np.random.randn(input_layer_size, hidden_layer_size),
"W2": weight_std * np.random.randn(hidden_layer_size, output_layer_size),
"b1": np.zeros(hidden_layer_size),
"b2": np.zeros(output_layer_size)}
weight_std表征初始化权重数量级,可以不添加。
二.前向预测函数predict
在编写predict
函数之前我们需要两个函数:sigmoid()
和softmax
,具体编写理解在前面博客中有,这里只是贴一下:
@classmethod
def sigmoid(cls,X):
return 1/(1+np.exp(-X))
@classmethod
def softmax(cls, X):
if X.ndim == 1:
X = X.reshape(1, X.shape[0])
c = np.max(X, axis=1)
exp_a = np.exp(X - np.tile(c, (X.shape[1],1)).T)
exp_sum = np.sum(exp_a, axis=1)
return exp_a/np.tile(exp_sum, (exp_a.shape[1],1)).T
那么就可以实现predict()
函数,基本思路即通过sigmoid
层间神经元迭代,最终经过softmax
计算结果输出。
def predict(self, X):
y = X
for W, b in zip(["W1","W2"], ["b1","b2"]):
y = self.sigmoid(np.dot(X, self.params[W])+ np.tile(self.params[b], (X.shape[0], 1)))
return self.softmax(y)
这样就可以完成神经网络最基本的功能:预测功能,当然这种预测是不会更新权重的。
三.损失函数构造
这一步主要是构造一个损失函数loss
,用来计算最终预测结果的损失是多少。
首先损失函数选择的是交叉熵损失函数,构造如下(如果想更深入了解请看上篇博文):
@classmethod
def cross_entropy_error(cls, Y, labels):
if Y.ndim == 1:
Y = Y.reshape(1, Y.shape[0])
delta = 1e-7
batch_size = Y.shape[0]
# 如果两个数组维数一致,则说明是one-hot编码,需要转换为label形式
if Y.ndim == labels.ndim:
labels = np.argmax(labels, axis=1)
return -np.sum(np.log(Y[np.arange(batch_size), labels]+delta))/batch_size
从而完成loss函数定义:
def loss(self, X, labels):
# 首先对X进行预测
Y = self.predict(X)
# 而后返回交叉熵损失函数计算后的结果
return self.cross_entropy_error(X, labels)
四. 实现梯度计算
这一步主要是为了训练神经网络而做的反馈,通过梯度更新的方式来使得神经网络真正“活”起来。
简单原理即通过对权重或偏重的每一个参数求导数,得到这个参数的梯度更新矩阵。
当然本神经网络中最慢、最值得优化的部分就是这个部分,因为这样更新梯度会非常冗余。
因为每一次求导的过程就伴随着调用loss
函数一次,意味着要重新将输入数据重新预测一遍,从而得到这个参数对整个神经网络的影响,我的下篇博客大概会从这个地方说起。
当然这种方式是神经网络最初的构型,也应该去实现一下:
def numerical_gradient(self, X, labels):
loss_w = lambda w:self.loss(X, labels)
gradient = {}
for key in ["W1", "W2", "b1", "b2"]:
gradient[key] = self.numerical_gradient_for_param(loss_w, self.params[key])
return gradient
# 分别计算参数的梯度情况
def numerical_gradient_for_param(self, f, param):
h = 1e-4
temp_grad = np.zero_like(param)
it = np.nditer(param, flags=['multi-index'], op_flags=['readwrite'])
while not it.finished:
idx = it.multi-index
temp = param[idx]
param[idx]+=h
f1 = f(param)
param[idx]-=2*h
f2 = f(param)
param[idx] = temp
temp_grad[idx] = (f1 -f2)/2*h
it.iternext()
return temp_grad
五.更新梯度,实现神经网络训练功能
这一步是神经网络的灵魂,通过完成梯度更新,神经网络才能不断的迭代训练,最终接近模型的局部最优解。
更新梯度的操作也十分的简单,即对每个参数进行:
x
1
=
x
1
−
η
α
f
α
x
1
x_1 = x_1 - η\frac{αf}{αx_1}
x1=x1−ηαx1αf
η
为学习率,大小决定着最终最优解能否最接近最优解,决定着最终的准确度
def gradient_descent(self, X, labels, lr=0.01):
grad = self.numerical_gradient(X, labels)
for key in ["W1", "W2", "b1", "b2"]:
self.params[key] -=lr*grad[key]
return self.params
六.准确率预测函数实现
神经网络最终训练结果当然需要和labels进行比对,从而测定当前神经网络的训练情况,方法也非常的简单:
def get_accuracy(self, X, labels):
y = np.argmax(self.predict(X),axis=1)
return np.sum(y==labels)/labels.shape[0]
七.使用mini_batch方式进行训练以及预测
(x_train, y_train), (x_test, y_test) = load_mnist()
train_acc_list = []
test_acc_list = []
net = TwoLayerNetwork(x_train.shape[1], 100, 10)
batch_size = 100
learning_rate = 0.01
iter_times = 10000
epoch = max(x_train.shape[0]/batch_size,1)
for i in range(iter_times):
x_batch_num = np.random.choice(x_train.shape[0], batch_size)
x_batch = x_train[x_batch_num]
y_batch = y_train[x_batch_num]
net.gradient_descent(x_batch, y_batch)
if i % epoch == 0:
train_acc = net.get_accuracy(x_train, y_train)
test_acc = net.get_accuracy(x_test, y_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
print("训练集预测准确度:"+str(train_acc)+" 测试集预测准确度:"+str(test_acc))
这一部分中的batch_size是从训练集中一次迭代抽取的数量,允许重复,增加随机性。
epoch
参数是为了计算一轮训练进行的循环次数,在这里面即60000/100=600次,每600次迭代相当于训练集所有数据都训练了一遍,然后输出训练结果。
经过这样一轮流程,一个简单的二层神经网络就完成了,下一篇会分享反向传播的优化,通过对神经网络的梯度计算实现优化,可以非常明显的提高神经网络训练速度。