提出问题:手写数字的识别(这是学习神经网络的一个最最方便收集数据的分类问题了)
制作训练所需的数据集:
先来看看别人制作好的数据集,这是一个样本总量为100的数据集,图中文件用csv保存,然后最左边框住的是代表的真实数字,然后其所在的行的其他数字就是照片的像素值(结合第二张图看)
什么?你还不理解?好的,那我们看图说话!这是一张手写的0的图片截图,看到这些颜色介于0-255之间的像素点之后你是否恍然大悟呢?
再具体一点说,实际的图片如图所示
制作测试所需的测试集:
那么如何用一个数字图片得到一个像素颜色数组呢?
import imageio
import glob
import numpy
import matplotlib.pyplot
%matplotlib inline
our_own_dataset = []
for image_file_name in glob.glob('my_own_images/2828_my_own_2.png'):
label = int(image_file_name[-5:-4])
# load image data from png files into an array
img_array = imageio.imread(image_file_name, as_gray=True)
# 将28*28的二维数组转变成784*1的一维数组,然后再用255去减(数字小了方便计算)
img_data = 255.0 - img_array.reshape(784)
# 将数字进行归一化处理,即修改为0.01到1.0之间的小数)
img_data = (img_data / 255.0 * 0.99) + 0.01
# 将这个数组对应的手写字符标签加入到数组中
record = numpy.append(label,img_data)
our_own_dataset.append(record)
pass # 在循环中,使用pass语句句来跳过该次循环,在语句中只起到占位作用
matplotlib.pyplot.imshow(our_own_dataset[3][1:].reshape(28,28), cmap='Greys', interpolation='None')
print(our_own_dataset[0])
有时我们的精力有限不可能收集到足够特殊的图片,那么我们应该如何保证资料的普适性?
答:我们可以对已有的图片组图形变换处理,如旋转
import numpy
import matplotlib.pyplot
%matplotlib inline
import scipy.ndimage
data_file = open("mnist_dataset/mnist_train_100.csv", 'r')
data_list = data_file.readlines()
data_file.close()
record = 6
all_values = data_list[record].split(',') # 读取第7组数据并按照逗号分隔放入数组
scaled_input = ((numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01).reshape(28,28) # 将数据归一化后分成28*28的二维数组
matplotlib.pyplot.imshow(scaled_input, cmap='Greys', interpolation='None') # 打印数字
# rotated anticlockwise by 10 degrees(逆时针旋转10度)
inputs_plus10_img = scipy.ndimage.rotate(scaled_input, 10.0, cval=0.01, order=1, reshape=False)
# rotated clockwise by 10 degrees(顺时针旋转10度)
inputs_minus10_img = scipy.ndimage.rotate(scaled_input, -10.0, cval=0.01, order=1, reshape=False)
matplotlib.pyplot.imshow(inputs_plus10_img, cmap='Greys', interpolation='None')
matplotlib.pyplot.imshow(inputs_minus10_img, cmap='Greys', interpolation='None')
左一:原图像,左二:逆时针旋转10度,左三:顺时针旋转10度
接下来我们来讲如何训练神经网络
第一步:定义神经网络(代码如下)
numpy.random.normal(loc,scale,size)
功能:产生满足正态分布的样本数据
loc:均值(期望)
scale:标准差
size:输出值的维度,如输入size=(m,n,k),则输出m*n*k个样本
lambda x: scipy.special.expit(x)
功能:又称sigmoid激活函数,用于将输入的x压缩到0到1之间
数学表示:expit(x)= 1 /(1 + exp(-x))
等价于:
def activation_function(self, x): return scipy.special.expit(x)
numpy.array(object,ndmin)
功能:创建一个数组
object:数组
ndmin:指定结果数组应具有的最小维数
numpy.dot(a,b)
功能:返回两个数组的点积(数量积)
a:数组a
b:数组b
hidden_errors = numpy.dot(self.who.T, output_errors)
功能: # 求隐层误差序列
解析:例如输出层有俩个节点,隐层两层节点时
第一个式子是按照权重占比来求出隐层误差
第二个式子是忽略掉分母近似地求隐层误差
第三个式子是最终表达式推广
numpy.transpose(a)
功能:矩阵转置
a:矩阵
self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs))
误差函数E=目标值和实际值之差的平方和,求偏导可知道其
节点k的输出Ok,只取决于权重Wj,k,所以可以化简为
Ok:即输出层的输出值,等价于sigmoid(隐层输出值*输出权值之和)
(隐层输出值*输出权值之和)对权值求导后为对应隐层输出值Oj
系数2可以去掉,因为我们只对误差的斜率方向感兴趣,这样就能使用梯度下降的方法
误差函数E对权值的导数=(误差Ei*Si*(1-Si))与(隐层输出值的转置)的数量积
使用梯度下降更新权值,斜率为正时,为了取得最小值应减小原权值;斜率为负时,为了取得最小值应增大原权值,α是学习率可以调节变化强度,确保不会超调
class neuralNetwork:
def __init__(self, inputnodes, hiddennodes, outputnodes, learningrate):
# 输入曾节点数
self.inodes = inputnodes
# 隐层节点数
self.hnodes = hiddennodes
# 输出层节点数
self.onodes = outputnodes
# 使用正太分布N1(0,28)输出200*784个样本,用于初始化输入权值
self.wih = numpy.random.normal(0.0, pow(self.inodes, -0.5), (self.hnodes, self.inodes))
# 使用正太分布N2(0,14.14)输出200*10个样本,用于初始化输出权值
self.who = numpy.random.normal(0.0, pow(self.hnodes, -0.5), (self.onodes, self.hnodes))
# 初始化学习率
self.lr = learningrate
# 定义激活函数
self.activation_function = lambda x: scipy.special.expit(x)
pass
# train the neural network
def train(self, inputs_list, targets_list):
# 将list转为2维数组并转置
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)
# 更新输入权值,这里是“+”所以直接用了output_errors
self.who += self.lr * numpy.dot((output_errors * final_outputs * (1.0 - final_outputs)), numpy.transpose(hidden_outputs))
# 更新输出权值,这里是“+”所以直接用了hidden_errors
self.wih += self.lr * numpy.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)), numpy.transpose(inputs))
pass
# query the neural network
def query(self, inputs_list):
# 将list转为2维数组并转置
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
第二步:训练神经网络,因为是28*28的输入,所以令输入层节点数为784,隐层节点数为200,输出层节点数为10,学习率为0.1
numpy.asfarray(arr)
功能:转换为浮点型数组
arr:输入数组
[1:]
功能:获取从位置1开始后面的字符(默认首位是0)
numpy.zeros(shape)
功能:返回指定形状的零矩阵(即矩阵内元素全为0)
shape:数据尺寸,可以为5、(2,3)、(2,3,5)等,其中(2,3)代表2行3列二维矩阵
input_nodes = 784
hidden_nodes = 200
output_nodes = 10
learning_rate = 0.1
# 创建神经网络
n = neuralNetwork(input_nodes,hidden_nodes,output_nodes, learning_rate)
# 获取训练样本数据
training_data_file = open("mnist_dataset/mnist_train_100.csv", 'r')
training_data_list = training_data_file.readlines()
training_data_file.close()
# 执行训练操作,总共有100*5次数据传入神经网络进行训练
# 设置训练次数
epochs = 5
for e in range(epochs):
for record in training_data_list:
# 将字符串分割为数组
all_values = record.split(',')
# 获取输入序列,将数组限制到[0.01,1]的范围内(不让有0的出现,因为0与任何数相乘都为0,会导致权值无论如何都不会变化)
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
# 初始化真实数据,因为all_values的0位置数据即为预测的真实值,所以令对应的位置值0.99,如all_values[0]=4,则targets=[0.0.1,0.01,0.01,0.01,0.99,0.01,...,0.01]
targets = numpy.zeros(output_nodes) + 0.01
targets[int(all_values[0])] = 0.99
# 执行训练过程
n.train(inputs, targets)
pass
pass
第三步:测试神经网络训练好坏,若正确率高则可投入使用,否则需要继续训练(或提高训练轮次)
numpy.argmax(a)
功能:返回数组中最大值的索引
a:数组
scorecard = []
# go through all the records in the test data set
for record in test_data_list:
# 将字符串分割为数组
all_values = record.split(',')
# 得到真实结果
correct_label = int(all_values[0])
# 获取输入序列,将数组限制到[0.01,1]的范围内
inputs = (numpy.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
# 放入输入序列到神经网络,查询神经网络输出结果
outputs = n.query(inputs)
# 得到神经网络预测的数值
label = numpy.argmax(outputs)
# 判断预测值与实际值是否吻合
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)
训练轮次为5时
训练伦次为10时,准确率提高了
第四步:使用神经网络判断图片所属的数字
import imageio
import glob
our_own_dataset = []
# 获取文件夹下图片
for image_file_name in glob.glob('my_own_images/2828_my_own_?.png'):
# 获取图片对应的真实数字标签
label = int(image_file_name[-5:-4])
# 将图片转换成数组
img_array = imageio.imread(image_file_name, as_gray=True)
# 将28*28的数组转换成1*784的数组,然后255减去各元素值
img_data = 255.0 - img_array.reshape(784)
# 获取输入序列,将数组限制到[0.01,1]的范围内
img_data = (img_data / 255.0 * 0.99) + 0.01
# 拼接数据
record = numpy.append(label,img_data)
# 将数据放入数组
our_own_dataset.append(record)
pass
# 第几个图片(从0开始)
item = 2
# 获取正确的标签
correct_label = our_own_dataset[item][0]
# 获取输入序列
inputs = our_own_dataset[item][1:]
# 查询
outputs = n.query(inputs)
print (outputs)
# 获取预测数字
label = numpy.argmax(outputs)
print("network says ", label)
# 判断是否匹配成功
if (label == correct_label):
print ("match!")
else:
print ("no match!")
pass
当item=2时,匹配成功
当item=3时,匹配失败
https://www.cnblogs.com/sdu20112013/p/11561278.html
帮助理解反向传播:我们得到一个损失值:Loss=1/2*(target1-out1)+1/2*(target2-out2),Loss中变量为out1和out2,我们想让损失值变小就需要对俩者分别求偏导找到其递减区间,我们以out1为例:
对out1求偏导后为:
进一步发现,out1的改变受到neto1的影响,所以我们再对neto1求导
进一步发现,neto1的改变收到w5,outh1,w6,outh2,b2的影响,我们现在只关心w5的影响,所以对w5求偏导
最总我们得到了这样链式求导的式子,即损失值关于权值的偏导 = 链式求导的结果
上述式子也可以更具体地写成
为了简化我们有
最终我们更新权值地表达式表示为