文章目录
- (一) 问题描述
- (二) 设计简要描述
- (三) 程序清单
- (四) 结果分析
- (五) 调试报告
- (六) 实验小结
(一) 问题描述
不使用任何机器学习框架,仅仅通过Numpy库构建一个最简单的全连接前馈神经网络,并用该网络识别mnist提供的手写数字体。
(二) 设计简要描述
机器学习的三个基本步骤——
程序设计思路——(此图放大可看清)
(三) 程序清单
import numpy as np
from mnist import load_mnist
# sigmoid函数
def sigmoid(x):
return 1 / (1 + np.exp(-x))
# softmax函数
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a, axis=1, keepdims=True)
y = exp_a / sum_exp_a
return y
# 损失函数
# y是神经网络的输出,t是正确解标签
def cross_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
# 计算并返回数值微分值的代码
def numerical_gradient(f, x):
# f是函数,x是自变量
h = 1e-4 # 0.0001
grad = np.zeros_like(x) # 生成和x形状相同的数组
it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite']) # 高效的多维迭代器对象来遍历数组
while not it.finished:
idx = it.multi_index # 表示进入下一次迭代
tmp_val = x[idx]
# f(x+h)的计算
x[idx] = tmp_val + h
fh1 = f(x)
# f(x-h)的计算
x[idx] = tmp_val - h
fh2 = f(x)
grad[idx] = (fh1 - fh2) / (2 * h)
x[idx] = tmp_val # 还原,因为这里只是计算一下梯度,需要暂时修改x矩阵
it.iternext()
return grad
class TwoLayerNet:
# 模型初始化
# size是神经元数量
def __init__(self, input_size, hidden_size, output_size, weight_init_std=0.01):
# 初始化权重
self.params = {}
# 第一层的权重
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size) # rand函数根据给定维度生成[0,1)?
# 第一层的偏置
self.params['b1'] = np.zeros(hidden_size)
# 请添加网络第二层的权重和偏置的初始化代码
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b2'] = np.zeros(output_size)
# 模型前向传播过程
def forward(self, x):
# 请从参数字典获取网络参数
W1, W2, b1, b2 = self.params['W1'], self.params['W2'], self.params['b1'], self.params['b2']
# 实现第一层的运算
z1 = np.dot(x, W1) + b1
h1 = sigmoid(z1)
# 请实现第二层的运算
h2 = np.dot(h1, W2) + b2 # 注意,第二层的输入x是第一层的输出h1
y = softmax(h2) # 第二层的激活函数是softmax而不是sigmoid
return y
# 定义损失函数
def loss(self, x, t): # x:输入数据, t:正确标签作为监督数据
y = self.forward(x)
return cross_entropy_error(y, t)
# 计算预测的准确率
def accuracy(self, x, t): # 假定输入的数据x和标签t都是mini-batch形式的
# 请补充实现模型前向输出的代码
y = self.forward(x)
# 请补充提取模型预测类别和标签真实类别的代码
y = np.argmax(y, axis=1) # argmax()函数的作用是沿着一个轴返回最大值的索引
t = np.argmax(t, axis=1)
# 请补充计算并返回模型类别预测准确率的代码
accuracy = np.sum(y == t) / float(x.shape[0])
return accuracy
# 通过数值微分的方式计算模型参数的梯度
def gradient(self, x, t):
# 定义字典形式的参数梯度
grads = {}
# 请定义需传入numerical_gradient函数的需求梯度的函数
loss_W = lambda W: self.loss(x, t)
# 请补充通过数值微分的方法计算参数W1、W2、b1、b2的梯度的代码
grads['W1'] = numerical_gradient(loss_W, self.params['W1'])
grads['b1'] = numerical_gradient(loss_W, self.params['b1'])
grads['W2'] = numerical_gradient(loss_W, self.params['W2'])
grads['b2'] = numerical_gradient(loss_W, self.params['b2'])
# 返回计算出的梯度
return grads
if __name__ == '__main__':
# 获得MNIST数据集
(x_train, t_train), (x_test, t_test) = load_mnist(one_hot_label=True)
# 定义训练循环迭代次数
iters_num = 10000
# 获取训练数据规模
train_size = x_train.shape[0]
# 定义训练批次大小
batch_size = 100
# 定义学习率
learning_rate = 0.1
# 创建记录模型训练损失值的列表
train_loss_list = []
# 创建记录模型在训练数据集上预测精度的列表
train_acc_list = []
# 创建记录模型在测试数据集上预测精度的列表
test_acc_list = []
# 计算一个epoch所需的训练迭代次数(一个epoch定义为所有训练数据都遍历过一次所需的迭代次数)
iter_per_epoch = 1
MyTwoLayerNet = TwoLayerNet(input_size=784, hidden_size=50, output_size=10) # 实例化TwoLayerNet类创建MyTwoLayerNet对象
#训练循环的代码
for i in range(iters_num):
# 在每次训练迭代内部选择一个批次的数据
batch_choose = np.random.choice(train_size, batch_size) # choice()函数的作用是从train_size中随机选出batch_size个
x_batch = x_train[batch_choose]
t_batch = t_train[batch_choose]
# 请补充计算梯度的代码
grad = MyTwoLayerNet.gradient(x_batch, t_batch)
# 请补充更新模型参数的代码
for params in ('W1', 'b1', 'W2', 'b2'):
MyTwoLayerNet.params[params] -= learning_rate * grad[params]
# 请补充向train_loss_list列表添加本轮迭代的模型损失值的代码
loss = MyTwoLayerNet.loss(x_batch, t_batch)
train_loss_list.append(loss)
# 判断是否完成了一个epoch,即所有训练数据都遍历完一遍
if i % iter_per_epoch == 0:
# 请补充向train_acc_list列表添加当前模型对于训练集预测精度的代码
train_acc = MyTwoLayerNet.accuracy(x_train, t_train)
# 请补充向test_acc_list列表添加当前模型对于测试集预测精度的代码
test_acc = MyTwoLayerNet.accuracy(x_test, t_test)
train_acc_list.append(train_acc)
test_acc_list.append(test_acc)
# 输出一个epoch完成后模型分别在训练集和测试集上的预测精度以及损失值
print("iteration:{} ,train acc:{}, test acc:{} ,loss:{}".format(i, round(train_acc, 3), round(test_acc, 3),
round(loss, 2)))
(四) 结果分析
运行约15个小时后的结果
初始精度大约为0.1,因为一共10个数字……
经过如此之长的时间训练精度也只提高到0.2
效果这样差的原因是:
① 没有用上GPU
② 梯度下降算法过于朴素,没有用上反向传播项目结构
(五) 调试报告
- 权重矩阵的形状不知道如何设置,联想到神经网络的示意图,每层网络之间有多条连线,每个连线对应一个权重,故权重矩阵设置为上一层神经元个数X下一层神经元个数,即
# 第一层的权重
self.params['W1'] = weight_init_std * np.random.randn(input_size, hidden_size)
# 第二层的权重
self.params['W2'] = weight_init_std * np.random.randn(hidden_size, output_size)
- 训练太慢
将每一次的训练批数设置为1,即
# 计算一个epoch所需的训练迭代次数(一个epoch定义为所有训练数据都遍历过一次所需的迭代次数)
iter_per_epoch = 1
- 写计算梯度的函数时应当注意每算完一个梯度要将矩阵还原
(六) 实验小结
本次实验没有通过深度学习框架,而是主要依靠numpy库构建了一个含有一个隐藏层的神经网络,将交叉熵作为损失函数,在mnist训练集和测试集上分别求精度,更深刻地体会到了神经网络的运作原理,可以改进的地方有:
- 梯度下降方式过于朴素,导致每次训练时间开销大
- 神经网络的形状是写死的,不利于扩展
此外我加深了对机器学习的认识,无论那种类型的神经网络,本质上都可以看作是一个数学函数,其输入输出由具体情景决定,函数的框架在训练前就已经设定好,训练的本质是是通过输出与目标值的比对,不断修改这个函数的参数值,直到输出与目标的差距很小,就可以认为得到了一个较好的模型。