在上一篇文章中,我们从数学理论对多层感知机的反向传播进行了推导。南柯一梦宁沉沦:神经网络中反向传播算法数学推导​zhuanlan.zhihu.com

这一篇文章中我们将基于上一篇文章最后给出的算法使用Python语言来实现一个多层感知机。

完整代码以及代码的使用方法,可以光顾我的GithubProfessorHuang/Python_LeNet_UnderlyingImplementation​github.com

MNIST数据准备

要进行训练,我们第一步需要先准备好训练数据,在这里我们使用经典的MNIST数据集。MNIST数据集的获取有多种方式,存储的格式也各不相同。在这里,我建议直接在MNIST的官网,即Yann Lecun的个人网站上获取。http://yann.lecun.com/exdb/mnist/index.html

我们可以在网站上下载得到四个压缩包,分别是训练图片,训练标签,测试图片和测试标签,解压之后获得四个文件的存储格式为idx。我们需要使用Python的struct库中unpack函数进行解析。然后使用numpy库将数据转换成numpy数组的形式便于我们之后处理。

图片数据解析为一个三维数组(也可称为张量),格式为图片数量×784×1.需要提前将图片数据从0-255的整数转换成0-1的浮点数。标签数据解析为一维数据,只存储了标签值的一个标量。我们需要将它也转换成一个三维数组,格式为标签数量×10×1,每个标签以one-hot格式存储,即一个10维列向量,正确标签为下标的数为1,其它的为0.

import numpy as np

from struct import unpack

def read_image(path):

with open(path, 'rb') as f:

magic, num, rows, cols = unpack('>4I',f.read(16))

img = np.fromfile(f, dtype=np.uint8).reshape(num, 784, 1) # 在这里可以调整图片读入格式

return img

def read_label(path):

with open(path, 'rb') as f:

magic, num = unpack('>2I',f.read(8))

label = np.fromfile(f, dtype=np.uint8)

return label

def normalize_image(image):

img = image.astype(np.float32)/255.0

return img

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():

train_image = read_image(r'C:\Users\95410\Downloads\数据集\MNIST\train-images.idx3-ubyte')

train_label = read_label(r'C:\Users\95410\Downloads\数据集\MNIST\train-labels.idx1-ubyte')

test_image = read_image(r'C:\Users\95410\Downloads\数据集\MNIST\t10k-images.idx3-ubyte')

test_label = read_label(r'C:\Users\95410\Downloads\数据集\MNIST\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)

return train_image, train_label, test_image, test_label

train_image, train_label, test_image, test_label = dataset_loader()

train_image维度为60000×784×1,train_label维度为60000×10×1

test_image维度为10000×784×1,test_label维度为10000×10×1

编写神经网络类

初始化神经网络

我们定义一个神经网络类NetWork,将涉及到的函数与神经网络各层参数封装在里面。

class NetWork(object):

def __init__(self, sizes):

'''初始化神经网络,给每层的权重和偏置赋初值权重为一个列表,列表中每个值是一个二维n×m的numpy数组偏置为一个列表,列表中每个值是一个二维n×1的numpy数组'''

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

self.biases = [np.random.randn(n,1) for n in sizes[1:]]

在初始化一个神经网络时,我们只需要以列表的形式提供神经网络各层的大小,神经网络各层的权重矩阵以及偏置项便会随机初始化并以二维numpy数组的进行存储,并以列表的形式按顺序存放在self.weights以及self.biases中,可以让该神经网络中其它方法使用与更新。

注意权重矩阵的初始化使用np.random.randn提供标准正态分布的随机数,它有正也有负。而我之前不小心使用了np.random.random提供的是0-1的浮点数,结果导致神经网络无法训练。

神经网络前向传播

神经网络前向传播很简单,取出权重矩阵和偏置项,通过矩阵乘法运算和矩阵相加运算,再经过激活函数即可根据前一层的输出得到当前层的输出,递归运算下去即可得到神经网络最终的输出。

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

矩阵的乘法用np.dot函数实现。我们使用的是sigmoid函数

作为激活函数。

def sigmoid(self, z):

'''sigmoid激活函数'''

a = 1.0 / (1.0 + np.exp(-z))

return a

神经网络反向传播

根据输入列向量x,前向传播出各层激活前的输出

和激活后的输出

为了之后计算delta误差以及损失函数对权重矩阵的导数。

将最后一层输出

与标签列向量y代入到损失函数对

的导数,求得最后一层的delta误差

利用公式

可以依次求出每层的delta误差,Hadmard积直接用*符号即可,表示逐元素相乘。

每求出一层的delta误差,便可以很快的带入公式,该层的偏置的导数与delta误差相等,该层权重矩阵的导数等于该层delta误差右乘上上一层激活后输出的转置。

def backprop(self, x, y):

'''计算通过单幅图像求得的每层权重和偏置的导数'''

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]

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)

# 先求最后一层的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())

# 将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

backprop方法只是根据一副图像以及对应的标签求得神经网络参数的导数,而我们使用随机梯度下降法,需要使用一个batch的数据来更新数据。sigmoid_prime是对sigmoid函数的一阶导数:

def sigmoid_prime(self, z):

'''sigmoid函数的一阶导数'''

return self.sigmoid(z) * (1 - self.sigmoid(z))

我们使用方法update_mini _batch来调用backprop方法实现对一个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]

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)]

update_mini_batch方法一次接收一个batch的训练图片和对应的训练标签,根据该batch数据求得的参数的平均导数,使用梯度下降法对神经网络中各层权重矩阵与偏置进行更新。

我们需要使用整个60000张训练数据来对神经网络进行训练,因此我们需要一个更高层的函数SGD,接收训练数据,并将训练数据分成一个个batch,再调用update_mini_batch方法对参数进行更新。

def SGD(self, train_image, train_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)))

SGD接收的参数中,epochs代表训练轮数,将60000张数据全部训练一遍称为一个epoch。mini_batch_size表示batch大小,即一次使用多少张图片对参数进行更新。eta表示学习率。

验证神经网络准确率

我们在SGD方法中可以看见evaluate方法,它在每训练完一个epoch数据后,使用10000张测试数据来验证我们神经网络的准确率。

def evaluate(self, images, labels):

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

验证的方法很简单,依次从验证数据集中取出图片,经过神经网络前向传播,看最终预测值与图片的标签是否一致即可。evaluate方法返回10000张图片中预测正确的数量。

测试我们的神经网络

我们使用两行代码即可对我们的神经网络定义以及训练

# 训练神经网络

net_trained = NetWork([784, 30, 10])

net_trained.SGD(train_image, train_label, 30, 10, 3)

我们设置一个三层的神经网络,唯一的一个隐藏层只有30个神经元,可以加快我们的训练速度。我们调用SGD方法,训练30个epoch,batch大小为10,学习率为3.这些参数都是我们可以调整的,但相应地会取得不同的训练效果,不合适的参数有时候会导致训练无法正确进行。

完成30个epoch的训练,在我的电脑上大概只需要3分钟即可,而我们神经网络对MNIST验证集的预测正确率已经可以达到94.85%。bingo!

参考:

[1]刘建平Pinard:深度神经网络(DNN)反向传播算法(BP)深度神经网络(DNN)反向传播算法(BP) - 刘建平Pinard - 博客园​www.cnblogs.com

[2] Neural Networks and Deep Learning by By Michael NielsenNeural networks and deep learning​neuralnetworksanddeeplearning.com

[3] 孤独暗星: MNIST手写数字数据集的读取,基于python3https://blog.csdn.net/weixin_40522523/article/details/82823812​blog.csdn.net

python 底层实现_用Python从底层实现一个多层感知机相关推荐

  1. python 概率分布模型_使用python的概率模型进行公司估值

    python 概率分布模型 Note from Towards Data Science's editors: While we allow independent authors to publis ...

  2. python 时间序列预测_使用Python进行动手时间序列预测

    python 时间序列预测 Time series analysis is the endeavor of extracting meaningful summary and statistical ...

  3. python 字节流分段_由Python历史「解密」Python底层逻辑

    一次纯粹的hacking Python的作者,Guido von Rossum,荷兰人.1982年,Guido从阿姆斯特丹大学获得了数学和计算机硕士学位.尽管,他算得上是一位数学家,但他更加享受计算机 ...

  4. python底层与机器底层关系_由Python历史「解密」Python底层逻辑

    一次纯粹的hacking Python的作者,Guido von Rossum,荷兰人.1982年,Guido从阿姆斯特丹大学获得了数学和计算机硕士学位.尽管,他算得上是一位数学家,但他更加享受计算机 ...

  5. python queue 调试_学Python不是盲目的,是有做过功课认真去了解的

    有多少伙伴是因为一句'人生苦短,我用Python'萌生想法学Python的!我跟大家更新过很多Python学习教程普及过多次的Python相关知识,不过大家还是还得计划一下Python学习路线!Pyt ...

  6. python医学图像读取_对python读取CT医学图像的实例详解

    需要安装OpenCV和SimpleItk. SimpleItk比较简单,直接pip install SimpleItk即可. 代码如下: #coding:utf-8 import SimpleITK ...

  7. python精通时间_学Python需要多久能学会?精通Python需要多长时间?

    该楼层疑似违规已被系统折叠 隐藏此楼查看此楼 严格意思上的说,Python其实是一个脚本语言,编程语言多种多样,但是却有开源和闭源之分,Python就是一种开放核心源代码编程语言,其开发代码的效率非常 ...

  8. python笛卡尔_用Python 3来模拟笛卡尔积

    在数学中,两个集合 和 的笛卡尔积,是所有可能的有序对组成的集合,其中有序对的第一个对象是 的成员,第二个对象是 的成员.在集合论中表示为 ,例子如下: . 例如,集合 , ,那么这两个集合的笛卡尔积 ...

  9. python移动图形工作站_让Python跑得更快

    原标题:让Python跑得更快 点击关注 异步图书,置顶公众号 每天与你分享 IT好书 技术干货 职场知识 Tips 参与文末话题讨论,即有机会获得异步图书一本. Python很容易学.你之所以阅读本 ...

最新文章

  1. Storm 01之 Storm基本概念及第一个demo
  2. Eslint配置文件 `.eslintrc.js`
  3. python能做表格吗-python可以用来做excel吗
  4. Windows Mobile 开发系列文章收藏 - 讨论篇
  5. 【学术相关】你只看到了200万年薪的招聘,看不到被困校园的几十万博士
  6. 老李分享:Android -自动化埋点 2
  7. Ruby:字符集和编码学习总结
  8. 解决HbuiderX将uni-app开发的项目运行到小程序编译后文件vendor.js太大的问题
  9. 如何使用explain进行SQL语句调优
  10. MYSQL优化派生表(子查询)在From语句中的
  11. Asp.Net母版页和内容页运行机制
  12. linux系统 看com口,Linux如何设置com1口,让超级终端通过com1口进行登录
  13. 管家婆 源码 php,在windows平台上构建本身的PHP
  14. Linux中eclipse配置Maven,eclipse maven选项怎么配置settings
  15. php fseek函数,php fseek函数怎么用
  16. 电容或电感的电压_电容与电感的对偶性小结
  17. 翻转数组,将数组倒序输出
  18. css vw vh ie9,css3中calc、vw、vh、vmin、vmax 属性的应用及兼容性详解
  19. canvas绘制圆形头像
  20. android实现qq邮箱多个图标效果

热门文章

  1. sql server 常用的扩展存储过程
  2. 使用nginx进行负载均衡
  3. OpenLdap 相关命令
  4. dropdownlist总是获取第一个值
  5. oracle 基础1
  6. USB的pid和vid以及usb路径名字之间的关系
  7. 自动控制理论及matlab,自动控制理论及MATLAB实现
  8. linux转mysql_转linux下mysql命令
  9. 黑龙江2020计算机一级考试时间,黑龙江2020年计算机等级考试报名时间汇总
  10. linux java services_在 Linux 上创建第一个 Service Fabric Java 应用程序