实验:手写数字识别(卷积神经网络)
2024-05-27 10:12:21

实验:手写数字识别(卷积神经网络)

http://cnn.yao38.com/

相关概念

卷积神经网络

定义

杨立昆(法语:Yann Le Cun,英语:Yann LeCun,原中文译名扬·勒丘恩,1960 年 7 月 8 日—)是一名法国计算机科学家,2018 年图灵奖得主,他在机器学习、计算机视觉、移动机器人和计算神经科学等领域都有很多贡献。他最著名的工作是在光学字符识别和计算机视觉上使用卷积神经网络,他也被称为卷积网络之父。

img

卷积神经网络(Convolutional Neural Network)简称 CNN,CNN 是一种常用的神经网络,它是一种特殊类型的人工神经网络,受人类视觉神经系统觉启发,广泛应用于计算机视觉任务中,特别是图像和视频处理。

CNN 的核心思想是通过卷积层(Convolutional Layer)和池化层(Pooling Layer)来自动学习图像中的特征。卷积层使用卷积操作对输入图像进行特征提取,通过应用一系列的卷积核(也称为滤波器)来检测不同的特征,例如边缘、纹理或形状。池化层则通过降采样操作减少特征图的维度,保留重要的特征信息并减少计算量。

CNN 通常由多个卷积层池化层交替堆叠构成,最后通过全连接层(Fully Connected Layer)将提取的特征映射到最终的分类或预测输出。在训练过程中,CNN 使用反向传播算法来优化网络参数,使网络能够更好地适应训练数据并泛化到新的未见数据。

CNN 在计算机视觉中广泛应用,可以用于图像分类、目标检测、图像分割、人脸识别等任务。它的设计和结构使其能够有效地处理图像数据的局部关联性和平移不变性,并且具有良好的特征提取和表示能力,因此在许多视觉任务中表现出色。

CNN 通过自动提取特征、参数共享、平移不变性和处理高维数据的效率等方面的优势,使得它在计算机视觉任务中成为一种强大而有效的工具。它能够更好地适应图像数据的特点,提取有用的特征,并实现更准确和高效的视觉分析和处理。

适用于其输入本身具有二维结构(如图片)的应用。

放大镜

像所有神经网络一样,CNN 由多个层组成。

1688723627826

对于类似常见的图片分类问题,输出层会返回网络对每个可能的类(企鹅、汽车或树)的预测可信度。
CNN 包含丰富多样的层,其中大部分的层是将输入图像转换成一组特征,最后几层使用这些特征执行分类。

人工神经元

生物神经元是构成人类和其他生物体神经系统的基本单元。它们是神经系统中信息传递和处理的基本单元,通过电化学过程来完成信号的传递。生物神经元在结构和功能上与人工神经元(用于人工智能和深度学习)有一些相似之处,但也存在显著差异。

典型的生物神经元包括以下几个主要部分:

  1. 细胞体(细胞核): 细胞体是神经元的主体部分,包含了细胞核,其中包含了细胞的遗传信息(DNA)。
  2. 树突(Dendrites): 树突是从细胞体伸出的分支,用于接收来自其他神经元的输入信号。树突上有许多突起,可以增加接收信号的表面积。
  3. 轴突(Axon): 轴突是一条长的纤维,负责将神经信号从细胞体传递到其他神经元的树突或者其他细胞。轴突末端可以分为许多小的突起,称为突触末端。
  4. 突触(Synapse): 突触是神经元之间传递信号的连接点。一个神经元的轴突末端与另一个神经元的树突相连接,通过神经递质(神经化学物质)在突触间传递信号。
  5. 神经递质(Neurotransmitters): 神经递质是在突触传递信号时释放的化学物质,它们通过绑定受体在接收神经元的树突上传递信号。
  6. 动作电位(Action Potential): 当神经元受到足够的刺激时,会产生电脉冲,称为动作电位。动作电位通过轴突传递,然后在突触末端释放神经递质来传递信号给其他神经元。

生物神经元通过复杂的电化学信号传递和突触传递来实现信息的处理和传递。神经元之间的连接形成了大脑和神经系统的复杂网络,这些网络负责感知、学习、记忆、控制运动等各种生理和行为功能。

在深度学习中,神经元是构成神经网络的基本组件之一,它是对生物神经元的抽象模型。神经元被用来模拟信息传递和处理的方式,通过神经元的连接和激活状态,神经网络可以完成复杂的学习任务。

深度学习中的神经元包括以下几个核心部分:

  1. 权重(Weights): 每个神经元都有与之相关联的权重,这些权重用于调整输入数据的影响力。权重决定了输入数据在神经元中的重要性,不同权重会导致不同的信号传递和处理。
  2. 激活函数(Activation Function): 激活函数决定了神经元是否激活(输出非零值)。激活函数引入了非线性性质,使得神经网络能够学习复杂的函数关系。常见的激活函数包括 Sigmoid、ReLU(Rectified Linear Unit)、Tanh 等。
  3. 偏置(Bias): 偏置是一个常数,与神经元的权重一起影响神经元的激活状态。它允许神经网络进行平移变换,有助于适应不同的数据分布。
  4. 输入(Inputs): 每个神经元都接收来自其他神经元或外部数据的输入。输入经过权重和偏置的调整,然后通过激活函数产生输出。

在深度学习模型中,神经元以层的形式组织,不同层之间的神经元之间通过权重连接。输入层接受原始数据,隐藏层通过逐层处理逐渐提取特征,最后的输出层生成模型的预测结果。通过调整神经元的权重和偏置,深度学习模型可以通过大量的数据进行训练,从而学习数据的特征和模式。

实验

1)数据简介

数字识别是计算机从纸质文档、照片或其他来源接收、理解并识别可读的数字的能力,目前比较受关注的是手写数字识别。手写数字识别是一个典型的图像分类问题,已经被广泛应用于汇款单号识别、手写邮政编码识别等领域,大大缩短了业务处理时间,提升了工作效率和质量。

在处理如 图 1 所示的手写邮政编码的简单图像分类任务时,可以使用基于 MNIST 数据集的手写数字识别模型。MNIST 是深度学习领域标准、易用的成熟数据集,包含 50 000 条训练样本和 10 000 条测试样本。

image-20230610015701476

  • 任务输入:一系列手写数字图片,其中每张图片都是 28x28 的单通道像素矩阵。
  • 任务输出:经过了大小归一化和居中处理,输出对应的 0~9 的数字标签。

手写数字识别的模型是深度学习中相对简单的模型,非常适用初学者。正如学习编程时,我们输入的第一个程序是打印“Hello World!”一样。 在飞桨的入门教程中,我们选取了手写数字识别模型作为启蒙教材,以便更好的帮助读者快速掌握飞桨平台的使用。

飞桨提供了多个封装好的数据集 API,涵盖计算机视觉、自然语言处理、推荐系统等多个领域,帮助读者快速完成深度学习任务。如在手写数字识别任务中,通过paddle.vision.datasets.MNIST可以直接获取处理好的 MNIST 训练集、测试集。

MNIST 是一个经典的手写数字数据集,广泛用于测试和验证机器学习和深度学习模型的性能。它是深度学习领域中最常见的基准数据集之一。

MNIST 数据集包含了大量的手写数字图像,每个图像都是单个 0 到 9 之间的数字。图像的尺寸固定为 28x28 像素,通常以灰度图像的形式呈现。这意味着每个图像都由一个二维数组表示,数组中的每个元素代表图像上对应位置的像素值。

Applsci 09 03169 g001

MNIST 数据集总共包含 60000 个训练样本和 10000 个测试样本。训练集用于训练模型,测试集用于评估模型在未见过数据上的性能。这个数据集的标签已经预先确定,因此我们知道每个图像对应的真实数字标签。

MNIST 数据集在计算机视觉和深度学习社区中被广泛使用,特别是在图像分类任务中。许多研究论文和教程都使用 MNIST 数据集来演示和比较不同的算法和模型,因为它的相对简单性和可用性使得许多人都能轻松地开始尝试机器学习和深度学习技术。

MNIST - Machine Learning Datasets

2)查看数据集中图片和标签

1
2
3
4
import paddle

## 设置数据读取器,API自动读取MNIST数据训练集
train_dataset = paddle.vision.datasets.MNIST(mode='train')

通过如下代码读取任意一个数据内容,观察打印结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import numpy as np

train_data0 = np.array(train_dataset[0][0])
train_label_0 = np.array(train_dataset[0][1])

## 显示第一batch的第一个图像
import matplotlib.pyplot as plt
plt.figure("Image") ## 图像窗口名称
plt.figure(figsize=(2,2))
plt.imshow(train_data0, cmap=plt.cm.binary)
plt.axis('on') ## 关掉坐标轴为 off
plt.title('image') ## 图像题目
plt.show()

print("图像数据形状和对应数据为:", train_data0.shape)
print("图像标签形状和对应数据为:", train_label_0.shape, train_label_0)
print("\n打印第一个batch的第一个图像,对应标签数字为{}".format(train_label_0))

3)加载数据集

1
2
3
4
5
6
7
8
9
10
11
12
13
from paddle.vision.transforms import Normalize

## 归一化处理
## mean=[127.5]: 均值127.5
## std: 标准方差为127.5
## data_format: 数据模式: C:通道数, H:高度, W: 宽度
transform = Normalize(mean=[127.5], std=[127.5], data_format='CHW')

## mode='train' 训练集 transform=transform: 代表归一化的参数处理
train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=transform)

## mode='test' 验证集 transform=transform: 代表归一化的参数处理
val_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform)

4)训练模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
## 导入模块
import paddle
from paddle.vision.transforms import ToTensor

## 加载数据集
train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=ToTensor())
val_dataset = paddle.vision.datasets.MNIST(mode='test', transform=ToTensor())

## 定义卷积神经网络模型
model = paddle.nn.Sequential(
## 第一个卷积核 输入通道数 1, 输出通道数32 卷积核大小5*5 步长为1 , 填充为2, 输出的时候图像大小不变
## 其中 这一层的参数个数: in_channels*out_channels*kernel_size*kernel_size+out_channels=832
paddle.nn.Conv2D(in_channels=1, out_channels=32, kernel_size=5, stride=1, padding=2),
## 最大池化, 图像缩放为原来大小的一般 kernel_size:2 使用2*2的卷积核, stride=2 步长为2
## 池化的时候 不需要特征值
paddle.nn.MaxPool2D(kernel_size=2, stride=2),
## 激活函数, 引入非线性编号
paddle.nn.ReLU(),
## 第二个卷积核 输入通道:32 out_channels=64 输出通道64 kernel_size=5 卷积核大小为5, padding:填充为2
paddle.nn.Conv2D(in_channels=32, out_channels=64, kernel_size=5, stride=1, padding=2),
## 最大池化技术
paddle.nn.MaxPool2D(kernel_size=2, stride=2),
paddle.nn.ReLU(),
## 压平为1维数组
paddle.nn.Flatten(),
## 进行线性激活函数变化
paddle.nn.Linear(in_features=64*7*7, out_features=10),
## 进行分类任务处理
paddle.nn.Softmax(axis=-1)
)

## 使用 paddle.Model 包装模型
model = paddle.Model(model)

## 模型配置
## 优化器配置
optimizer = paddle.optimizer.Adam(parameters=model.parameters(), learning_rate=0.001)
## 交叉熵损失
loss = paddle.nn.CrossEntropyLoss()
## 监控指标 准确率和损失
metrics = paddle.metric.Accuracy()
model.prepare(optimizer=optimizer, loss=loss, metrics=metrics)

## 训练模型
model.fit(train_dataset, ## 训练数据集
val_dataset, ## 验证数据集
epochs=5, ## 训练轮次
batch_size=64, ## 训练批次
verbose=1) ## 显示详细信息

## 保存模型
model.save("mnist_cnn_model", training=True)

5)加载模型进行推理

动态图推理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
import random

import paddle
import numpy as np
from paddle.vision.transforms import ToTensor

## 定义卷积神经网络模型
mynetwork = paddle.nn.Sequential(
## 第一个卷积核 输入通道数 1, 输出通道数32 卷积核大小5*5 步长为1 , 填充为2, 输出的时候图像大小不变
## 其中 这一层的参数个数: in_channels*out_channels*kernel_size*kernel_size+out_channels=832
paddle.nn.Conv2D(in_channels=1, out_channels=32, kernel_size=5, stride=1, padding=2),
## 最大池化, 图像缩放为原来大小的一般 kernel_size:2 使用2*2的卷积核, stride=2 步长为2
## 池化的时候 不需要特征值
paddle.nn.MaxPool2D(kernel_size=2, stride=2),
## 激活函数, 引入非线性编号
paddle.nn.ReLU(),
## 第二个卷积核 输入通道:32 out_channels=64 输出通道64 kernel_size=5 卷积核大小为5, padding:填充为2
paddle.nn.Conv2D(in_channels=32, out_channels=64, kernel_size=5, stride=1, padding=2),
## 最大池化技术
paddle.nn.MaxPool2D(kernel_size=2, stride=2),
paddle.nn.ReLU(),
## 压平为1维数组
paddle.nn.Flatten(),
## 进行线性激活函数变化
paddle.nn.Linear(in_features=64*7*7, out_features=10),
## 进行分类任务处理
paddle.nn.Softmax(axis=-1)
)

## 使用 paddle.Model 包装模型
model = paddle.Model(mynetwork)

## 加载权重
model.load('./mnist_cnn_model')

## 模型配置
## 优化器配置
optimizer = paddle.optimizer.Adam(parameters=model.parameters(), learning_rate=0.001)
## 交叉熵损失
loss = paddle.nn.CrossEntropyLoss()
## 监控指标 准确率和损失
metrics = paddle.metric.Accuracy()
model.prepare(optimizer=optimizer, loss=loss, metrics=metrics)

## 加载数据集
val_dataset = paddle.vision.datasets.MNIST(mode='test', transform=ToTensor())

## 使用模型进行预测
images, labels = val_dataset[random.randint(0, 10000)]
images = images[np.newaxis, np.newaxis, ...] ## 添加一个批量维度
images = paddle.to_tensor(images) ## 转换为PaddlePaddle张量
## print(images)

outputs = model.predict(images) ## 推理
predicted = np.argmax(outputs, axis=-1)[0] ## 获取预测类别

print(f"Predicted class: {predicted}, Real class: {labels}")

静态图推理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import paddle
import numpy as np
from paddle.vision.transforms import Normalize
import matplotlib.pyplot as plt

## 加载训练集
train_dataset = paddle.vision.datasets.MNIST(mode='train')

## 验证集
val_dataset = paddle.vision.datasets.MNIST(mode="test")

train_data0 = np.array(train_dataset[1][0])
train_data1 = np.array(train_dataset[1][1])

plt.figure(figsize=(2, 2))
plt.imshow(train_data0, cmap=plt.cm.binary)
plt.show()

加载数据集

1
2
3
4
5
6
7
transfrom = Normalize(mean=[127.5], std=[127.5], data_format='CHW')

## 加载训练集
train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=transfrom)

## 验证集
val_dataset = paddle.vision.datasets.MNIST(mode="test", transform=transfrom)

加载静态图模型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import paddle

paddle.enable_static()
startup_prog = paddle.paddle.static.default_startup_program()

"""
❗❗❗❗❗❗❗❗❗
该路径是在保存模型的时候导出的路径
注意这里的training是False的时候,导出的是静态图。
model.save("mnist_cnn_model", training=False)
"""
path_prefix = './djdj/digit_recognition_model'

exe = paddle.static.Executor(paddle.CPUPlace())
exe.run(startup_prog)

[inference_program, feed_target_names, fetch_targets] = (
paddle.static.load_inference_model(path_prefix, exe))

rowitem = val_dataset[1]
tensor_img = rowitem[0][np.newaxis]
labels = rowitem[1]

results = exe.run(inference_program,
feed={feed_target_names[0]: tensor_img},
fetch_list=fetch_targets)

predicted = np.argmax(results, axis=-1)[0] ## 获取预测类别

print(f"Predicted class: {predicted}, Real class: {labels}")