本文作者:馬立
分享嘉宾:白浩杰
文章整理:马立辉
内容来源:百度云智学院
导读
上篇文章,我们重点讲解了卷积神经网络的基本概念及结构。本篇,我们将进行代码实战——完成手写数字识别任务。
本次实验代码是基于百度的深度学习框架——飞桨(PaddlePaddle)。
本篇文章主要内容包括:
1.创建实验资源
2.项目实战
3.总结
1.创建实验资源
本次实验将在百度云智学院实验平台上进行,该平台已经为大家准备好实验所需环境。实验前,我们需要创建实验资源,步骤如下:
《手写数字识别项目实战》地址为:
http://abcxueyuan.cloud.baidu.com/newlab/#/lab_detail/lab_simple_say?id=116
打开该网址进入如下页面,点击:开始学习。在弹出的页面中点击创建资源。
创建资源完成后点击进入实验。按以下步骤操作:
步骤一:
步骤二:
步骤三:
步骤四:
步骤五:
按照上述步骤完成实验资源的创建后,便可开始进行实验。
2.项目实战
2.1数据集介绍
当我们学习编程的时候,编写的第一个程序一般是打印出"Hello World"。而机器学习(或深度学习)的入门实验,一般是完成MNIST数据集上的手写数字识别任务。原因是手写数字识别属于典型的图像分类任务,比较简单。同时MNIST数据集也很经典。MNIST数据集作为一个简单的计算机视觉数据集,包含一系列如下图所示的手写数字图片和对应的标签。MNIST数据集有训练图片60000张,测试图片10000张,图片均是28×28像素,标签则对应着0-9的10个数字。每张图片都经过了归一化和居中处理。
2.2模型概览
本次实验,我们将用三种图像分类模型来实现,以便让大家对比不同模型的效果。
在介绍模型前,先给出一些说明:
我们来研究一下这三种分类模型:
①Softmax回归(Softmax Regression)
先将输入层经过一个全连接层得到特征,然后直接通过softmax函数计算多个类别的概率并输出。这便是最简单的Softmax回归模型。
输入层的数据????传到输出层,在激活操作之前,会乘以相应的权重????,并加上偏置变量????,具体如下:
其中:
下图为softmax回归模型的网络结构图,图中权重用蓝线表示、偏置用红线表示、+1代表偏置参数的系数为1。
在分类问题中,我们一般采用交叉熵损失函数(cross entropy loss),公式如下:
②多层感知机(Multilayer Perceptron, MLP)
Softmax回归模型采用了最简单的两层神经网络,即只有输入层和输出层,因此其拟合能力有限。为了达到更好的识别效果,我们考虑在输入层和输出层中间加上若干个隐藏层。
下图为多层感知机的网络结构图,图中权重用蓝线表示、偏置用红线表示、+1代表偏置参数的系数为1。
③卷积神经网络(Convolutional Neural Network, CNN)
在多层感知器模型中,我们是将图像展开成一维向量输入到网络中,这忽略了图像的位置和结构信息,而卷积神经网络能够更好的利用图像的结构信息。LeNet-5是一个较简单的卷积神经网络。下图显示了其结构:输入的二维图像,先经过两次卷积层到池化层(降采样层),再经过全连接层,最后使用softmax函数作为输出层激活函数。
2.3代码实现
1.加载 Paddle框架 的 Fluid 模块:
import os
from PIL import Image # 导入图像处理模块
import matplotlib.pyplot as plt
import numpy
import paddle # 导入paddle模块
import paddle.fluid as fluid
from __future__ import print_function # 将python3中的print特性导入当前版本
2.搭建三种分类器:
- Softmax回归:只通过一层简单的以softmax为激活函数的全连接层,就可以得到分类的结果。
def softmax_regression():
"""
定义softmax分类器:
一个以softmax为激活函数的全连接层
Return:
predict_image -- 分类的结果
"""
# 输入的原始图像数据,大小为28*28*1
img = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32')
# 以softmax为激活函数的全连接层,输出层的大小必须为数字的个数10
predict = fluid.layers.fc(
input=img, size=10, act='softmax')
return predict
- 多层感知机:下面代码实现了一个含有两个隐藏层的多层感知机。其中两个隐藏层的激活函数均采用ReLU,输出层的激活函数用Softmax。
def multilayer_perceptron():
"""
定义多层感知机分类器:
含有两个隐藏层(全连接层)的多层感知器
其中前两个隐藏层的激活函数采用 ReLU,输出层的激活函数用 Softmax
Return:
predict_image -- 分类的结果
"""
# 输入的原始图像数据,大小为28*28*1
img = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32')
# 第一个全连接层,激活函数为ReLU
hidden = fluid.layers.fc(input=img, size=200, act='relu')
# 第二个全连接层,激活函数为ReLU
hidden = fluid.layers.fc(input=hidden, size=200, act='relu')
# 以softmax为激活函数的全连接输出层,输出层的大小必须为数字的个数10
prediction = fluid.layers.fc(input=hidden, size=10, act='softmax')
return prediction
- 卷积神经网络LeNet-5:输入的二维图像,首先经过两次卷积层到池化层,再经过全连接层,最后使用以softmax为激活函数的全连接层作为输出层。
def convolutional_neural_network():
"""
定义卷积神经网络分类器:
输入的二维图像,经过两个卷积-池化层,使用以softmax为激活函数的全连接层作为输出层
Return:
predict -- 分类的结果
"""
# 输入的原始图像数据,大小为28*28*1
img = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32')
# 第一个卷积-池化层
# 使用20个5*5的滤波器,池化大小为2,池化步长为2,激活函数为Relu
conv_pool_1 = fluid.nets.simple_img_conv_pool(
input=img,
filter_size=5,
num_filters=20,
pool_size=2,
pool_stride=2,
act="relu")
conv_pool_1 = fluid.layers.batch_norm(conv_pool_1)
# 第二个卷积-池化层
# 使用50个5*5的滤波器,池化大小为2,池化步长为2,激活函数为Relu
conv_pool_2 = fluid.nets.simple_img_conv_pool(
input=conv_pool_1,
filter_size=5,
num_filters=50,
pool_size=2,
pool_stride=2,
act="relu")
# 以softmax为激活函数的全连接输出层,输出层的大小必须为数字的个数10
prediction = fluid.layers.fc(input=conv_pool_2, size=10, act='softmax')
return prediction
3.设置训练程序 train_program:
def train_program():
"""
配置train_program
Return:
predict -- 分类的结果
avg_cost -- 平均损失
acc -- 分类的准确率
"""
# 标签层,名称为label,对应输入图片的类别标签
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
# predict = softmax_regression() # 取消注释将使用 Softmax回归
# predict = multilayer_perceptron() # 取消注释将使用 多层感知器
predict = convolutional_neural_network() # 取消注释将使用 LeNet5卷积神经网络
# 使用类交叉熵函数计算predict和label之间的损失函数
cost = fluid.layers.cross_entropy(input=predict, label=label)
# 计算平均损失
avg_cost = fluid.layers.mean(cost)
# 计算分类准确率
acc = fluid.layers.accuracy(input=predict, label=label)
return predict, [avg_cost, acc]
4.创建优化器:
Adam就是一种常用的、效果良好的自适应学习率调整优化算法。learning_rate是学习率,它的大小关系到网络的训练收敛速度。
def optimizer_program():
return fluid.optimizer.Adam(learning_rate=0.001)
5.读取数据集:
paddle.dataset.mnist.train()和paddle.dataset.mnist.test()分别加载训练和测试数据集。这两个函数各自返回一个reader。PaddlePaddle中的reader是一个Python函数,每次调用的时候返回一个Python yield generator。
代码中的shuffle是一个reader decorator,它接受一个reader A,返回另一个reader B。reader B 每次读入buffer_size条训练数据到一个buffer里,然后随机打乱其顺序,并且逐条输出。
batch是一个特殊的decorator,它的输入是一个reader,输出是一个batched reader。在PaddlePaddle里,一个reader每次yield一条训练数据,而一个batched reader每次yield一个minibatch。
# 一个minibatch中有64个数据
BATCH_SIZE = 64
# 每次读取训练集中的500个数据并随机打乱,传入batched reader中,batched reader 每次 yield 64个数据
train_reader = paddle.batch(
paddle.reader.shuffle(
paddle.dataset.mnist.train(), buf_size=500),
batch_size=BATCH_SIZE)
# 读取测试集的数据,每次 yield 64个数据
test_reader = paddle.batch(
paddle.dataset.mnist.test(), batch_size=BATCH_SIZE)
6.其他配置:
event_handler 用来在训练过程中输出训练结果。
def event_handler(pass_id, batch_id, cost):
# 打印训练的中间结果,训练轮次,batch数,损失函数
print("Pass %d, Batch %d, Cost %f" % (pass_id,batch_id, cost))
from paddle.utils.plot import Ploter
train_prompt = "Train cost"
test_prompt = "Test cost"
cost_ploter = Ploter(train_prompt, test_prompt)
# 将训练过程绘图表示
def event_handler_plot(ploter_title, step, cost):
cost_ploter.append(ploter_title, step, cost)
cost_ploter.plot()
# 该模型运行在单个CPU上
use_cuda = False # 如想使用GPU,请设置为 True
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()
# 调用train_program 获取预测值,损失值,
prediction, [avg_loss, acc] = train_program()
# 输入的原始图像数据,大小为28*28*1
img = fluid.layers.data(name='img', shape=[1, 28, 28], dtype='float32')
# 标签层,名称为label,对应输入图片的类别标签
label = fluid.layers.data(name='label', shape=[1], dtype='int64')
# 告知网络传入的数据分为两部分,第一部分是img值,第二部分是label值
feeder = fluid.DataFeeder(feed_list=[img, label], place=place)
# 选择Adam优化器
optimizer = fluid.optimizer.Adam(learning_rate=0.001)
optimizer.minimize(avg_loss)
设置训练过程的超参:
PASS_NUM = 5 #训练5轮
epochs = [epoch_id for epoch_id in range(PASS_NUM)]
# 将模型参数存储在名为 save_dirname 的文件中
save_dirname = "recognize_digits.inference.model"
def train_test(train_test_program,
train_test_feed, train_test_reader):
# 将分类准确率存储在acc_set中
acc_set = []
# 将平均损失存储在avg_loss_set中
avg_loss_set = []
# 将测试 reader yield 出的每一个数据传入网络中进行训练
for test_data in train_test_reader():
acc_np, avg_loss_np = exe.run(
program=train_test_program,
feed=train_test_feed.feed(test_data),
fetch_list=[acc, avg_loss])
acc_set.append(float(acc_np))
avg_loss_set.append(float(avg_loss_np))
# 获得测试数据上的准确率和损失值
acc_val_mean = numpy.array(acc_set).mean()
avg_loss_val_mean = numpy.array(avg_loss_set).mean()
# 返回平均损失值,平均准确率
return avg_loss_val_mean, acc_val_mean
创建执行器:
exe = fluid.Executor(place)
exe.run(fluid.default_startup_program())
设置 main_program 和 test_program :
main_program = fluid.default_main_program()
test_program = fluid.default_main_program().clone(for_test=True)
7.开始训练:
以上配置完成之后,便可以开始训练:
lists = []
step = 0
for epoch_id in epochs:
for step_id, data in enumerate(train_reader()):
metrics = exe.run(main_program,
feed=feeder.feed(data),
fetch_list=[avg_loss, acc])
if step % 100 == 0: #每训练100次 打印一次log
print("Pass %d, Batch %d, Cost %f" % (step, epoch_id, metrics[0]))
event_handler_plot(train_prompt, step, metrics[0])
step += 1
# 测试每个epoch的分类效果
avg_loss_val, acc_val = train_test(train_test_program=test_program,
train_test_reader=test_reader,
train_test_feed=feeder)
print("Test with Epoch %d, avg_cost: %s, acc: %s" %(epoch_id, avg_loss_val, acc_val))
event_handler_plot(test_prompt, step, metrics[0])
lists.append((epoch_id, avg_loss_val, acc_val))
# 保存训练好的模型参数用于预测
if save_dirname is not None:
fluid.io.save_inference_model(save_dirname,
["img"], [prediction], exe,
model_filename=None,
params_filename=None)
# 选择效果最好的pass
best = sorted(lists, key=lambda list: float(list[1]))[0]
print('Best pass is %s, testing Avgcost is %s' % (best[0], best[1]))
print('The classification accuracy is %.2f%%' % (float(best[2]) * 100))
训练过程是完全自动的,需要花费一点时间才能完成训练。
8.预测阶段:
模型训练完成之后,我们可以使用训练好的模型对手写体数字图片进行分类。
首先,定义一个用来处理输入的图片的函数 load_image(file)。 它会将输入的图片处理成满足分类器输入要求的格式。
def load_image(file):
im = Image.open(file).convert('L')
im = im.resize((28, 28), Image.ANTIALIAS)
im = numpy.array(im).reshape(1, 1, 28, 28).astype(numpy.float32)
im = im / 255.0 * 2.0 - 1.0
return im
cur_dir = os.getcwd()
tensor_img = load_image(cur_dir + '/infer_3.png')
之后,开始预测,并输出结果。
我们通过load_inference_model来设置预测用的网络结构。
inference_scope = fluid.core.Scope()
with fluid.scope_guard(inference_scope):
# 使用 fluid.io.load_inference_model 获取 inference program desc,
# feed_target_names 用于指定需要传入网络的变量名
# fetch_targets 指定希望从网络中fetch出的变量名
[inference_program, feed_target_names,
fetch_targets] = fluid.io.load_inference_model(
save_dirname, exe, None, None)
# 将feed构建成字典 {feed_target_name: feed_target_data}
# 结果将包含一个与fetch_targets对应的数据列表
results = exe.run(inference_program,
feed={feed_target_names[0]: tensor_img},
fetch_list=fetch_targets)
lab = numpy.argsort(results)
# 打印 infer_3.png 这张图片的预测结果
img=Image.open('./infer_3.png')
plt.imshow(img)
print("Inference result of infer_3.png is: %d" % lab[0][0][-1])
如果顺利,预测结果输出如下: Inference result of infer_3.png is: 3 , 说明我们的网络成功的识别出了这张图片!
3.总结
本篇文章介绍的softmax回归、多层感知机和卷积神经网络是最基础的深度学习模型,许多复杂的神经网络便是从其中衍生出来,因此这几个模型对之后的学习大有裨益。
同时,我们也观察到:从最简单的softmax回归到稍复杂的卷积神经网络,在MNIST数据集上的识别准确率有了大幅度的提升。
此外,本文还介绍了PaddlePaddle模型搭建的基本流程,从数据集的加载、网络层的构建,到最后的训练和预测。对这个流程熟悉以后,大家就可以用自己的数据,定义自己的网络模型,并完成自己的训练和预测任务了。
原文链接地址:https://developer.baidu.com/topic/show/290685
来源:oschina
链接:https://my.oschina.net/u/4299156/blog/4348213