一.运行环境配置
本次实验的运行环境win10(bit64),采用python环境为3.7.6,安装Python环境推荐使用Anaconda。Anaconda是一个免费开源的Python和R语言的发行版本,用于计算科学(数据科学、机器学习、大数据处理和预测分析)。在python编程中下载相关库是很浪费时间的,Anaconda有效地帮我们解决了这个问题。
我用的编译器是pycharm,建议基于虚拟环境进行编程,pycharm提供了很便利的方式搭建虚拟环境:
这里注意要在inhrerit global site-packages打勾,否则不会继承Anaconda中的数据包。你也可以通过virtualenv在cmd中配置,这里不作演示。
二.准备手写数字的数据集
数据集包括训练集和测试集
也可以在后面我的github上进行下载。
数据集中用数百个像素点(0-255)表示单个数字,第一个数字为数字设定值,后面我们将对其进行切片操作。
三.用python构建神经网络
1.我们先对函数进行初始化,设置输入层节点,输出层节点,学习率和隐层节点。然后再设置连接权重。我们要采用随机矩阵,而不是单个数字,因此采用分布中心值、标准方差和numpy数组的大小作为参数。代码如图:
self.wih = numpy.random.normal(0.0,pow(self.hnodes,0.5),(self.hnodes,self.inodes))
self.who = numpy.random.normal(0.0,pow(self.onodes,0.5),(self.onodes,self.hnodes))
第一个参数为正态分布的中心设定,第二个表示节点数目的0.5次方,第三个参数是我们希望的numpy数组大小。
为了方面后面获得隐藏层节点的输出信息,我们要定义一个激活函数,这里我们不用传统的def()函数,采用lambda快速创建。
self.activation_function = lambda x:scipy.special.expit(x)
这里调用scipy.special函数库,该条命令的含义是输入x,输出expit函数。
2.下面来写查询网络部分的代码:
我们先来做由输入层到隐层这部分,神经元输入层和隐层的节点是相当多的,如果我们逐个节点进行计算,其运算过程是不可想象的,而引入矩阵则可以大大方便我们。隐层的信号即为权重矩阵点乘输入矩阵,隐层的输出则由前面的激活函数获得,从隐层到输出层的过程与此类似,这里一并贴出代码:
def query(self, inputs_list):
inputs = numpy.array(inputs_list,ndmin=2).T
hidden_inputs = numpy.dot(self.wih,inputs)
hidden_outputs = self.activation_function(hidden_inputs)
final_inputs = numpy.dot(self.who,hidden_outputs)
final_outputs = self.activation_function(final_inputs)
return final_outputs
3. 我们开始编程最复杂的训练部分,首先是针对给定的训练样本计算输出,这与我们刚刚在query函数中所写的差不多。
inputs = numpy.array(inputs_list, ndmin=2).T
targets = numpy.array(target_list, ndmin=2).T
hidden_inputs = numpy.dot(self.wih, inputs)
hidden_outputs = self.activation_function(hidden_inputs)
final_inputs = numpy.dot(self.who, hidden_outputs)
final_outputs = self.activation_function(final_inputs)
output_errors = targets - final_outputs
hidden_errors = numpy.dot(self.who.T,output_errors)
这里补充一下神经网络更新节点j与下一层节点k之间链接权重的矩阵形式的表达式:
在python中对输入层和隐藏层,隐藏层和输出层进行相关编码:
self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0-final_outputs)),numpy.transpose(hidden_outputs))
self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0-hidden_outputs)),numpy.transpose(inputs))
四.识别手写数字
1. 初始化神经网络
input_nodes = 784
hidden_nodes = 100
output_nodes = 10
learning_rate = 0.3
n = neuralNetwork(input_nodes,hidden_nodes,output_nodes,learning_rate)
数据集中每个数组用一个28X28的数组表示,故输入节点为784个。
输出为个位数字,故输出节点为10个。
隐层节点数量可自由设置,数量多少会对识别率产生影响,这里我们设为100.
2.导入训练集,这里为提高识别率,我们将训练集循环两次,并对各像素点进行初始化,使其范围编程0.01-0.09,目标输出数组设定值设为1,其他值为0。
data_file = open("mnist_dateset/mnist_train.csv",'r')
data_list = data_file.readlines()
data_file.close()
epochs = 2
for i in range(epochs):
for record in data_list:
all_values = record.split(',')
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
targets = numpy.zeros(output_nodes) + 0.01
targets[int(all_values[0])] = 0.99
n.train(inputs, targets)
2. 读取测试集,验证我们识别的准确率。
数据初始化部分与导入训练集大致相同,随后进行一些绘图的相关设置,同时计算识别成功的次数和总数字数,相除得出识别率。
test_data_file = open("mnist_dateset/mnist_test.csv",'r')
test_data_list = test_data_file.readlines()
test_data_file.close()
# print(data_list[0])
scorecard = []
for record in test_data_list:
all_values = record.split(',')
correct_value = int(all_values[0])
print(all_values[0])
image_array = numpy.asfarray(all_values [1:]).reshape((28,28))
matplotlib.pyplot.imshow( image_array , cmap='Greys' , interpolation='None')
inputs = (numpy.asfarray(all_values[1:])/255.0 * 0.99) + 0.01
outputs = n.query(inputs)
label = numpy.argmax(outputs)
print(label)
if (label == correct_value):
scorecard.append(1)
else:
scorecard.append(0)
scorecard_array = numpy.asarray(scorecard)
五.一些优化
你可以通过更改学习率,隐层节点数量来提高识别率,也可以加入一些较为复杂的算法。该程序中我们使用了sigmoid函数,虽然很美,但由于梯度消失问题而逐渐被淘汰,图像如下
本来以为激活函数的改变会对识别率有很大影响,但尝试了Relu和tanh激活函数后发现其对最终结果几乎没有影响。可能改变对神经网络架构影响大的因素才会更有效地提高识别率。以上代码的识别率约为80,将隐层节点改为200,循环训练集5次(电脑运行了大约十分钟)后识别率变为97.25,代码已经在github上更新。
最后附上源代码链接(内含数据集):