【飞浆】神经网络实现手写数字识别
目录
继上一次SVM实现鸢尾花分类后,我们今次使用神经网络对0~9 的手写数字进行识别。
什么是神经网络?
首先我们可以通过一个短视频对神经网络有一个基本的了解:一分钟告诉你什么是神经网络。
接下来我再用文字简单谈谈神经网络的由来。
在神经网络出现之前,首先被提出的是一种叫做感知机的东西:

上图所示感知机有三个输入端X0、X1、Xn,其分别乘以三个权重W1、W2、Wn 并相加,再加上大小为1.0 的偏置W0(通常用b 表示)。这些数的加权和(净输入)最后再经过一个用阶跃函数所代表的激活函数就得到了一个输出y’。
而当我们将感知机的阶跃函数替换为其他的函数,这便就是神经元了。所以可以说,感知机就是使用阶跃函数作为激活函数。
当我们再将多个神经元组成一个层,多层之间逐个连接起来,便形成了神经网络:

接下来就开始试着用神经网络来完成手写数字识别吧。
首先导入一些必要的库:
import paddle as paddle
import paddle.fluid as fluid
import matplotlib.pyplot as plt
import os
import numpy as np
from PIL import Image
一、数据准备
大家有需要的话可以在这里下载MNIST数据集:《手写数字数据集MNIST.zip》
MNIST 数据集中的每张图片都是28×28像素的二维图片
BATCH_SIZE = 64
BUF_SIZE = 512
# 用于训练的数据提供器,每次从缓存中随机读取批次大小的数据
train_reader = paddle.batch(paddle.reader.shuffle(paddle.dataset.mnist.train(),
buf_size = BUF_SIZE),
batch_size = BATCH_SIZE)
# 用于测试的数据提供器
test_reader = paddle.batch(paddle.reader.shuffle(paddle.dataset.mnist.test(),
buf_size = BUF_SIZE),
batch_size = BATCH_SIZE)
其中paddle.dataset.mnist.train() 表示获取mnist 训练集;paddle.reader.shuffle() 表示每次缓存BUF_SIZE 个数据项并打乱;paddle.batch() 表示每BATCH_SIZE 组成一个batch。
二、网络配置
1. 定义网络结构
在这里我们定义一个由全连接层构造的前馈神经网络。
# 定义多层感知机
def multilayer_perceptron(input):
# 第一个全连接层,激活函数为ELU
hidden1 = fluid.layers.fc(input=input, size=100, act='elu')
# 第二个全连接层,激活函数为ELU
hidden2 = fluid.layers.fc(input=hidden1, size=100, act='elu')
# 以softmax 为激活函数的全连接输出层,输出层的大小必须为数字的个数10
prediction = fluid.layers.fc(input=hidden2, size=10, act='softmax')
return prediction
关于
fluid.layers.fc()函数的简介:
详情请参考百度API:
https://www.paddlepaddle.org.cn/documentation/docs/zh/1.2/api_cn/layers_cn.html#fc
该网络一共包含四层,分别是输入层(代码中未体现)、第一层隐层、第二层隐层、输出层。
- 输入层:为方便计算,每张图片都被化为784维向量,即X=(X0, X1, X2, … , X783)。+1代表偏置参数的系数为1,所以这一层有784个节点;
- 第一层隐层:全连接层,激活函数为ELU,节点数为100;
关于激活函数的详细讲解可前往《深度学习中几种常见的激活函数理解与总结》进行查看; - 第二层隐层:全连接层,激活函数也为ELU,节点数为100;
- 输出层:全连接层,激活函数为Softmax,节点数为10(因为最终我们想要得到的结果是系统给予的在0~9 间的预测,一共有10种答案,故预测输出的节点数为10。
2. 定义输入数据类型
接着就是定义输入的图片数据与对应标签:
# 输入的原始图像数据,大小为1*28*28(单通道,28*28像素)
image = fluid.layers.data(name='image', shape=[1,28,28], dtype='float32')
# 标签,名称为label,对应输入图片的类别标签
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
关于
fluid.layers.data()函数的简介:
详情请参考百度API:
https://www.paddlepaddle.org.cn/documentation/docs/zh/1.2/api_cn/layers_cn.html#data
然后创建一个网络并将image 作为参数代入:
# 获取分类器
predict = multilayer_perceptron(image)
3. 定义损失函数和准确率
有了一个网络对象之后,我们就要定义损失函数并获取网络的准确率:
# 使用交叉熵损失函数,描述真实样本标签和预测概率之间的差值
cost = fluid.layers.cross_entropy(input=predict, label=label)
# 使用类交叉熵函数计算predict 和label 之间的损失函数
avg_cost = fluid.layers.mean(cost)
# 计算分类准确率
acc = fluid.layers.accuracy(input=predict, label=label)
4. 定义优化算法
优化算法决定了神经网络的参数该如何更新。
# 获取测试程序
test_program = fluid.default_main_program().clone(for_test=True)
# 使用Adam 算法进行优化,learning_rate 是学习率(其大小与网络的训练收敛速度有关,默认0.001)
optimizer = fluid.optimizer.AdamOptimizer()
opts = optimizer.minimize(avg_cost)
关于
fluid.optimizer.AdamOptimizer()函数的简介:
详情请参考百度API:
https://www.paddlepaddle.org.cn/documentation/docs/zh/1.2/api_cn/optimizer_cn.html#adamoptimizer
fluid.default_main_program() 函数用于获取默认或全局main program(主程序)。该主程序用于训练和测试模型。default_main_program 是fluid 的许多编程接口(API)的Program 参数的默认值。例如,当用户program 没有传入的时候, Executor.run() 会默认执行default_main_program。
三、训练网络
1. 准备工作
上述使用了Program 描述了前馈神经网络模型,现在试着用Executor 来执行Program,训练定义好的Program。
首先我们应该先确定好是使用CPU 还是GPU 来训练网络(我在此使用CPU):
# 使用CPU 时use_cuda=False,使用GPU 时use_cuda=True
use_cuda = False
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
fluid.default_startup_program() 函数可以获取默认/全局 startup program (启动程序)。
告知网络传入的数据分为两部分,第一部分是image 值,第二部分是label 值:
# 告知网络传入的数据分为两部分,第一部分是image 值,第二部分是label 值
feeder = fluid.DataFeeder(place=place, feed_list=[image, label])
关于
fluid.DataFeeder()函数的简介:
详情请参考百度API:
https://www.paddlepaddle.org.cn/documentation/docs/zh/1.2/api_cn/data_feeder_cn.html
2. 提前定义画图所需变量与函数
# 展示模型训练曲线
all_train_iter = 0
all_train_iters = []
all_train_costs = []
all_train_accs = []
def draw_train_process(title, iters, costs, accs, label_cost, label_acc):
plt.title(title, fontsize=24)
plt.xlabel("iter", fontsize=20)
plt.ylabel("cost/acc", fontsize=20)
plt.plot(iters, costs, color='red', label=label_cost)
plt.plot(iters, accs, color='green', label=label_acc)
plt.legend()
plt.grid()
plt.show()
3. 开始训练
定义模型保存路径:
model_save_dir = "C:/Users/waao_wuyou/Desktop/handwrite_detection.model"
# 训练轮数
EPOCH_NUM = 2
for pass_id in range(EPOCH_NUM):
# 进行训练
for batch_id, data in enumerate(train_reader()): # 遍历train_reader
train_cost, train_acc = exe.run(program=fluid.default_main_program(), # 运行主程序
feed=feeder.feed(data), # 给模型喂入数据
fetch_list=[avg_cost, acc]) # fetch 误差、准确率
all_train_iter = all_train_iter + BATCH_SIZE
all_train_iters.append(all_train_iter)
all_train_costs.append(train_cost[0])
all_train_accs.append(train_acc[0])
# 每200个batch 打印一次信息,误差、准确率
if batch_id % 200 == 0:
print('Pass:%d, Batch:%d, Cost:%0.5f, Accuracy:%0.5f' %
(pass_id, batch_id, train_cost[0], train_acc[0]))
# 进行测试
test_accs = []
test_costs = []
# 每训练一轮,进行一次测试
for batch_id, data in enumerate(test_reader()): # 遍历test_reader
test_cost, test_acc = exe.run(program=test_program, # 执行训练程序
feed=feeder.feed(data), # 喂入数据
fetch_list=[avg_cost, acc]) # fetch 误差、准确率
# 每个batch 的准确率
test_accs.append(test_acc[0])
# 每个batch 的误差
test_costs.append(test_cost[0])
# 求测试结果的平均值
# 每轮平均误差
test_cost = (sum(test_costs) / len(test_costs))
# 每轮平均准确率
test_acc = (sum(test_accs) / len(test_accs))
print('Test:%d, Cost:%0.5f, Accuracy:%0.5f' %
(pass_id, test_cost, test_acc))
在exe.run 中,executor表示之前创建的执行器,program表示执行器所执行的program,是之前创建的program,如果该项参数没有给定的话则默认使用defalut_main_program,reader表示读取到的数据,feeder表示前向输入的变量,fetch_list表示用户想得到的变量。
训练结果:
Pass:0, Batch:0, Cost:2.94062, Accuracy:0.10938
Pass:0, Batch:200, Cost:0.52611, Accuracy:0.85938
Pass:0, Batch:400, Cost:0.25581, Accuracy:0.90625
Pass:0, Batch:600, Cost:0.27308, Accuracy:0.92188
Pass:0, Batch:800, Cost:0.09748, Accuracy:0.96875
Test:0, Cost:0.18250, Accuracy:0.94198
Pass:1, Batch:0, Cost:0.17744, Accuracy:0.92188
Pass:1, Batch:200, Cost:0.09337, Accuracy:0.95312
Pass:1, Batch:400, Cost:0.18929, Accuracy:0.92188
Pass:1, Batch:600, Cost:0.11689, Accuracy:0.98438
Pass:1, Batch:800, Cost:0.09298, Accuracy:0.96875
Test:1, Cost:0.12345, Accuracy:0.96248
save models to C:/Users/Noah/Desktop/handwrite_detection.model
训练模型保存完成!
可见这个网络模型第二次测试的结果的准确率已达0.96248
4. 保存网络模型
# 若路径不存在就创建
if not os.path.exists(model_save_dir):
os.makedirs(model_save_dir)
print('save models to %s' % (model_save_dir))
fluid.io.save_inference_model(model_save_dir, # 保存推理model 的路径
['image'], # 推理(inference)需要的feed 的数据
[predict], # 保存推理结果的Variables
exe) # executor 保存inference model
print('训练模型保存完成!')
四、训练过程可视化
draw_train_process("training",
all_train_iters,
all_train_costs,
all_train_accs,
"training cost",
"training acc")

五、模型预测
当我们训练好了网络模型之后,接下来就试着用这个模型对下面这个“3”进行识别分类:

首先对这个图片进行一些必要的处理:
def load_image(file):
im = Image.open(file).convert('L')
im = im.resize((28, 28), Image.ANTIALIAS)
im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32)
im = im / 255.0 * 2.0 - 1.0
return im
img = load_image('C:/Users/waao_wuyou/Desktop/infer_3.png')
首先对图片进行灰度化,然后压缩图像大小为28×28像素,接着将图像转换成一维向量,最后对一维向量进行归一化处理。
然后创建预测用的Executer:
infer_exe = fluid.Executor(place)
inference_scope = fluid.core.Scope()
然后通过fluid.io.load_inference_model(),预测器会从params_dirname中读取已经训练好的模型,来对从未遇见过的数据进行预测:
with fluid.scope_guard(inference_scope):
# 获取训练好的模型
# 从指定目录中加载 推理model(inference model)
[inference_program, # 推理Program
feed_target_names, # 是一个str列表,它包含需要在推理 Program 中提供数据的变量的名称。
fetch_targets] = fluid.io.load_inference_model(model_save_dir, # fetch_targets:是一个 Variable 列表,从中我们可以得到推断结果。model_save_dir:模型保存的路径
infer_exe) # infer_exe: 运行 inference model的 executor
results = exe.run(program=inference_program, # 运行推测程序
feed={feed_target_names[0]:img}, # 喂入要预测的img
fetch_list=fetch_targets) # 得到推测结果
关于
fluid.scope_guard()函数的简介:
详情请参考百度API:
https://www.paddlepaddle.org.cn/documentation/docs/zh/1.2/api_cn/fluid_cn.html#scope-guard
# 获取概率最大的label
# argsort函数返回的是result 数组值从小到大的索引值
lab = np.argsort(results)
print(results)
# -1代表读取数组中倒数第一列
print("该图片的预测结果的label 为:%d" % lab[0][0][-1])
预测结果正确:

最后再用我自己写的0~9 试一试:
(附链接:MyHandwritting.zip)
结果如下,其中这个“9”我都写了几十遍,甚至模仿着MNIST 的写还是识别失败,其余数字的识别效果还行。

六、完整代码
1. 模型训练
import paddle as paddle
import paddle.fluid as fluid
import matplotlib.pyplot as plt
import os
#========== 准备数据 ==========
BATCH_SIZE = 64
BUF_SIZE = 512
# 用于训练的数据提供器,每次从缓存中随机读取批次大小的数据
# paddle.dataset.mnist.train() 表示获取mnist 训练集
# paddle.reader.shuffle() 表示每次缓存BUF_SIZE 个数据项并打乱
# paddle.batch() 表示每BATCH_SIZE 组成一个batch
train_reader = paddle.batch(paddle.reader.shuffle(paddle.dataset.mnist.train(),
buf_size = BUF_SIZE),
batch_size = BATCH_SIZE)
# 用于测试的数据提供器
test_reader = paddle.batch(paddle.reader.shuffle(paddle.dataset.mnist.test(),
buf_size = BUF_SIZE),
batch_size = BATCH_SIZE)
#========== 网络配置 ==========
# 定义多层感知机
def multilayer_perceptron(input):
# 第一个全连接层,激活函数为ELU
hidden1 = fluid.layers.fc(input=input, size=100, act='elu')
# 第二个全连接层,激活函数为ELU
hidden2 = fluid.layers.fc(input=hidden1, size=100, act='elu')
# 以softmax 为激活函数的全连接输出层,输出层的大小必须为数字的个数10
prediction = fluid.layers.fc(input=hidden2, size=10, act='softmax')
return prediction
# 输入的原始图像数据,大小为1*28*28(单通道,28*28像素)
image = fluid.layers.data(name='image', shape=[1,28,28], dtype='float32')
# 标签,名称为label,对应输入图片的类别标签
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
# 获取分类器
predict = multilayer_perceptron(image)
# 定义损失函数和准确率
# 使用交叉熵损失函数,描述真实样本标签和预测概率之间的差值
cost = fluid.layers.cross_entropy(input=predict, label=label)
# 使用类交叉熵函数计算predict 和label 之间的损失函数
avg_cost = fluid.layers.mean(cost)
# 计算分类准确率
acc = fluid.layers.accuracy(input=predict, label=label)
# 定义优化函数(参数更新方式)
# 获取测试程序
test_program = fluid.default_main_program().clone(for_test=True)
# 使用Adam 算法进行优化,learning_rate 是学习率(其大小与网络的训练收敛速度有关)
optimizer = fluid.optimizer.AdamOptimizer()
opts = optimizer.minimize(avg_cost)
#========== 模型训练与评估 ==========
# 创建训练的Executor
# 定义使用CPU 还是GPU,使用CPU 时use_cuda=False,使用GPU 时use_cuda=True
use_cuda = False
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
# 告知网络传入的数据分为两部分,第一部分是image 值,第二部分是label 值
# DataFeeder 负责将数据提供器返回的数据转成一种特殊的数据结构,使其可以输入到Executor 中
feeder = fluid.DataFeeder(place=place, feed_list=[image, label])
# 展示模型训练曲线
all_train_iter = 0
all_train_iters = []
all_train_costs = []
all_train_accs = []
def draw_train_process(title, iters, costs, accs, label_cost, label_acc):
plt.title(title, fontsize=24)
plt.xlabel("iter", fontsize=20)
plt.ylabel("cost/acc", fontsize=20)
plt.plot(iters, costs, color='red', label=label_cost)
plt.plot(iters, accs, color='green', label=label_acc)
plt.legend()
plt.grid()
plt.show()
# 训练并保存数据
EPOCH_NUM = 2
model_save_dir = "C:/Users/waao_wuyou/Desktop/handwrite_detection.model"
for pass_id in range(EPOCH_NUM):
# 进行训练
for batch_id, data in enumerate(train_reader()): # 遍历train_reader
train_cost, train_acc = exe.run(program=fluid.default_main_program(), # 运行主程序
feed=feeder.feed(data), # 给模型喂入数据
fetch_list=[avg_cost, acc]) # fetch 误差、准确率
all_train_iter = all_train_iter + BATCH_SIZE
all_train_iters.append(all_train_iter)
all_train_costs.append(train_cost[0])
all_train_accs.append(train_acc[0])
# 每200个batch 打印一次信息,误差、准确率
if batch_id % 200 == 0:
print('Pass:%d, Batch:%d, Cost:%0.5f, Accuracy:%0.5f' %
(pass_id, batch_id, train_cost[0], train_acc[0]))
# 进行测试
test_accs = []
test_costs = []
# 每训练一轮,进行一次测试
for batch_id, data in enumerate(test_reader()): # 遍历test_reader
test_cost, test_acc = exe.run(program=test_program, # 执行训练程序
feed=feeder.feed(data), # 喂入数据
fetch_list=[avg_cost, acc]) # fetch 误差、准确率
# 每个batch 的准确率
test_accs.append(test_acc[0])
# 每个batch 的误差
test_costs.append(test_cost[0])
# 求测试结果的平均值
# 每轮平均误差
test_cost = (sum(test_costs) / len(test_costs))
# 每轮平均准确率
test_acc = (sum(test_accs) / len(test_accs))
print('Test:%d, Cost:%0.5f, Accuracy:%0.5f' %
(pass_id, test_cost, test_acc))
# 保存模型
# 若路径不存在就创建
if not os.path.exists(model_save_dir):
os.makedirs(model_save_dir)
print('save models to %s' % (model_save_dir))
fluid.io.save_inference_model(model_save_dir, # 保存推理model 的路径
['image'], # 推理(inference)需要的feed 的数据
[predict], # 保存推理结果的Variables
exe) # executor 保存inference model
print('训练模型保存完成!')
draw_train_process("training",
all_train_iters,
all_train_costs,
all_train_accs,
"training cost",
"training acc")
2. 模型预测
import paddle.fluid as fluid
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt
# 定义使用CPU 还是GPU,使用CPU 时use_cuda=False,使用GPU 时use_cuda=True
use_cuda = False
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
exe = fluid.Executor(place)
model_save_dir = "C:/Users/waao_wuyou/Desktop/handwrite_detection.model"
#========== 网络预测 ==========
def load_image(file):
im = Image.open(file).convert('L')
im = im.resize((28, 28), Image.ANTIALIAS)
im = np.array(im).reshape(1, 1, 28, 28).astype(np.float32)
im = im / 255.0 * 2.0 - 1.0
return im
infer_exe = fluid.Executor(place)
inference_scope = fluid.core.Scope()
for i in range(10):
img = load_image('C:/Users/waao_wuyou/Desktop/MyHandwritting/%d.jpg' % i)
with fluid.scope_guard(inference_scope):
# 获取训练好的模型
# 从指定目录中加载 推理model(inference model)
[inference_program, # 推理Program
feed_target_names, # 是一个str列表,它包含需要在推理 Program 中提供数据的变量的名称。
fetch_targets] = fluid.io.load_inference_model(model_save_dir, # fetch_targets:是一个 Variable 列表,从中我们可以得到推断结果。model_save_dir:模型保存的路径
infer_exe) # infer_exe: 运行 inference model的 executor
results = exe.run(program=inference_program, # 运行推测程序
feed={feed_target_names[0]:img}, # 喂入要预测的img
fetch_list=fetch_targets) # 得到推测结果
# 获取概率最大的label
# argsort函数返回的是result 数组值从小到大的索引值
lab = np.argsort(results)
# -1代表读取数组中倒数第一列
print("该图片的预测结果的label 为:%d" % lab[0][0][-1])
更多推荐





所有评论(0)