欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

误差反向传播法版 对MNIST数据集的二层神经网络的学习实现

程序员文章站 2022-05-21 22:21:48
...

# 1.神经网络的学习前提和步骤

前提

神经网络存在合适的权重和偏置。

步骤一(挑选mini-batch)

从训练数据中随机选出一部分数据,这部分数据称为mini-batch。
我们的目标是减少mini-batch这部分数据的损失函数的值。

步骤二(计算梯度)

为了减小mini-batch这部分数据的损失函数的值,需要求出有关各个权重参数的梯度。

步骤三(更新参数)

将权重参数沿梯度方向进行微小更新。

步骤四(重复)

重复步骤1,2,3

# 2.二层神经网络类的实现

同数值微分版的实现不同,这里的二层神经网络类运用上篇博客介绍的各种计算层(下图中的黑色方框)来进行实现。

下面是二层神经网络的计算图

误差反向传播法版 对MNIST数据集的二层神经网络的学习实现

其中利用计算图的正向传播和反向传播高效求梯度的方法,是最为重要的。

下面是二层神经网络类TwoLayerNet的具体实现代码

import sys,os
sys.path.append(os.pardir)
import numpy as np
from layers import *   # 导入加权和层类Affine和**函数层类ReLU
from collections import OrderedDict  # 导入有序字典类OrderedDict

# 应用误差反向传播法的二层神经网络类TwoLayerNet的实现
class TwoLayerNet:
	def __init__(self,input_size, hidden_size, output_size, weight_init_std=0.01):     # 第2、3、4个参数分别表示输入层、隐藏层、输出层神经元数,第5个参数表示初始化权重时高斯分布的规模
		# 初始化权重
		self.params = {}             # 初始化实例变量params(这是一个字典型变量)
		self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size) # 得到一个input_size*hidden_size大小的符合高斯分布的权重矩阵W1
		self.params['b1'] = np.zeros(hidden_size)                                      # 得到一个1*hidden_size大小的元素全为0的偏置矩阵b1
		
		self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
		self.params['b2'] = np.zeros(output_size)
		
		# 生成层
		self.layers = OrderedDict()  # 创建一个有序字典对象赋给实例变量layers
		self.layers['Affine1'] = Affine(self.params['W1'], self.params['b1'])          # layers中第一个元素    # 创建一个权重为W1偏置为b1的加权和层对象,存入有序字典变量layers,用layers['Affine1']表示
		self.layers['Relu1'] = ReLU()                                                  # layers中第二个元素    # 创建一个**函数ReLU层对象,存入有序字典变量layers,用layers['Relu1']表示
		
		self.layers['Affine2'] = Affine(self.params['W2'], self.params['b2'])          # layers中第三个元素    # 创建一个权重为W2偏置为b2的加权和层对象,存入有序字典变量layers,用layers['Affine2']表示
		self.lastLayer = SoftmaxWithLoss()                                             # 创建一个正规化和求损失函数值层对象赋给实例变量lastLayer
		
	def predict(self,x):                # 推理函数predict 按照顺序依次取有序字典变量layers的各个层,将上一层的输出作为下一层的输入,最终的得到推理结果y的加权和形式
		for layer in self.layers.values():
			x = layer.forward(x)	
		return x
		
	def loss(self, x, t):               # 损失函数loss 先通过推理函数predict得到加权和形式的推理结果y,然后通过二层神经网络最后一层lastLayer的前向函数得到损失函数值并输出
		y = self.predict(x)
		return self.lastLayer.forward(y,t)
		
	def accuracy(self, x, t):           # 精度函数accuracy 先通过推理函数predict得到加权和形式的推理结果y,然后通过记录y各行水平方向上最大值的下标得到简单解形式的推理结果y,最后通过y,t比较求出识别精度
		y = self.predict(x)
		y = np.argmax(y, axis=1)
		if t.ndim != 1 : t = np.argmax(t,axis=1)  # 如果t是one-hot形式,就将它转化为简单解形式
		accuracy = np.sum(y == t)/float(x.shape[0])
		return accuracy 
		
	def gradient(self,x,t):             # 梯度函数gradient 利用误差反向传播法求梯度(通过一次正向传播和一次反向传播,就高效地计算出了各权重偏置的梯度)
		# forward
		self.loss(x,t)                  # 调用二层神经网络的损失函数,实际上是进行了一次正向传播
		
		# backward
		dout = 1                        # 设反向传播的起点导数值为1
		dout = self.lastLayer.backward(dout)  # 调用最后一层的反向函数进行反向传播,利用正向传播得到的中间变量计算出单个图像的误差
		
		layers = list(self.layers.values())   # 定义一个与实例变量layers内容相同但存放顺序相反的局部变量layers
		layers.reverse()
		for layer in layers:                  # 按照layers的顺序(即二层神经网络的逆序)将dout进行反向传播,依次计算出损失函数关于各个参数的导数,即梯度
			dout = layer.backward(dout)
			
		# 设定
		grads = {}
		grads['W1'] = self.layers['Affine1'].dW
		grads['b1'] = self.layers['Affine1'].db
		grads['W2'] = self.layers['Affine2'].dW
		grads['b2'] = self.layers['Affine2'].db
		
		return grads

# 3.对MNIST数据集的学习和评价

下面的实现与数值微分版的实现基本一致,仅仅是计算梯度的第34行代码改成了调用用误差反向传播法实现的函数。

具体实现如下所示

import sys,os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
import matplotlib.pyplot as plt


# 以二层神经网络类TwoLayerNet为对象,使用MNIST数据集进行学习

(x_train, t_train),(x_test, t_test) = load_mnist(normalize = True, one_hot_label = True) # 取出MNIST数据集

train_acc_list = []   # 定义一个列表变量,用于记录每个epoch,训练数据的精度
test_acc_list = []    # 定义一个列表变量,用于记录每个epoch,测试数据的精度



# 超参数
iters_num = 10000              # 定义变量iters_num用于表示更新次数(这里设置为10000)
train_size = x_train.shape[0]  # 定义变量train_size用于表示训练数据总数目(即60000)
batch_size = 100               # 定义变量batch_size用于表示每次使用的批数据的大小(这里设置为100)
learning_rate = 0.1            # 定义变量learning_rate用于表示学习率的大小(这里设置为0.1)

iter_per_epoch = max(train_size / batch_size, 1) # 这里定义每个epoch的大小,实际定义为6000,以后每更新6000次,计算一次训练数据和测试数据的识别精度
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10) # 创建一个二层神经网络类TwoLayerNet的对象network,这里定义的入口参数是用于设置权重参数的形状的

for i in range(iters_num):     # for循环i从0到9999(更新10000次)
	# 获取mini-batch
	batch_mask = np.random.choice(train_size, batch_size)  # 从0到59999随机选择100个值存至数组batch_mask
	x_batch = x_train[batch_mask]                          # 把数组batch_mask的值作为数组x_train的下标,取出对应元素形成数组x_batch
	t_batch = t_train[batch_mask]                          # 把数组batch_mask的值作为数组t_train的下标,取出对应元素形成数组t_batch
	
	# 计算梯度
	#grad = network.numerical_gradient(x_batch, t_batch)    # 利用数值微分,计算出损失函数关于各个权重参数的梯度 
	grad = network.gradient(x_batch, t_batch)               # 利用误差反向传播法,高效地计算出损失函数关于各个权重参数的梯度
	
	# 更新参数
	for key in ('W1','b1','W2','b2'):
		network.params[key] -= learning_rate * grad[key]
		
	print(i,"次更新已经完成")	
		
	# 记录学习过程
	if i % iter_per_epoch == 0:                                # 对于每个epoch
		train_acc = network.accuracy(x_train, t_train)     # 计算训练数据的识别精度
		test_acc = network.accuracy(x_test, t_test)        # 计算测试数据的识别精度
		train_acc_list.append(train_acc)                   # 将计算出的识别精度存放至各自的列表
		test_acc_list.append(test_acc)
		print(train_acc, test_acc)                         # 打印出训练数据和测试数据的识别精度(用于比较,观察是否存在过拟合现象)
	

# 绘制图形
x = np.arange(len(train_acc_list))                                       # 设置x为精度数组下标
plt.plot(x, train_acc_list, label='train acc')                           # 以训练数据精度数组的值为y,绘制标签为'train acc'的曲线
plt.plot(x, test_acc_list, label='test acc', linestyle='--')             # 以测试数据精度数组的值为y,用虚线绘制标签为'test acc'的曲线
plt.xlabel("epochs")                                                     # 设置x轴标签为"epochs"
plt.ylabel("accuracy")                                                   # 设置y轴标签为"accuracy"
plt.ylim(0, 1.0)                                                         # 设置y轴的范围为0到1
plt.legend(loc='lower right')                                            # 设置图例,控制其在图像右下角
plt.show()                                                               # 显示图像

实验结果如下

误差反向传播法版 对MNIST数据集的二层神经网络的学习实现

实验结果是一样的,但这比以前使用数值微分的实现要快了很多很多倍。

 

# 本博客参考了《深度学习入门——基于Python的理论与实现》(斋藤康毅著,陆宇杰译),特在此声明。

相关标签: python与深度学习