本文参考《Python神经网络编程》的相关章节

现在很火的一个机器学习数据集就是手写数字数据集(MNIST)

这个网站提供了两个CSV文件:

        训练集:http://www.pjreddie.com/media/files/mnist_train.csv

        测试集:http://www.pjreddie.com/media/files/mnist_test.csv

训练集有60000个标记样本,用于训练。测试集有10000个标记样本,用于测试

 

以下网站提供了两个较小的数据集,我们在调试程序的过程中可以使用他们:

        训练集:https://raw.githubusercontent.com/makeyourownneuralnetwork/makeyourownneuralnetwork/master/mnist_dataset/mnist_train_100.csv

        测试集:https://raw.githubusercontent.com/makeyourownneuralnetwork/makeyourownneuralnetwork/master/mnist_dataset/mnist_test_10.csv

 

打开其中一个文件,我们可以看到其格式如下:

这是一个样例的数据,第一个值是该样例的标签(这里是7),剩下的数据是手写数字图片各个像素点的颜色值,所以每个值的取值范围为0~255,其个数为28*28共784个

我们用灰度变化来显示该手写数字,Python代码如下:

import numpy as np
from matplotlib import pyplot as plt
 
data_file = open('mnist_test_10.csv', 'r')
data_list = data_file.readlines()
data_file.close()
all_values = data_list[0].split(',')
image_array = np.array(all_values[1:]).astype(np.int).reshape((28, 28))
plt.imshow(image_array, cmap = 'Greys', interpolation = 'None')
plt.show()

没有问题, 我们可以看到一个由灰度表示出来的手写数字7。

手写数字的识别中一个很著名的一个方法K近邻(KNN)算法,十分简单,并且识别效果也是很好的,但是我们今天不使用这种方法,我们使用神经网络。

上一篇博文已经构建了一个很成熟的神经网络模型,我们可以直接使用。现在要解决的唯一问题就是,神经网络每一层的结点个数。

输入层,隐层:输入层和隐层结点数可以取相同值,每个像素点为一个输入,所以应该有28*28共784个

输出层:一共有10中可能的判断(0~9),我们用编码的思想设置10个结点,输出为1的那个结点的位置代表预测标签,即[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]代表预测为1


现在我们已经可以开始训练我们的神经网络了,但是有几个小细节值得注意:

一:输入的数据

我们需要调整数据到较小的范围(数据缩放),这里我就不介绍数据缩放对模型训练的影响了,感兴趣的同学请自行查阅相关文章。

我们这里将数据缩放至0.01~10的范围,选0.01作为最低点是避免0输入对权重更新无影响的情况

因为所有数据的取值范围为0~255,所以对所有数据除以255,变为0~1的输入,在乘0.99,变为0~0.99的输入,最后加上0.01,变为0.01~1.00的输入

 

二:权重的初始化

使用下一层的结点数的开方作为标准方差来初始化权重

 

三:步长

步长的选择,理论上是越小越好的,但是越小的步长需要的训练次数会越多,所以我们要选择合适的步长,要在训练次数下能达到最优,且波动不能太大。

我们这里选择0.3的步长,步长选择没有明确的方法,只有用实验来确定一个相对合适的步长。

 

 现在我们来看看Python的实现:

代码和数据集已经上传至我的资源:https://download.csdn.net/download/qq_41398808/11232343

 

文件读取:

def loaddataset(filename):
	fp = open(filename, 'r')
	dataset = []
	labelset = []
	for i in fp.readlines():
		a = i.strip().split(',')
		#对数据进行缩放
		dataset.append([(int(j) / 255.0 * 0.99 + 0.01) for j in a[1:]])
		labelset.append(int(a[0]))
	return np.array(dataset), np.array(labelset)

 修改标签(改为[0, 0, 1, 0, 0, 0, 0, 0, 0, 0]的形式):

对于激活函数来说,生成0,1的输出是不可能的,会导致出现大的权重和饱和网络,所以我们使用0.01代替0,0.99代替1

def adjustment_label(labelset):
	new_labelset = []
	for i in labelset:
		new_label = [0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01, 0.01]
		new_label[i] = 0.99
		new_labelset.append(new_label)
	return new_labelset

 激活函数sigmoid使用scipy库中的expit(x)函数,网传其运行速度会更快,毕竟60000个样例,我的电脑训练一次至少要25分钟。

import scipy.special as spc
def sigmoid(z):
	return spc.expit(z)

 参数初始化:

def parameter_initialization(x, y, z):
	value1 = np.random.normal(0.0, pow(1, -0.5), (1, y))
	value2 = np.random.normal(0.0, pow(1, -0.5), (1, z))
	weight1 = np.random.normal(0.0, pow(x, -0.5), (x, y))
	weight2 = np.random.normal(0.0, pow(y, -0.5), (y, z))
	return weight1, weight2, value1, value2

开始训练(训练过程不需要任何的修改):

def trainning(dataset, labelset, weight1, weight2, value1, value2, x):
	for i in range(len(dataset)):
		print(i)
		inputset = np.mat(dataset[i])
		outputset = np.mat(labelset[i])
		input1 = np.dot(inputset, weight1)
		output2 = sigmoid(input1 - value1)
		input2 = np.dot(output2, weight2)
		output3 = sigmoid(input2 - value2)
 
		a = np.multiply(output3, 1 - output3)
		g = np.multiply(a, outputset - output3)
		b = np.dot(g, np.transpose(weight2))
		c = np.multiply(output2, 1 - output2)
		e = np.multiply(b, c)
 
		value1_change = -x * e
		value2_change = -x * g
		weight1_change = x * np.dot(np.transpose(inputset), e)
		weight2_change = x * np.dot(np.transpose(output2), g)
 
		value1 += value1_change
		value2 += value2_change
		weight1 += weight1_change
		weight2 += weight2_change
	return weight1, weight2, value1, value2

测试过程:

因为可能出现10个输出中没有大于0.5或多个大于0.5的情况,所以我们寻找10个输出中最大的一个的位置,作为预测标签

def testing(dataset, labelset, weight1, weight2, value1, value2):
	rightcount = 0
	for i in range(len(dataset)):
		inputset = np.mat(dataset[i])
		outputset = np.mat(labelset[i])
		input1 = np.dot(inputset, weight1)
		output2 = sigmoid(input1 - value1)
		input2 = np.dot(output2, weight2)
		output3 = sigmoid(input2 - value2)
 
		label = np.argmax(output3)
		if int(label) == int(labelset[i]):
			rightcount += 1
 
		print("真实值为%d, 预测值为%d"%(labelset[i], label))
	print("正确率为%f"%(rightcount / len(dataset)))

np.argmax()返回行向量中最大值的位置

 

 我们可以将训练好的参数用文件的形式保存,之后可以直接使用,不需要再次训练,毕竟训练一次要好久呢。我的训练好的参数文件也上传至我的资源。

最后我们在测试集上的预测准确率为:

正确率为0.953000
[Finished in 1005.7s]

结果是相当不错的。 我们可以适当的调小步长,增大训练次数来提高准确率,我就不去实现了(训练过程实在太长)。