作者:欧新宇(Xinyu OU)
当前版本:Release v1.1
开发平台:Paddle 2.5.2
运行环境:Intel Core i7-7700K CPU 4.2GHz, nVidia GeForce GTX 1080 Ti
本教案所涉及的数据集仅用于教学和交流使用,请勿用作商用。
最后更新:2022年9月17日
Mnist手写数字识别数据集是一个包含大量手写数字图片的公开数据集,常被用作计算机视觉和机器学习领域的基准测试。而LeNet作为早期卷积神经网络(Convolutional Neural Network, CNN)的代表之一,其结构简单且性能优越,是初学者理解CNN工作原理的理想模型。在本项目中,我们将从数据预处理开始,详细介绍Mnist数据集的加载、标准化和划分(训练集、验证集和测试集)。随后,我们将构建基于LeNet的神经网络模型,包括卷积层、池化层、全连接层以及激活函数等关键组件。在模型训练阶段,我们将使用适当的优化算法(如梯度下降)和损失函数(如交叉熵损失)来指导模型参数的更新,以最小化预测误差。最后,我们将对训练好的模型进行评估,并在测试集上验证其性能。通过本项目,读者将能够掌握神经网络的基本构建原理、训练过程以及评估方法。同时,通过实际操作,读者将能够加深对神经网络在图像识别领域应用的理解,为后续更复杂的计算机视觉任务打下坚实的基础。
实验难度:简单
实验摘要:使用Paddle内置工具包实现MNIST数据集的下载,并且对下载好的数据集进行可视化分析
实验目标:
实验建议:
MNIST手写数字识别是深度学习的Hello World
,它可以用来实现对0-9的10个类别的手写数字进行分类。该任务的配套数据集包含60000张训练图片和10000张测试图片,每个图片都对应于0-9中的一个数字,图片的分辨率是28×28的像素矩阵。下面给出部分图像的示例。
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
import paddle
paddle.disable_static()
数据预处理是深度学习工程应用中非常重要的一个环节,在计算机视觉任务中,它通过对图像样本进行多种变换可以实现训练样本规模和多样性的同时提升,从而提高模型的泛化能力。在paddle.vision.transforms中,飞桨也内置了如翻转、裁剪、调节亮度、去均值和方差等操作。在本任务中,我们会在初始化MNIST时,通过tansform字段实现Normalize变化的传入,Normalize实现就是零均值,1方差的图像归一化。图像归一化可以有效加快模型训练的收敛速度。
import paddle.vision.transforms as T
transform = T.Compose([T.Normalize(mean=[127.5], std=[127.5], data_format='CHW')])
在paddle.vision.datasets库中,Paddle内置了若干常见的计算机视觉数据集,包括MNIST, Cifar10, Cifar100, FashionMNIST, Pascal VOC2012等。在本项目中,我们可以通过这个内置库实现MNIST数据集的加载。其中,训练集mode='train'
用于训练模型,测试集mode='test'
用于评估模型,这也是数据集最常见的使用方法之一。不过,这里的之一
深有意味,我们后面会详细介绍在实际工程中,我们最好能将数据集划分训练集、验证集和测试集三个部分;而非仅仅只是训练集和测试集两部分。
# 从paddle.vision载入内置Mnist库,并使用transform类进行归一化预处理
train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=transform)
test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform)
print('数据载入完毕!')
数据载入完毕!
该步骤是非必须的必要步骤
。非必须指它并不是训练和展示数据所必须的步骤;必要步骤是因为观察数据集有利于我们更好地理解数据,并选择合适的方法进行数据预处理。
# Q3:样本测试
# 1. 显示训练集和测试集的样本数
# 2. 随机(或指定)一个id,并从训练集中读取该ID的样本,并进行可视化观察,注意Mnist是灰度图
id = 5
data, label = train_dataset[id][0],train_dataset[id][1]
data = data.reshape([28,28])
plt.figure(figsize=(3,3))
plt.imshow(data, cmap='gray') # plt.cm.binary
print('训练集:{}张;测试集:{}张。\n 其中,第{}张测试图片的标签为:{}'.format(len(train_dataset), len(test_dataset), id, label))
训练集:60000张;测试集:10000张。
其中,第5张测试图片的标签为:[2]
实验难度:中等
实验摘要:使用Paddle内置工具包实现MNIST数据集的下载,并且对下载好的数据集进行可视化分析
实验目标:
paddle.models
库实现基本模型的载入和定义;paddle.summary()
方法实现模型结构的可视化,并初步理解各部分的含义;model.save()
方法将训练好的模型进行保存。实验建议:
Paddle模型的网络配置一般包含三种方式,一是使用Paddle内置的模型,二是自定义组网,三是通过迁移学习直接载入预训练模型。在本项目中,我们使用Paddle内置的模型LeNet。
LeNet5是一个包含三个卷积层、两个最大池化层和两个全连接层的简单卷积神经网络,基本接入如下图所示
在使用Paddle内置模型进行模型设置时,我们只需要使用 paddle.vision.modles
库就可以了,它内置了包括AlexNet,VGG,ResNet,MobileNet,DenseNet等诸多经典模型。在该模型库中,我们可以通过 num_classes
字段来定义任务的类别数。此处,我们设置 num_classes=10
来实现0-9这10个数字的分类。
# 1. 初始化模型
LeNet = paddle.vision.models.LeNet(num_classes=10)
# 2. 可视化模型的配置信息
display(LeNet)
LeNet(
(features): Sequential(
(0): Conv2D(1, 6, kernel_size=[3, 3], padding=1, data_format=NCHW)
(1): ReLU()
(2): MaxPool2D(kernel_size=2, stride=2, padding=0)
(3): Conv2D(6, 16, kernel_size=[5, 5], data_format=NCHW)
(4): ReLU()
(5): MaxPool2D(kernel_size=2, stride=2, padding=0)
)
(fc): Sequential(
(0): Linear(in_features=400, out_features=120, dtype=float32)
(1): Linear(in_features=120, out_features=84, dtype=float32)
(2): Linear(in_features=84, out_features=10, dtype=float32)
)
)
# 1. 可视化模型的结构和参数
paddle.summary(LeNet, (1,1,28,28))
# 2. 模型测试
Xin = paddle.rand([3, 1, 28, 28]) # 生成随机数,形态为[NCHW]
Xout = LeNet(Xin) # 执行前向传输产生输出
print('输出的形态为:{}'.format(Xout.shape)) # 输出结果形态
---------------------------------------------------------------------------
Layer (type) Input Shape Output Shape Param #
===========================================================================
Conv2D-1 [[1, 1, 28, 28]] [1, 6, 28, 28] 60
ReLU-1 [[1, 6, 28, 28]] [1, 6, 28, 28] 0
MaxPool2D-1 [[1, 6, 28, 28]] [1, 6, 14, 14] 0
Conv2D-2 [[1, 6, 14, 14]] [1, 16, 10, 10] 2,416
ReLU-2 [[1, 16, 10, 10]] [1, 16, 10, 10] 0
MaxPool2D-2 [[1, 16, 10, 10]] [1, 16, 5, 5] 0
Linear-1 [[1, 400]] [1, 120] 48,120
Linear-2 [[1, 120]] [1, 84] 10,164
Linear-3 [[1, 84]] [1, 10] 850
===========================================================================
Total params: 61,610
Trainable params: 61,610
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.11
Params size (MB): 0.24
Estimated Total Size (MB): 0.35
---------------------------------------------------------------------------
输出的形态为:[3, 10]
从Paddle2.0开始,Paddle提供了不少快捷的模型配置和训练方法。基本的训练步骤主要可以分为以下几步:
在本例中,我们将使用最常见的交叉熵损失来进行分类,并使用Adam优化算法和Accuracy精度指标来对模型进行评价。
# 1. 实例化模型
model = paddle.Model(LeNet)
# 2. 配置模型训练参数
model.prepare(
paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters()), # 优化函数Adam
paddle.nn.CrossEntropyLoss(), # 交叉熵损失函数
paddle.metric.Accuracy() # 精度评价指标
)
在训练过程中,主要超参数包括指定训练用的数据源train_dataset
、验证用数据源test_dataset
、训练论述epochs
、训练的批大小batch_size
。超参数verbose
用于控制训练过程中是否显示日志。
# 训练模型
# device = paddle.set_device('gpu') # =cpu|gpu,程序通常会根据硬件情况,进行自动选择
model.fit(train_dataset, # 训练集
# test_dataset, # 测试集,设置后则在每个周期后进行验证,若不指定则不在验证集上进行验证
epochs=5, # 训练周期数
batch_size=64, # 训练的批次大小
verbose=1) # 是否显示训练日志
The loss value printed in the log is the current step, and the metric is the average value of previous steps.
Epoch 1/5
step 10/938 [..............................] - loss: 2.1836 - acc: 0.1328 - ETA: 21s - 23ms/step
step 938/938 [==============================] - loss: 0.0501 - acc: 0.9253 - 11ms/step
Epoch 2/5
step 938/938 [==============================] - loss: 0.0487 - acc: 0.9736 - 11ms/step
Epoch 3/5
step 938/938 [==============================] - loss: 0.0150 - acc: 0.9803 - 11ms/step
Epoch 4/5
step 938/938 [==============================] - loss: 0.0028 - acc: 0.9823 - 11ms/step
Epoch 5/5
step 938/938 [==============================] - loss: 0.0061 - acc: 0.9844 - 11ms/step
从训练过程的打印日志中,可观察到损失函数值 loss 逐渐变小,精度 acc 逐渐上升的趋势,反映出不错的训练效果。在后面的项目中,我们将设计一个绘图函数用于可视化训练过程的各种结果。
将模型保存到硬盘后,再下一次使用模型进行预测时,就无需再进行训练,可以直接进行使用。模型的保存一般分为两种,第一种,仅用于推理使用,保存的模型只包含前向传输时所设计的参数,此时超参数 training=False
;另一种模式的保存,还包含模型训练的优化器的参数,可以用于后续的恢复训练,此时超参数 training=True
。
# 1. 定义根目录
# root_path = 'D://Workspace//DeepLearning//Websitev2' # Windows
root_path = '/home/aistudio' # AI Studio
# 2. 定义项目目录
# projcet_path = os.path.join(root_path, 'Data', 'Projects', 'Project005LeNetMNIST') # Windows
projcet_path = os.path.join(root_path, 'work') # AI Studio
# 3. 模型保存
model.save(os.path.join(projcet_path, 'final_model'), training=True)
离线测试与在线测试最主要的区别是离线测试使用的是测试数据集 test_dataset
,而在线测试一般使用的是验证数据集 eval_dataset
。所以,我们会在训练时将训练好的模型保存到硬盘上,然后再通过 model.load()
命令重新将数据集进行载入并在测试数据集上进行验证。(当然,直接在训练好的模型上进行测试也可以)模型的离线测试可以调用 paddle.Model.evaluate
库实现评估评估完成后将输出模型在测试集上的损失函数值 loss 和精度 acc。
# 1. 载入模型
model.load(os.path.join(projcet_path, 'final_model'))
# 2. 运行评估函数在测试集上进行性能评估
model.evaluate(test_dataset, batch_size=64, verbose=1)
Eval begin...
step 157/157 [==============================] - loss: 9.3060e-05 - acc: 0.9815 - 8ms/step
Eval samples: 10000
{'loss': [9.3059774e-05], 'acc': 0.9815}
从结果可以看到,初步训练得到的模型精度在98%附近,在逐渐熟悉深度学习模型开发和训练技巧后,可以通过调整其中的训练参数来进一步提升模型的精度。
实验难度:中等
实验摘要:使用经过训练后保存的Paddle模型来完成对给定数据的推理与预测。
实验目标:
实验建议:
28×28
,并且统一使用图像归一化将所有图像的均值置为0,方差置为1。import os
import numpy as np
import cv2
import random
import matplotlib.pyplot as plt
import paddle
import paddle.vision.transforms as T
# 1. 定义根目录
# root_path = 'D://Workspace//DeepLearning//Websitev2' # Windows
root_path = '/home/aistudio' # AI Studio
# 2. 定义项目目录
# projcet_path = os.path.join(root_path, 'Data', 'Projects', 'Project005LeNetMNIST') # windows
projcet_path = os.path.join(root_path, 'work') # AI Studio
在本项目中我们将使用两种输入数据进行测试,一种是从给定的测试数据集中随机选择一幅图像进行测试,另一种是从硬盘上读取一张图像来进行测试。对于所有载入的图像都需要做一些预处理,主要包括以下几个方面:
numpy.array
格式, 32bit浮点数据类型(float32)transform = T.Compose([T.Normalize(mean=[127.5], std=[127.5])]) # 将数据归一化到[0~1]之间,也可以归一化为[-1,1]之间, img = img/255.0*2.0-1.0
TestMode = 'test_dataset' # test_dataset:测试集|Images:硬盘上的单独数据
if TestMode == 'test_dataset':
id = random.randrange(0,10000,1)
test_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform)
data, label = test_dataset[id][0],test_dataset[id][1]
data = data.reshape([28,28]) # resize image with high-quality 图像大小为28*28
img = data.reshape([1,1,28,28]).astype('float32')
print('第{}个测试数据的标签为:{}'.format(id, label))
elif TestMode == 'Images':
img_path = 'D:\\WorkSpace\\DeepLearning\\Website\\Data\\Projects\\Project004LeNetMNIST\\Images\\Single6.png'
data = cv2.imread(img_path, 0) # cv2.imread(path, 0|1),其中0表示灰度模式,1表示彩色模式
data = cv2.resize(data, (28, 28)) # resize image with high-quality 图像大小为28*28
img = np.array(data).reshape(1,1,28,28).astype('float32') # 返回新形状的数组,把它变成一个 numpy 数组以匹配数据馈送格式。 # cv2.imread(path, 0|1),其中0表示灰度模式,1表示彩色模式
# img = img/255.0
img = transform(img)
plt.figure(figsize=(3,3))
plt.imshow(data, cmap='gray') # plt.cm.binary
第8503个测试数据的标签为:[9]
<matplotlib.image.AxesImage at 0x255cfbfbbe0>
# 1.模型载入
LeNet = paddle.vision.models.LeNet(num_classes=10) # 初始化LeNet模型
model = paddle.Model(LeNet) # 用Model封装模型,并进行实例化
model.prepare() # 配置模型推理参数,此处无需设置额外参数,包括优化器,损失函数等
model.load(os.path.join(projcet_path, 'final_model'))
# 2.使用预先训练好的模型进行推理和预测
img = paddle.to_tensor(img) # 将numpy矩阵转换为paddle tensor
imgIn = paddle.unsqueeze(img, axis=0) # 将tensor扩展为5D形态,以适配paddle的输入要求
prob = model.predict(imgIn, verbose=0) # 使用model.predict()接口进行推理
pred = prob[0][0].argmax() # 输出推理结果中最大概率的标签
print('预测结果为:{}'.format(pred)) # 打印预测结果
预测结果为:9
实验目的:学会用一些现有单独知识做一些拓展练习,特别是对接一些能落地的项目
实验建议:手写数字识别对输入样本有一定的要求,有兴趣的同学可以进行多种尝试,如何生成样本,如果对任意样本进行预处理等。
import os
import cv2
import numpy as np
import matplotlib.pyplot as plt
import random
import paddle
import paddle.vision.transforms as T
#########################全局参数定义#########################
# 1. 定义根目录
# root_path = 'D://Workspace//DeepLearning//Websitev2' # Windows
root_path = '/home/aistudio' # AI Studio
# 2. 定义项目目录
# projcet_path = os.path.join(root_path, 'Data', 'Projects', 'Project005LeNetMNIST') # Windows
projcet_path = os.path.join(root_path, 'work') # AI Studio
#########################模型训练#########################
# 1. 数据准备
# 1.1 定义数据预处理
transform = T.Compose([T.Normalize(mean=[127.5], std=[127.5], data_format='CHW')])
img_path = os.path.join(projcet_path, 'Images', 'Multi00.png')
data = cv2.imread(img_path, 0)
plt.imshow(data, cmap='binary')
# 3.模型载入和超参数配置
LeNet = paddle.vision.models.LeNet(num_classes=10) # 初始化LeNet模型
model = paddle.Model(LeNet) # 用Model封装模型,并进行实例化
model.prepare() # 配置模型推理参数,此处无需设置额外参数,包括优化器,损失函数等
model.load(os.path.join(projcet_path, 'final_model'))
result = []
for i in range(0,6):
x = data[:, i*150:(i+1)*150]
img = cv2.resize(x, (28, 28)) # resize image with high-quality 图像大小为28*28
img = np.array(img).reshape(1,1,28,28).astype('float32') # 返回新形状的数组,把它变成一个 numpy 数组以匹配数据馈送格式。 # cv2.imread(path, 0|1),其中0表示灰度模式,1表示彩色模式
img = transform(img)
img = paddle.to_tensor(img) # 将numpy矩阵转换为paddle tensor
imgIn = paddle.unsqueeze(img, axis=0) # 将tensor扩展为5D形态,以适配paddle的输入要求
prob = model.predict(imgIn, verbose=0) # 使用model.predict()接口进行推理
pred = prob[0][0].argmax() # 输出推理结果中最大概率的标签
result.append(pred)
print('预测结果为: {}'.format(result)) # 打印预测结果
预测结果为: [4, 1, 8, 7, 6, 3]