Python神经网络识别手写数字-MNIST数据集
一、手写数字集-MNIST二、数据预处理输入数据处理输出数据处理三、神经网络的结构选择四、训练网络测试网络测试正确率的函数五、完整的训练学习率的调整训练轮数epoch改变网络结构附录代码一、手写数字集-MNIST
要让计算机能够识别出来图片的内容是一件十分困难的事情,识别人的手写笔记也不简单,它们不像印刷字符那样清晰明确,因此在识别上带来一定的困难。
要想让神经网络达到预期的效果就需要大量数据进行学习。那数据怎么来,不用自己收集,国外已经有人制作了一个手写数据集MNIST。在下面这个网站就可以下载完整的数据集。
下载链接:MNIST数据集
网站提供了两种CSV文件:训练集、测试集。
下图展示了打开后的CSV部分数据。
数据的第一列是标签,也就是实际上图像代表的数字,如‘2‘。这是我们希望得到的答案。随后的数值,也就是从每一行除了第一个数字以外的数值,代表的每一个像素的灰度值,这个数组的尺寸为28*28。这意味着,标签后面跟随了784个数值。光看数值,我们很难看出这张图片显示的是什么。所以在进行训练之前,我们可以将其转化为图像的形式来确认一下这是手写数字。
利用python打开文件,查看一下数据。
data_file = open("C:/Users/15619/train_file.csv", 'r') # 打开文件data_list = data_file.readlines() # 将文件中所有的行读入data_file变量data_file.close # 关闭文件data_list[0] # 查看数据的第一行数
最后输出的结果如下:
上图我们可以看到,数据之间用逗号分隔,所有数值大小都处在0-255的范围内。我们接下来会使用下列代码将用逗号分隔的数字转化为数组。
用函数按照逗号为分隔标志,将数据拆分成单个数值。利用切片忽略掉第一个数值,剩下的数据就是784个像素值,可以转化为28*28=784的数组。imshow绘制数组
代码如下:
import matplotlib.pyplot import numpy%matplotlib inlineall_values = data_list[0].split(',') # 先把数值转化为numpy的类型,然后利用reshape转化为数组image_array = numpy.asfarray(all_values[1:]).reshape((28,28)) matplotlib.pyplot.imshow(image_array, cmap='Greys', interpolation='None')
二、数据预处理
输入数据处理
上一节我们知道了如何获取到现成的数据,然后还通过一小段代码输出了一行数据的可视化图像。这节我们来看看如何将数据喂给神经网络。首先要进行下面的处理:
将0-255的像素值,缩放到0.01-1之间,选择0.01作为最小值是为了避免先前观察到的0值输入最终会认为地造成权重更新失败。
首先将像素值/255得到0-1的数值,然后乘以0.99范围变为0-0.99,接着加上0.01,这样可以将整体数值偏移到0.01-1.0。
实现代码
scaled_input = (numpy.asfarray(all_values[1:])/255.0*0.99)+0.01print(scaled_input)
输出数据处理
神经网络的输入数据已经准备就绪了,现在,我们来讨论一下输出的数据以及匹配的正确输出答案。我们采用的是simoid激活函数,该函数无法输出负数或这大于1的数值,且无法达到0或者1,因为它们是两个极限值。因此,我们得对目标值进行一定处理。
我们要实现的目标是能够对图像进行分类,然后系统输出10个数值,分别对应10个结果。这意味这我们需要选择10个节点。数值的标签是0-9之间的一个,当答案是1的时候,我们希望的输出是对应’1‘的一个节点能够输出尽可能接近1的数值,也就是该节点属于激活状态,而其他的节点尽可能处于抑制状态。
下图展示了输出节点的输出示例。
从第一列看,标签是5,输出第六个节点的数值较大,而其他的数值比较小,接近于零。
而最后一列比较有意思,输出的最大数值对应9,而对应4的数值为中等大小。这说明识别对象比较模糊,使得手写字迹难以辨认。通常来说,我们会选择最大的数值作为最后的答案。
现在,我们需要把标签转化为一个目标数组,用于神经网络的训练。比如标签为“5”的节点,其他节点的数值都应该很小,所以我们希望的输出应该是[0,0,0,0,0,1,0,0,0,0]。但是前面我们知道,输出不可能为0或者1,所以我们需要对0和1分别用0.01和0.99来代替。所以最后我们选用的目标输出是[0.01,0.01,0.01,0.01,0.01,0.99,0.01,0.01,0.01,0.01]。
通过下面的代码来实现上述内容:
# 输出节点设定为10outnodes = 10# 用zero()函数创建一个用零填充的长度为10的数组。targets = numpy.zero(outnodes)+ 0.01# 标签位置的数值用0.99来代替tartets[int(all_value[0])] = 0.99
三、神经网络的结构选择
输入节点784:图像尺寸是28*28大小,所以选择这个输入节点数。
隐含层节点100:选择100个节点并不是通过什么科学计算的方法得到的。而是,首先我们认为神经网络可以找到输入数据中的特征,而这些特征通常都可以用更为简单的方式来表达,因此隐含层节点选择不会超过输入节点数。而选择数量太少的话,网络能力就会不足。100个节点也不是固定的,选择多少个,最好的办法就是多次实验,然后选择一个合适的数字即可。
输出节点10:由于分类标签有十种结果,所以选择10个节点。
四、训练网络
综合以上描述后的代码如下,我们通过对一个小样本的数据进行训练,打开包含100条数据的csv文件进行网络训练。
import numpyimport scipy.specialimport matplotlib.pyplot%matplotlib inlineclass neuralNetwork():# 初始化函数def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):self.innodes = inputnodesself.hinodes = hiddennodesself.outnodes = outputnodesself.lr = learningrate # 链接权重# 三个参数:正态分布中心,标准方差,numpy数组大小self.winhi = numpy.random.normal(0.0, pow(self.hinodes, -0.5), (self.hinodes, self.innodes))# 其中0.0是正态分布的中心点,pow(self.hinodes, -0.5)就是对这个隐含层节点数进行开平方。# 最后一个参数就是我们希望得到的numpy数组的大小。self.whiout = numpy.random.normal(0.0, pow(self.outnodes, -0.5),(self.outnodes, self.hinodes))# 激活函数self.activation_function = lambda x: scipy.special.expit(x)passdef train(self, inputs_list, targets_list):# 把输入列表转化为numpy型的2维inputs = numpy.array(inputs_list,ndmin=2).Ttargets = numpy.array(targets_list,ndmin=2).T# 计算隐含层输出hidden_inputs = numpy.dot(self.winhi, inputs)hidden_outputs = self.activation_function(hidden_inputs)# 计算输出层输出final_inputs = numpy.dot(self.whiout, hidden_outputs)final_outputs = self.activation_function(final_inputs)# 计算输出层的误差output_errors = targets - final_outputshidden_errors = numpy.dot(self.whiout.T, output_errors)# 更新链接权重self.whiout +=self.lr*numpy.dot((output_errors*final_outputs*(1-final_outputs)), numpy.transpose(hidden_outputs))# final_outputs已经经过了sigmoid运算,所以可以直接调用。# 对输入层和隐含层之间的权重进行更新self.winhi +=self.lr*numpy.dot((hidden_errors*hidden_outputs*(1.0-hidden_outputs)), numpy.transpose(inputs))# 输入的是一个数列例如[1,2,3,4]def query(self, inputs_list):inputs = numpy.array(inputs_list,ndmin=2).Thidden_inputs = numpy.dot(self.winhi, inputs)# 经过隐含层的激活函数hidden_outputs = self.activation_function(hidden_inputs)# 信号从隐含层传递到输出层final_outputs = numpy.dot(self.whiout, hidden_outputs)# 信号经过输出层的激活函数final_outputs = self.activation_function(final_outputs)return final_outputs# 输入节点,隐含层节点,输出节点,学习率input_nodes = 784hidden_nodes = 100output_nodes = 10learning_rate = 0.3epochs = 3# 创建一个神经网络对象n = neuralNetwork(input_nodes, hidden_nodes, output_nodes,learning_rate)# 读取训练数据training_data_file = open("C:/Users/15619/train_file.csv", 'r')training_data_list = training_data_file.readlines()training_data_file.close()# 训练神经网络for e in range(epochs):for record in training_data_list:all_values = record.split(',')# 输入数据预处理inputs = (numpy.asfarray(all_values[1:])/255*0.99)+0.01# 目标数据预处理targets = numpy.zeros(output_nodes)+0.01targets[int(all_values[0])]=0.99n.train(inputs, targets)passpass
测试网络
上面,我们用了一个100条记录的数据进行网络训练,我们现在可以检测一下网络的效果怎么样。先来使用刚刚训练数据的第一条来进行测验一下。
首先我们要读取数据。
test_data_file = open("C:/Users/15619/train_file.csv", 'r')test_data_list = test_data_file.readlines()test_data_file.close()all_values = test_data_list[0].split(',')print(all_values[0])
下面展示了该条数据的正确结果为7.
我们来通过调用网络的query()函数来查询一下神经网络的输出。
test_inputs = (numpy.asfarray(all_values[1:])/255.0*0.99)+0.01n.query(test_inputs)
输出的结果如下:
可见,输出的第八个节点,也就是对应’7‘的节点数值比较大,而其他节点输出都比较小。说明网络的识别结果是正确的。
恭喜!我们成功达成了目的。训练神经网络,然后让网络告诉我们图片的内容是什么。这不过是训练数据集的一部分,仅仅使用了100个数据进行训练,这使得识别正确率不会很高。而完整的训练集有60000条记录。所以让我们继续编写代码,来看看神经网络到底有多大的能耐!
测试正确率的函数
下面让我们来写一个可以输出网络测试的正确率的函数
def test_acc(test_data_list):rightnum = 0allnum = 0for record in test_data_list:allnum+=1.0all_values = record.split(',')correct_label = int(all_values[0])inputs = (numpy.asfarray(all_values[1:])/255*0.99)+0.01outputs = n.query(inputs)label = numpy.argmax(outputs)if(label == correct_label):rightnum+=1.0passacc = rightnum/allnumprint("Accuracy:",acc)
经过调用函数测试,得到的正确率如下图所示。
可见正确率不是很高,接下来我们会使用完整的数据进行训练,然后测试一下正确率。看看效果如何吧!
五、完整的训练
让我们修改一下文件路径,然后就可以对完整的文件进行训练。训练结果如下:
可见,大量的数据对精度提升有着很大的作用,这个表现令人吃惊。几乎是95%的准确率。到这里我们完成了对神经网络的简单尝试,而且取得了非常好的效果。
学习率的调整
后续我们可以继续对网络进行改进,以求得到更高精度的结果。比如我们可以调整隐含层节点,或者学习率。本次训练使用的是0.3的学习率,如果我们尝试了使用0.6的学习率,输出的精度会有所下降,达到0.90。似乎朝着不好的方向发展了。然后尝试调整到0.1,我们发现精度可以高达0.9523,这似乎让网络更好了。
显然学习率的选择对于结果有重要影响,所以选择一个合适的学习率很重要。
下面的图表展示了学习率与正确率的一个曲线关系图。
训练轮数epoch
训练论数是什么意思呢?刚刚开始我也很难明白,觉得数据训练完了就可以了。但是后来才了解到,完整训练完一次成为一个epoch,而多个epoch也会对网络的训练效果有一定影响。
为什么要训练多次呢,其实是为了通过更多调整权值的机会,有利于梯度下降的权值更新。
我们对上面的代码进行改进一下看看。
for e in range(epochs):for record in training_data_list:all_values = record.split(',')# 输入数据预处理inputs = (numpy.asfarray(all_values[1:])/255*0.99)+0.01# 目标数据预处理targets = numpy.zeros(output_nodes)+0.01targets[int(all_values[0])]=0.99n.train(inputs, targets)passpass
训练结果如下:
由于训练次数提升,电脑运算花费的时间也增加了,我的电脑运行了大概2分钟才得到结果。
上图可见从之前的0.9414提高到了0.951。虽然提升不是很高,但也是巨大进步。从这里我们证实了epoch数量确实会对效果造成一定影响。但是epochs数量是不是越大越好呢。我们来用一个图片展示一下epoch大小对网络精度的影响。
改变网络结构
由于网络结构的输入节点和输出节点已经固定了,因此我们只能通过改变隐含层的节点数来观察结构对网络训练效果的影响。
我们来试图改变一下隐含层节点数,将其从100变道200,然后看看效果怎么样。
效果不是很明显,但是重在有一定提升。下面展示了隐含层节点与网络训练效果之间的关系曲线。
附录代码
import numpyimport scipy.specialimport matplotlib.pyplot%matplotlib inlineclass neuralNetwork():# 初始化函数def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):self.innodes = inputnodesself.hinodes = hiddennodesself.outnodes = outputnodesself.lr = learningrate # 链接权重# 三个参数:正态分布中心,标准方差,numpy数组大小self.winhi = numpy.random.normal(0.0, pow(self.hinodes, -0.5), (self.hinodes, self.innodes))# 其中0.0是正态分布的中心点,pow(self.hinodes, -0.5)就是对这个隐含层节点数进行开平方。# 最后一个参数就是我们希望得到的numpy数组的大小。self.whiout = numpy.random.normal(0.0, pow(self.outnodes, -0.5),(self.outnodes, self.hinodes))# 激活函数self.activation_function = lambda x: scipy.special.expit(x)passdef train(self, inputs_list, targets_list):# 把输入列表转化为numpy型的2维inputs = numpy.array(inputs_list,ndmin=2).Ttargets = numpy.array(targets_list,ndmin=2).T# 计算隐含层输出hidden_inputs = numpy.dot(self.winhi, inputs)hidden_outputs = self.activation_function(hidden_inputs)# 计算输出层输出final_inputs = numpy.dot(self.whiout, hidden_outputs)final_outputs = self.activation_function(final_inputs)# 计算输出层的误差output_errors = targets - final_outputshidden_errors = numpy.dot(self.whiout.T, output_errors)# 更新链接权重self.whiout +=self.lr*numpy.dot((output_errors*final_outputs*(1-final_outputs)), numpy.transpose(hidden_outputs))# final_outputs已经经过了sigmoid运算,所以可以直接调用。# 对输入层和隐含层之间的权重进行更新self.winhi +=self.lr*numpy.dot((hidden_errors*hidden_outputs*(1.0-hidden_outputs)), numpy.transpose(inputs))# 输入的是一个数列例如[1,2,3,4]def query(self, inputs_list):inputs = numpy.array(inputs_list,ndmin=2).Thidden_inputs = numpy.dot(self.winhi, inputs)# 经过隐含层的激活函数hidden_outputs = self.activation_function(hidden_inputs)# 信号从隐含层传递到输出层final_outputs = numpy.dot(self.whiout, hidden_outputs)# 信号经过输出层的激活函数final_outputs = self.activation_function(final_outputs)return final_outputs# 测试网络效果的函数def test_acc(test_data_list):rightnum = 0allnum = 0for record in test_data_list:allnum+=1.0all_values = record.split(',')correct_label = int(all_values[0])inputs = (numpy.asfarray(all_values[1:])/255*0.99)+0.01outputs = n.query(inputs)label = numpy.argmax(outputs)if(label == correct_label):rightnum+=1.0passacc = rightnum/allnumprint("Accuracy:",acc)# 输入节点,隐含层节点,输出节点,学习率,训练轮数input_nodes = 784hidden_nodes = 200output_nodes = 10learning_rate = 0.3epochs = 3# 创建一个神经网络对象n = neuralNetwork(input_nodes, hidden_nodes, output_nodes,learning_rate)# 读取训练数据training_data_file = open("C:/Users/15619/Downloads/mnist_train.csv", 'r')training_data_list = training_data_file.readlines()training_data_file.close()# 训练神经网络for e in range(epochs):for record in training_data_list:all_values = record.split(',')# 输入数据预处理inputs = (numpy.asfarray(all_values[1:])/255*0.99)+0.01# 目标数据预处理targets = numpy.zeros(output_nodes)+0.01targets[int(all_values[0])]=0.99n.train(inputs, targets)passpass# 读取测试数据test_data_file = open("C:/Users/15619/Downloads/mnist_test.csv", 'r')test_data_list = test_data_file.readlines()test_data_file.close()# 调用函数test_acc(test_data_list)
如果觉得《Python神经网络识别手写数字-MNIST数据集》对你有帮助,请点赞、收藏,并留下你的观点哦!