终于要开始实战阶段了,这里我们要实现的是一个手写数字的识别,这个可以应用于邮局的邮编号码识别。要实现神经网络学习,我们先需要一个手写数字图片的数据集,这个数据集我们用的是美国国家标准与技术研究所的子数据集—mnist。以下是取自该数据集的一些图像:
这个数据集总共有60000个数据,也就是有60000张图片,每张图片的像素是28*28。每个像素点代表一个灰度值。今天先把主要程序讲一遍,明天再试着运行程序。话不多说,开讲:
1)定义类
# -*- coding: utf-8 -*-
#!/usr/bin/env python3
import random
import numpy as np
class Network(object):
def __init__(self,sizes):
self.num_layers=len(sizes)
self.sizes=sizes
self.biases=[np.random.randn(y,1) for y in sizes[1:]]
'''创建一个偏差向量'''
self.weights=[np.random.randn(y,x) for x,y in zip(sizes[:-1],sizes[1:])]
'''创建一个权重偏差矩阵''
这里我们首先引入两个函数模块random和numpy,前者是用于生成随机数,后者是用来矩阵运算的模块。
然后,定义一个Network class。初始化属性,该神经网络的层数;神经网络的大小;初始化偏差向量;初始化权重矩阵。
这里的难点是初始化两个参数,这里你会看到zip函数两个for循环,我们先讲zip函数。这个函数是将若干列表中的元素按位置组成元组的列表,然后输出。示例如下:
>>> a = [1,2,3] >>> b = [4,5,6] >>> c = [4,5,6,7,8] >>> zipped = zip(a,b) # 打包为元组的列表 [(1, 4), (2, 5), (3, 6)] >>> zip(a,c) # 元素个数与最短的列表一致 [(1, 4), (2, 5), (3, 6)] >>> zip(*zipped) # 与 zip 相反,可理解为解压,返回二维矩阵式 [(1, 2, 3), (4, 5, 6)]
这里的sizes是[784,30,10],于是变成了zip([784,30],[30,10])。接下来是randn(x1,x2,....)函数,这个函数的意思是生成x1*x2*....数组。这样我们就生成了30*784和10*30两个矩阵。
2)前馈算法(feedforward)
def feedforward(self,a): for b,w in zip(self.biases,self.weights): a=sigmoid(np.dot(w,a)+b) return a
这里通过for循环将weight和biases从矩阵中取出来,然后再赋给dot点乘求和,再用sigmoid函数激励。
3)随机梯度下降算法(SGD)
def SGD(self,training_data,epochs,mini_batch_size,eta,test_data=None): if test_data:n_test=len(test_data) '''测试集大小''' n=len(training_data) '''训练集大小''' for j in range(epochs): '''循环次数''' random.shuffle(training_data) '''打乱数据''' mini_batches=[training_data[k:k+mini_batch_size] for k in range(0, n, mini_batch_size)] for mini_batch in mini_batches: self.update_minibatch(mini_batch,eta) '''将训练数据全都分成小的小的数据集当作列表更新,还有学习率''' if test_data: print("Epoch{0}:{1}/{2}".format(j,self.evaluate(test_data),n_test)) '''这里通过索引来格式化字符串''' else: print("Epoch{0} complete".format(j)) '''数据已经检索完毕'''
这里唯一要注意的就是,这里我们用切片将数据分成了小的数据集。这里第二个for循环里面,取小数据集大小为步长,用于切分数据集。
4)更新小的数据集
def update_minibatch(self,mini_batch,eta): nabla_b=[np.zeros(b.shape) for b in self.biases] nabla_w=[np.zeros(w.shape) for w in self.weights] '''初始化生成相对应的偏导数矩阵''' for x,y in mini_batch: delta_nabla_b,delta_nabla_w=self.backprop(x,y) '''更新偏导数矩阵''' nabla_b=[nb+dnb for nb,dnb in zip(nabla_b,delta_nabla_b)] nabla_w=[nw+dnw for nw,dnw in zip(nabla_w,delta_nabla_w)] '''求偏导数和''' self.weights=[w-(eta/len(mini_batch))*nw for w,nw in zip(self.weights,nabla_w)] self.biases=[b-(eta/len(mini_batch))*nb for b,nb in zip(self.biases,nabla_b)] '''公式20和21'''
这里,我们先用zeros初始化代价函数关于weight和baises的偏导数矩阵,接下来用BP算法更新,接下来再计算偏导数和,最后根据下图公式来更新得到weights和biases:
5)BP算法更新梯度
def backprop(self,x,y): nabla_b=[np.zeros(b.shape) for b in self.biases] nabla_w=[np.zeros(w.shape) for w in self.weights] '''初始化偏置和权重矩阵''' activation=x activations=[x] zs=[]
'''第一步,初始化每层的激励值''' for b,w in zip(self.biases,self.weights): z=np.dot(w,activation)+b '''点乘''' zs.append(z) activation=sigmoid(z) activations.append(activation) '''第二步,正向更新(feedforward)激励值''' delta=self.cost_derivative(activations[-1],y)
'''第三步,求误差error''' sigmoid_prime(zs[-1]) nabla_b[-1]=delta nabla_w[-1]=np.dot(delta,activations[-2].transpose()) '''第五步,先求出最后一层的w和b,transpose矩阵转置''' for l in range(2,self.num_layers):
'''第四步,从倒数第二层开始更新''' z=zs[-l] sp=sigmoid_prime(z) delta = np.dot(self.weights[-l+1].transpose(),delta)*sp
'''从后一层开始更新,计算delta(error)''' nabla_b[-l]=delta nabla_w[-l]=np.dot(delta,activations[-l-1].transpose())
'''第五步,更新偏导数''' return (nabla_b,nabla_w)
这里是BP算法最重要的地方。前面简单的注释都可以看懂,最后面的一段结合以下公式:
6)计算准确率和代价函数求偏导
def evaluate(self,test_data): test_results=[(np.argmax(self.feedforward(x)),y)
for (x,y) in test_data]
'''输出测试集的神经网络输出值和期望值''' return sum(int(x==y)for (x,y) in test_results) '''返回结果中的正确数总个数(布尔值和)'''
def cost_derivative(self,output_activations,y): return (output_activations-y)
'''代价函数求偏导'''
这里需要讲的是函数numpy.argmax(),这个函数是用来输出列表中最大值的索引,如下:
最大值是8,所以其索引是4,a[4]=8。
7)其他
def sigmoid(z): return 1.0/(1.0+np.exp(-z)) def sigmoid_prime(z): return sigmoid(z)*(1-sigmoid(z))
这两个就很简单了,第一个是sigmoid 函数,第二个是sigmoid函数的导函数。
希望有志同道合的小伙伴关注我的公众平台,欢迎您的批评指正,共同交流进步。