参考书籍:
Python神经网络编程.pdf(强烈推荐小白阅读)
链接:https://pan.baidu.com/s/1tfNnemef1XzpzrDrslaaxA
提取码:sten
这本书整本书使用一个识别数字的例子来手把手教你写一个最简单的2层神经网络程序,页数也不多,两个晚上学习的时间就可以读完,跟着书一步步把程序一点点敲出来或者复制粘贴过来,然后再把每一行程序都看懂,结束之后你就会对神经网络的工作原理及流程有了一个很清楚的认识了。
代码中有我自己写的注释,仅仅是我个人的理解,虽然还有一点点不是很懂,但是不影响对神经网络工作原理的理解。
程序中需要的测试集下载地址:
链接:https://pan.baidu.com/s/1NTe99USuzUIbj43V4gG3Xw
提取码:zdjq
需要注意的是 第一个数据集里面有60000个样本,由于数据量有点大,电脑不太行的同学请谨慎使用这个大数据集,不过一般也不会花费太长的时间,我的新台式机也就才花了十几秒,一般的笔记本只要不是年数过多或者卡的不行的,最多也就运行l几分钟的样子吧,我的差不多新的笔记本运行了两分半,也都能接受。
第二个数据集有10个样本
第三个数据集有100个样本,这都不会花费太长的时间
书中有几个公式比较重要:
仔细理解这几个公式的含义,然后反向传播就知道是咋回事了,反向传播的程序就是按照下面的公式来写的
神经网络工作流程:
第一步:初始化各层的权重系数和设置激活函数
初始化的方法有很多种,你可以随意初始化,也可以按照一定的数学分布去初始化
激活函数也有好多种,选择适合你的就行
第二步:对神经网络进行训练
import numpy
import matplotlib.pyplot as plt
import scipy.special
#下面是2层神经网络模型
class neuralNetwork:
def __init__(self,inputnodes,hiddennodes,outputnodes,learningrate):
#下面所定义的inodes、hnodes、onodes和lr就是只在这个neuralNetwork类的里面使用,就相当于内部参数,使用的时候直接使用self来进行调用
self.inodes = inputnodes
self.hnodes = hiddennodes
self.onodes = outputnodes
self.lr = learningrate
# rand()生成[0,1)的数据,另外要注意W矩阵的行和列数,这里的wih和who直接就是我们之前所说的w的转置,
# 即这里的wih和who是不需要再进行任何的转置操作了,他们可以直接和输入x相乘
# self.wih = numpy.random.rand(self.hnodes,self.inodes) - 0.5
# self.who = numpy.random.rand(self.onodes,self.hnodes) - 0.5
#下面是更为复杂的初始参数的设置
'''
我们将正态分布的中心设定为0.0。 与下一层中节点相关的标准方差的表达式, 按照Python的形式, 就是pow(self.hnodes, -0.5),
简单说来, 这个表达式就是表示节点数目的-0.5次方。 最后一个参数, 就是我们希望的numpy数组的形状大小。
'''
#下面定义的wih和who也是只在当前这个类中使用,相当于就是一个内部参数,使用的时候就直接使用self来进行调用
# 初始化各层的权重系数
self.wih = numpy.random.normal(0.0, pow(self.hnodes,-0.5), (self.hnodes, self.inodes))#使用正态概率分布采样权重, 其中平均值为0, 标准方差为节点传入链接数目的开方,
self.who = numpy.random.normal(0.0, pow(self.onodes,-0.5), (self.onodes, self.hnodes))
self.activation_function = lambda x: scipy.special.expit(x)#定义激活函数
pass
def train(self,inputs_list,targets_list):
inputs = numpy.array(inputs_list, ndmin=2).T#把输入的数据变成列向量
targets = numpy.array(targets_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)#这是输出层到隐藏层之间的误差反向传播
#下面是利用误差的反向传播来更新各层之间的权重参数
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))#这是更新输入层和隐藏层之间的权重参数
pass
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
pass
#下面input_nodes、hidden_nodes、output_nodes和learning_rate就相当于我们在主函数中定义的变量一样,只在类之外的主函数中使用
#设置各层的神经元个数以及学习率,注意这里是设置各层的神经元的个数,而不是各层的层数,我们这个程序只是一个最简单的2层神经网络模型
input_nodes = 784#输入层有784个神经元,也就是我们共有784个数据需要输入给神经网络
hidden_nodes = 100#隐藏层有100个神经元,注意这里是隐藏层节点的个数,而不是隐藏层的层数,我们这里隐藏层就只有一层,也就是2层神经网络模型。增大隐藏层节点个数,代码运行时间明显加长
output_nodes = 10#输出层有10个神经元,每一个神经元的输出最大值都对应着当前神经网络所预测到的数字
learning_rate = 0.3 #增大学习率,代码运行的总时间好像没有减少啊,但是正确率的确下降了
n = neuralNetwork(input_nodes,hidden_nodes,output_nodes,learning_rate)
#注意下面打开的是训练集,这里面只有100个训练样本
echo = 3 #当echo=1时,准确率达不到1,但是当echo等于3时,准确率基本上一直是1
for e in range(echo):#对同一个数据集进行多个世代的训练,也可以增加正确率
training_data_file = open("E:\Project\Python\mnist_train_100.csv", 'r')#打开训练数据所在文件,这个训练集中共有100个手写数字,数字是0-9,即每一个数字会有好几种不同的写法在里面
training_data_list = training_data_file.readlines()#按行读取训练数据
training_data_file.close()#关闭文件
for record in training_data_list:
# print(record)#这里面的record就是training_data_list训练集中的每一行数据,使用for循环来遍历整个训练集
all_values = record.split(',')#使用逗号 , 来进行分割,每隔一个 ,就分割出一个字符串元素,每循环一次record都会更新一次,更新成最新的训练样本,总共有100个训练样本
# asfarry表示把文本字符串转换实数,注意此时的输入是一个1*784的数组,这里并不需要把数据变成28x28的矩阵,只有画图的时候才需要把数据变成矩阵
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01 #把数据大小范围限定在0.01-1.01之间,我们要避免让0作为神经网络的输入值,所以这里要进行+0.01的操作
#为了防止0和1会导致大的权重和饱和网络,我们把最后10的输出值(取0和1两个值)变成了取0.01和0.99
targets = numpy.zeros(output_nodes) + 0.01
#print(targets)#此时的targets的大小是100*10,100的意思是指训练集中总共有100个样本,10的意思是指每一个训练样本的的输出都是1行10列的矩阵,哪一列的数最大,那么神经网络的预测数字值就是对应的列数
targets[int(all_values[0])] = 0.99 #这句话的意思是把训练集中的每一个样本的第一个数据(也就是标签值)都改成了0.99,防止出现1
n.train(inputs, targets)
pass
pass
test_data_file = open("E:\Project\Python\mnist_train_10.csv", 'r')
test_data_list = test_data_file.readlines()
test_data_file.close()
scorecard = []
for record in test_data_list:
all_values = record.split(',')
correct_label = int(all_values[0])
print(correct_label, "correct label")
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
outputs = n.query(inputs)
label = numpy.argmax(outputs)
print(label, "network's answer")
if (label == correct_label):
scorecard.append(1)
else:
scorecard.append(0)
pass
pass
scorecard_array = numpy.asarray(scorecard)
print ("performance = ", scorecard_array.sum() / scorecard_array.size)
'''
#注意下面打开的是测试集,这里面有60000条数据
test_data_file = open("E:\Project\Python\mnist_train.csv", 'r')
test_data_list = test_data_file.readlines()
test_data_file.close()
all_values = test_data_list[3].split(',')#这一行决定着我们要把数据集中的哪一行作为测试样本
#下面这行的作用是把测试样本的数据进行改造,即把他们从索引值为1开始向后取,然后把字符串换成实数,最后再把这些数据变成28x28的矩阵
#注意只有在画图的时候才需要把数据变成28x28的矩阵,如果只是作为神经网络的输入,那么就是一长串的数字(28x28=784个数字)
image_array = numpy.asfarray( all_values[1:]).reshape((28,28))#[1:]表示取出来除了第一个元素之外的所有值,也就是从索引值为1的那个元素开始取,因为第一个是标签值,暂时不需要
#print(image_array)
#测试训练好的神经网络
test_inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
n.query(test_inputs)
#plt.imshow( image_array, cmap='Greys',interpolation='None')
#plt.show()
'''
'''
#多个世代训练同一个数据集
echo = 3
for e in range(echo):
pass
'''
输出结果: