基于python的手写数字识别底层实现
(1)加深对多层感知神经网络基本原理的理解;
(2)掌握多层感知神经网络中反向传播算法数学推导和随机梯度下降法进行训练的原理;
(3)熟练使用python语言,学会利用python从底层实现多层感知机;
(4)分析影响多层感知机准确率的因素;
(5)结合实际问题与理论相结合,为进一步学习人工智能相关课程和从事相关领域的理论研究工作奠定基础。
一、
现在我们来实际设计一个用于手写数字识别的多层感知器系统,这里利用的数据集为经典的MNIST数据集,用Python从底层实现一个多层感知机,仅用了numpy和struct模块完成数据的处理、神经网络搭建、BP算法和SGD(随机梯度下降)算法的实现。最后通过调整神经网络结构和参数(隐藏层层数、神经元数目、学习率等),并进行评估比较,以达到最优的分类器设计。
本项目主要包括四个模块:数据读入及处理、模型构建、模型训练及模型比较,下面给出各模块的功能及相关代码以及实验步骤。
二、代码
代码如下
# coding: utf-8
# 使用Python从底层实现了一个多层感知器
# 先对MNIST数据集进行读入以及处理
# 数据从MNIST官网直接下载并保存到E:\学习资料\模式识别\神经网络代码\实验代码\MNIST_data
import numpy as np
from struct import unpack
import time
start = time.clock()
#读入图片
def read_image(path):
'''
解析idx3-ubyte文件,即解析MNIST图像文件
'''
'''
也可不解压,直接打开.gz文件。path是.gz文件的路径
import gzip
with gzip.open(path, 'rb') as f:
'''
#print('loading %s' % path)
with open(path, 'rb') as f:
# 前16位为附加数据,每4位为一个整数,分别为幻数,图片数量,每张图片像素行数,列数。
# >4I’表示的是以大端的转换4个无符号整型变量(4个字节,非负数)
magic, num, rows, cols = unpack('>4I',f.read(16))
#print('magic:%d num:%d' % (magic, num))
#通过np.fromfile读入文件后,会返回一个一维数组,现在需要把每张照片都拿出来,
#所以使用了reshape这个函数,这个函数返回一个具相同数据但形状不同的数组
img = np.fromfile(f, dtype=np.uint8).reshape(num, 784, 1) # 在这里可以调整图片读入格式
return img
#读取标签的原理与读入图片的原理相同,就不在重复一遍了。
def read_label(path):
with open(path, 'rb') as f:
# 前8位为附加数据,每4位为一个整数,分别为幻数,标签数量。
magic, num = unpack('>2I',f.read(8))
# print('magic:%d num:%d' % (magic, num))
label = np.fromfile(f, dtype=np.uint8)
return label
#将图像的像素值正规化为0.0~1.0
def normalize_image(image):
img = image.astype(np.float32)/255.0
return img
#将标签转化为one_hot编码
#one-hot数组是指[0,0,1,0,0,0,0,0,0,0]这样的数组
def one_hot_label(label):
lab = np.zeros((label.size, 10))
for i, row in enumerate(lab):
row[label[i]] = 1
return lab
# 加载数据集以及数据预处理
def dataset_loader():
'''读入MNIST数据集
Parameters
----------
normalize : 将图像的像素值正规化为0.0~1.0
one_hot_label :
one_hot为True的情况下,标签作为one-hot数组返回
one-hot数组是指[0,0,1,0,0,0,0,0,0,0]这样的数组
Returns
----------
(训练图像, 训练标签), (测试图像, 测试标签)
'''
train_image = read_image(r'E:\学习资料\模式识别\神经网络代码\实验代码\全连接神经网络\MNIST_data\train-images.idx3-ubyte')
train_label = read_label(r'E:\学习资料\模式识别\神经网络代码\实验代码\全连接神经网络\MNIST_data\train-labels.idx1-ubyte')
test_image = read_image(r'E:\学习资料\模式识别\神经网络代码\实验代码\全连接神经网络\MNIST_data\t10k-images.idx3-ubyte')
test_label = read_label(r'E:\学习资料\模式识别\神经网络代码\实验代码\全连接神经网络\MNIST_data\t10k-labels.idx1-ubyte')
train_image = normalize_image(train_image)
train_label = one_hot_label(train_label)
train_label = train_label.reshape(train_label.shape[0],train_label.shape[1],1)
test_image = normalize_image(test_image)
test_label = one_hot_label(test_label)
test_label = test_label.reshape(test_label.shape[0],test_label.shape[1],1)
#train_image维度为60000×784×1,train_label维度为60000×10×1
#test_image维度为10000×784×1,test_label维度为10000×10×1
return train_image, train_label, test_image, test_label
# 编写神经网络类
class NetWork(object):
def __init__(self, sizes):
'''
初始化神经网络,给每层的权重和偏置赋初值
权重为一个列表,列表中每个值是一个二维n×m的numpy数组
偏置为一个列表,列表中每个值是一个二维n×1的numpy数组'''
## sizes是神经网络的层数
self.num_layers = len(sizes)
self.sizes = sizes
#权重矩阵以及偏置项
self.weights = [np.random.randn(n,m) for m,n in zip(sizes[:-1], sizes[1:])] # 一定得用rnadn而不是random,rnadn它有正也有负
self.biases = [np.random.randn(n,1) for n in sizes[1:]]
def sigmoid(self, z):
'''sigmoid激活函数,S型曲线'''
a = 1.0 / (1.0 + np.exp(-z))
return a
def sigmoid_prime(self, z):
'''sigmoid函数的一阶导数'''
return self.sigmoid(z) * (1 - self.sigmoid(z))
def feed_forward(self, x):
'''完成前向传播过程,由输入值计算神经网络最终的输出值
输入为一个列向量,输出也为一个列向量'''
value = x
#取出权重矩阵和偏置项,通过矩阵乘法运算和矩阵相加运算,
#再经过激活函数即可根据前一层的输出得到当前层的输出
for i in range(len(self.weights)):
value = self.sigmoid(np.dot(self.weights[i], value) + self.biases[i])
y = value
return y
#测试准确度函数
def evaluate(self, images, labels):
'''验证神经网络准确率,它在每训练完一个epoch数据后,
使用10000张测试数据来验证我们神经网络的准确率。'''
result = 0
for img, lab in zip(images, labels):
predict_label = self.feed_forward(img)
if np.argmax(predict_label) == np.argmax(lab):
result += 1
return result
#定义随机梯度下降函数,训练神经网络,赋予神经网络学习的能力
def SGD(self, train_image, train_label, test_image, test_label, epochs, mini_batch_size, eta):
'''Stochastic gradiend descent随机梯度下降法,将训练数据分多个batch
一次使用一个mini_batch_size的数据,调用update_mini_batch函数更新参数'''
for j in range(epochs):
mini_batches_image = [train_image[k:k+mini_batch_size] for k in range(0, len(train_image), mini_batch_size)]
mini_batches_label = [train_label[k:k+mini_batch_size] for k in range(0, len(train_label), mini_batch_size)]
for mini_batch_image, mini_batch_label in zip(mini_batches_image, mini_batches_label):
self.update_mini_batch(mini_batch_image, mini_batch_label, eta, mini_batch_size)
print("Epoch{0}: accuracy is {1}/{2}".format(j+1, self.evaluate(test_image, test_label), len(test_image)))
#update_mini_batch方法一次接收一个batch的训练图片和对应的训练标签
def update_mini_batch(self, mini_batch_image, mini_batch_label, eta, mini_batch_size):
'''通过一个batch的数据对神经网络参数进行更新
需要对当前batch中每张图片调用backprop函数将误差反向传播
求每张图片对应的权重梯度以及偏置梯度,最后进行平均使用梯度下降法更新参数'''
nabla_b = [np.zeros(b.shape) for b in self.biases]
nabla_w = [np.zeros(w.shape) for w in self.weights]
#mini_batch是一个tuple的list,[(x, y)]。而eta 则是学习率。
for x,y in zip(mini_batch_image, mini_batch_label):
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/mini_batch_size)*nw for w, nw in zip(self.weights, nabla_w)]
self.biases = [b-(eta/mini_batch_size)*nb for b, nb in zip(self.biases, nabla_b)]
#神经网络反向传播
def backprop(self, x, y):
'''计算通过单幅图像求得的每层权重和偏置的梯度'''
# delta_nabla_b、delta_nabla_w 分别用来装对每层权重矩阵和偏向向量求的偏导数
delta_nabla_b = [np.zeros(b.shape) for b in self.biases]
delta_nabla_w = [np.zeros(w.shape) for w in self.weights]
# 前向传播,计算各层的激活前的输出值以及激活之后的输出值,为下一步反向传播计算作准备
activations = [x]
# 储存每个未经由 sigmoid 计算的神经元的值
zs = []
for b, w in zip(self.biases, self.weights):
z = np.dot(w, activations[-1]) + b
zs.append(z)
activation = self.sigmoid(z)
activations.append(activation)
# 计算输出层的error
# 先求最后一层的delta误差以及b和W的导数
cost = activations[-1] - y
delta = cost * self.sigmoid_prime(zs[-1])
delta_nabla_b[-1] = delta
delta_nabla_w[-1] = np.dot(delta, activations[-2].transpose())
# 反向更新error
# 将delta误差反向传播以及各层b和W的导数,一直计算到第二层
for l in range(2, self.num_layers):
delta = np.dot(self.weights[-l+1].transpose(), delta) * self.sigmoid_prime(zs[-l])
delta_nabla_b[-l] = delta
delta_nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
return delta_nabla_b, delta_nabla_w
def main():
# 加载数据集
train_image, train_label, test_image, test_label = dataset_loader()
# 训练神经网络
net_trained = NetWork([784, 30,10])
net_trained.SGD(train_image, train_label, test_image, test_label, 30, 1000, 3)
if __name__ == '__main__':
main()
end = time.clock()
print('Running Time: %s Seconds' % (end - start))
实验结果
实验报告下载地址:https://download.csdn.net/download/by1jing/12889620