【深度学习项目二】卷积神经网络LeNet实现minst数字识别

本文主要是介绍【深度学习项目二】卷积神经网络LeNet实现minst数字识别,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

相关文章:
【深度学习项目一】全连接神经网络实现mnist数字识别
【深度学习项目二】卷积神经网络LeNet实现minst数字识别
【深度学习项目三】ResNet50多分类任务【十二生肖分类】
『深度学习项目四』基于ResNet101人脸特征点检测
项目链接:https://aistudio.baidu.com/aistudio/projectdetail/1928935

1.分类任务介绍

二分类、多分类、多标签

2.图像分类目标和原理

特征会遇到一些干扰:视角变化、形变、遮挡、背景干扰等

神经网络发展过程:

import paddle
import numpy as np
import matplotlib.pyplot as plt

3. 数据准备

继续应用框架中封装好的手写数字识别数据集。

3.1 数据集加载和预处理

# 数据预处理
import paddle.vision.transforms as T
#调用API实现训练时对图像的旋转、形变等,增强样本量,增强模型泛化能力# 数据预处理
transform = T.Normalize(mean=[127.5], std=[127.5])# 训练数据集
train_dataset = paddle.vision.datasets.MNIST(mode='train', transform=transform)# 验证数据集
eval_dataset = paddle.vision.datasets.MNIST(mode='test', transform=transform)print('训练样本量:{},测试样本量:{}'.format(len(train_dataset), len(eval_dataset)))

训练样本量:60000,测试样本量:10000

3.2 数据查看

print('图片:')
print(type(train_dataset[0][0]))
print(train_dataset[0][0])
print('标签:')
print(type(train_dataset[0][1]))
print(train_dataset[0][1])# 可视化展示
plt.figure()
plt.imshow(train_dataset[0][0].reshape([28,28]), cmap=plt.cm.binary)
plt.show()

4 模型选择和开发

选用LeNet-5网络结构。

LeNet-5模型源于论文“LeCun Y, Bottou L, Bengio Y, et al. Gradient-based learning applied to document recognition[J]. Proceedings of the IEEE, 1998, 86(11): 2278-2324.”,

论文地址:https://ieeexplore.ieee.org/document/726791

4.1 网络结构定义

每个阶段用到的Layer-网络结构依次是:卷积层-池化层-卷积层-池化层-全连接层

4.2卷积操作

4.2.1 单通道卷积

输入单通道图像3x3和2x2卷积核相乘,2x2卷积核的值是通过训练学习的

4.2.2 多通道卷积

4.2.3 多通道输出

4.2.4 Batch

[N, C, H, W]

卷积核信息不变,卷积操作会多一定的倍数(和样本数有关)。

4.3 池化层

(算力有限才做的)
池化是使用某一位置的相邻输出的总体统计特征代替网络在该位置的输出,其好处是当输入数据做出少量平移时,经过池化函数后的大多数输出还能保持不变。
比如:当识别一张图像是否是人脸时,我们需要知道人脸左边有一只眼睛,右边也有一只眼睛,而不需要知道眼睛的精确位置,这时候通过池化某一片区域的像素点来得到总体统计特征会显得很有用。由于池化之后特征图会变得更小,如果后面连接的是全连接层,能有效的减小神经元的个数,节省存储空间并提高计算效率。

4.3.1池化的作用

池化层是特征选择和信息过滤的过程,过程中会损失一部分信息,但是会同时会减少参数和计算量,在模型效果和计算性能之间寻找平衡,随着运算速度的不断提高,慢慢可能会有一些设计上的变化,现在有些网络已经开始少用或者不用池化层。

4.3.2 Avg Pooling 平均池化

对邻域内特征点求平均

  • 优缺点:能很好的保留背景,但容易使得图片变模糊
  • 正向传播:邻域内取平均
  • 反向传播:特征值根据领域大小被平均,然后传给每个索引位置

4.3.3 Max Pooling 最大池化

对邻域内特征点取最大,更好保存特征

  • 优缺点:能很好的保留一些关键的纹理特征,现在更多的再使用Max Pooling而很少用Avg Pooling
  • 正向传播:取邻域内最大,并记住最大值的索引位置,以方便反向传播
  • 反向传播:将特征值填充到正向传播中,值最大的索引位置,其他位置补0

4.3.4公式

4.4. Padding

角落边缘的像素,只被一个过滤器输出所使用,因为它位于这个3×3的区域的一角。但如果是在中间的像素点,就会有许多3×3的区域与之重叠。
所以那些在角落或者边缘区域的像素点在输出中采用较少,意味着你丢掉了图像边缘位置的许多信息

  • 解决办法就是填充操作,在原图像外围以0进行填充,在不影响特征提取的同时,增加了对边缘信息的特征提取。

  • 另外一个好处是,我们在做卷积操作时,每经过一次卷积我们的输入图像大小就会变小,最后经过多次卷积可能我们的图像会变得特别小,我们不希望图像变小的话就可以通过填充操作。

4.5. 激活函数

参考论文:https://arxiv.org/pdf/1811.03378.pdf

4.5.1 Sigmoid

4.5.2 Tanh

Sigmoid和Tanh激活函数有共同的缺点:即在z很大或很小时,梯度几乎为零,因此使用梯度下降优化算法更新网络很慢

4.5.3 ReLU

Relu目前是选用比较多的激活函数,但是也存在一些缺点,在z小于0时,斜率即导数为0
为了解决这个问题,后来也提出来了Leaky Relu激活函数,不过目前使用的不是特别多。

4.6. Dropout

论文:https://jmlr.org/papers/volume15/srivastava14a/srivastava14a.pdf

当一个复杂的前馈神经网络被训练在小的数据集时,容易造成过拟合。为了防止过拟合,可以通过随机丢弃部分特征节点的方式来减少这个问题发生。

5 网络结构代码实现

5.1 网络结构代码实现1

import paddle.nn as nnnetwork = nn.Sequential(nn.Conv2D(in_channels=1, out_channels=6, kernel_size=5, stride=1, padding=0),  # C1 卷积层#nn.Conv2D(in_channels=1, out_channels=6, 卷积核kernel_size=(5,6), 步长stride=1, padding=0),  nn.Tanh(),nn.AvgPool2D(kernel_size=2, stride=2),  # S2 平局池化层nn.Sigmoid(),   # Sigmoid激活函数;#池化不会改变通道数nn.Conv2D(in_channels=6, out_channels=16, kernel_size=5, stride=1, padding=0),  # C3 卷积层nn.Tanh(),nn.AvgPool2D(kernel_size=2, stride=2),  # S4 平均池化层nn.Sigmoid(),  # Sigmoid激活函数nn.Conv2D(in_channels=16, out_channels=120, kernel_size=5, stride=1, padding=0), # C5 卷积层nn.Tanh(),nn.Flatten(),#拉平,将原来二维变成一维nn.Linear(in_features=120, out_features=84), # F6 全连接层nn.Tanh(),nn.Linear(in_features=84, out_features=10) # OUTPUT 全连接层
)

模型可视化

paddle.summary(network, (1, 1, 32, 32))
Layer (type)       Input Shape          Output Shape         Param #    
===========================================================================Conv2D-1       [[1, 1, 32, 32]]      [1, 6, 28, 28]          156      Tanh-1        [[1, 6, 28, 28]]      [1, 6, 28, 28]           0       AvgPool2D-1     [[1, 6, 28, 28]]      [1, 6, 14, 14]           0       Sigmoid-1      [[1, 6, 14, 14]]      [1, 6, 14, 14]           0       Conv2D-2       [[1, 6, 14, 14]]     [1, 16, 10, 10]         2,416     Tanh-2       [[1, 16, 10, 10]]     [1, 16, 10, 10]           0       AvgPool2D-2    [[1, 16, 10, 10]]      [1, 16, 5, 5]            0       Sigmoid-2      [[1, 16, 5, 5]]       [1, 16, 5, 5]            0       Conv2D-3       [[1, 16, 5, 5]]       [1, 120, 1, 1]        48,120     Tanh-3        [[1, 120, 1, 1]]      [1, 120, 1, 1]           0       Flatten-1      [[1, 120, 1, 1]]         [1, 120]              0       Linear-1          [[1, 120]]            [1, 84]            10,164     Tanh-4           [[1, 84]]             [1, 84]               0       Linear-2          [[1, 84]]             [1, 10]              850      
===========================================================================
Total params: 61,706
Trainable params: 61,706
Non-trainable params: 0
---------------------------------------------------------------------------
Input size (MB): 0.00
Forward/backward pass size (MB): 0.12
Params size (MB): 0.24
Estimated Total Size (MB): 0.36
---------------------------------------------------------------------------{'total_params': 61706, 'trainable_params': 61706}

5.2网络结构代码实现2

用Sequential写法。

import paddle.nn as nnnetwork_2 = nn.Sequential(nn.Conv2D(in_channels=1, out_channels=6, kernel_size=3, stride=1, padding=1),#这里改变了卷积核大小以及加了paddingnn.ReLU(),nn.MaxPool2D(kernel_size=2, stride=2),nn.Conv2D(in_channels=6, out_channels=16, kernel_size=5, stride=1, padding=0),nn.ReLU(),nn.MaxPool2D(kernel_size=2, stride=2),nn.Flatten(),nn.Linear(in_features=400, out_features=120),  # 400 = 5x5x16,输入形状为32x32, 输入形状为28x28时调整为256nn.Linear(in_features=120, out_features=84),nn.Linear(in_features=84, out_features=10)
)

5.3 网络结构代码实现3【推荐比较灵活】

模型结构和【网络结构代码实现2】一致,用Sub Class写法。

class LeNet(nn.Layer):"""继承paddle.nn.Layer定义网络结构"""def __init__(self, num_classes=10):"""初始化函数"""super(LeNet, self).__init__()self.features = nn.Sequential(nn.Conv2D(in_channels=1, out_channels=6, kernel_size=3, stride=1, padding=1),  # 第一层卷积nn.ReLU(), # 激活函数nn.MaxPool2D(kernel_size=2, stride=2),  # 最大池化,下采样nn.Conv2D(in_channels=6, out_channels=16, kernel_size=5, stride=1, padding=0), # 第二层卷积nn.ReLU(), # 激活函数nn.MaxPool2D(kernel_size=2, stride=2) # 最大池化,下采样)self.fc = nn.Sequential(nn.Linear(400, 120),  # 全连接nn.Linear(120, 84),   # 全连接nn.Linear(84, num_classes) # 输出层)def forward(self, inputs):"""前向计算"""y = self.features(inputs)y = paddle.flatten(y, 1) #拉伸成一维out = self.fc(y)return outnetwork_3 = LeNet()

5.4网络结构代码实现4

直接应用高层API中封装好的LeNet网络接口。

network_4 = paddle.vision.models.LeNet(num_classes=10)

6.模型训练和优化

模型配置

  • 优化器:SGD
  • 损失函数:交叉熵(cross entropy)
  • 评估指标:Accuracy
fit(train_data=None, eval_data=None, batch_size=1, epochs=1, eval_freq=1, log_freq=10, save_dir=None, save_freq=1, verbose=2, drop_last=False, shuffle=True, num_workers=0, callbacks=None)
训练模型。当 eval_data 给定时,会在 eval_freq 个 epoch 后进行一次评估。参数:
train_data (Dataset|DataLoader) - 一个可迭代的数据源,推荐给定一个 paddle paddle.io.Dataset 或 paddle.io.Dataloader 的实例。默认值:None。eval_data (Dataset|DataLoader) - 一个可迭代的数据源,推荐给定一个 paddle paddle.io.Dataset 或 paddle.io.Dataloader 的实例。当给定时,会在每个 epoch 后都会进行评估。默认值:None。batch_size (int) - 训练数据或评估数据的批大小,当 train_data 或 eval_data 为 DataLoader 的实例时,该参数会被忽略。默认值:1。epochs (int) - 训练的轮数。默认值:1。eval_freq (int) - 评估的频率,多少个 epoch 评估一次。默认值:1。log_freq (int) - 日志打印的频率,多少个 step 打印一次日志。默认值:1。save_dir (str|None) - 保存模型的文件夹,如果不设定,将不保存模型。默认值:None。save_freq (int) - 保存模型的频率,多少个 epoch 保存一次模型。默认值:1。verbose (int) - 可视化的模型,必须为0,1,2。当设定为0时,不打印日志,设定为1时,使用进度条的方式打印日志,设定为2时,一行一行地打印日志。默认值:2。drop_last (bool) - 是否丢弃训练数据中最后几个不足设定的批次大小的数据。默认值:False。shuffle (bool) - 是否对训练数据进行洗牌。当 train_data 为 DataLoader 的实例时,该参数会被忽略。默认值:True。num_workers (int) - 启动子进程用于读取数据的数量。当 train_data 和 eval_data 都为 DataLoader 的实例时,该参数会被忽略。默认值:0。callbacks (Callback|list[Callback]|None) - Callback 的一个实例或实例列表。该参数不给定时,默认会插入 ProgBarLogger 和 ModelCheckpoint 这两个实例。默认值:None。
# 模型封装
model = paddle.Model(network_4)# 模型配置
model.prepare(paddle.optimizer.Adam(learning_rate=0.001, parameters=model.parameters()), # 优化器paddle.nn.CrossEntropyLoss(), # 损失函数paddle.metric.Accuracy()) # 评估指标callback = paddle.callbacks.VisualDL(log_dir='visualdl_log_dir_LeNet学习率0.001')
# 启动全流程训练
model.fit(train_dataset,  # 训练数据集eval_dataset,   # 评估数据集epochs=5,       # 训练轮次batch_size=64,  # 单次计算数据样本量verbose=1,callbacks=callback)      # 日志展示形式
The loss value printed in the log is the current step, and the metric is the average value of previous step.
Epoch 1/5
step 938/938 [==============================] - loss: 0.0221 - acc: 0.9867 - 21ms/step        
Eval begin...
The loss value printed in the log is the current batch, and the metric is the average value of previous step.
step 157/157 [==============================] - loss: 0.0037 - acc: 0.9849 - 9ms/step           
Eval samples: 10000
Epoch 2/5
step 938/938 [==============================] - loss: 0.0251 - acc: 0.9893 - 21ms/step        
Eval begin...
The loss value printed in the log is the current batch, and the metric is the average value of previous step.
step 157/157 [==============================] - loss: 1.8192e-04 - acc: 0.9837 - 8ms/step       
Eval samples: 10000
Epoch 3/5
step 938/938 [==============================] - loss: 0.0029 - acc: 0.9903 - 21ms/step         
Eval begin...
The loss value printed in the log is the current batch, and the metric is the average value of previous step.
step 157/157 [==============================] - loss: 6.0831e-04 - acc: 0.9826 - 9ms/step       
Eval samples: 10000
Epoch 4/5
step 938/938 [==============================] - loss: 4.9427e-04 - acc: 0.9916 - 21ms/step     
Eval begin...
The loss value printed in the log is the current batch, and the metric is the average value of previous step.
step 157/157 [==============================] - loss: 5.9639e-04 - acc: 0.9809 - 8ms/step       
Eval samples: 10000
Epoch 5/5
step 938/938 [==============================] - loss: 0.0323 - acc: 0.9914 - 21ms/step           
Eval begin...
The loss value printed in the log is the current batch, and the metric is the average value of previous step.
step 157/157 [==============================] - loss: 3.1420e-04 - acc: 0.9834 - 8ms/step 

6.1可视化训练结果

7 模型评估

7.1 模型评估

evaluate(eval_data, batch_size=1, log_freq=10, verbose=2, num_workers=0, callbacks=None)
在输入数据上,评估模型的损失函数值和评估指标。参数:
eval_data (Dataset|DataLoader) - 一个可迭代的数据源,推荐给定一个 paddle paddle.io.Dataset 或 paddle.io.Dataloader 的实例。默认值:None。batch_size (int) - 训练数据或评估数据的批大小,当 eval_data 为 DataLoader 的实例时,该参数会被忽略。默认值:1。log_freq (int) - 日志打印的频率,多少个 step 打印一次日志。默认值:1。verbose (int) - 可视化的模型,必须为0,1,2。当设定为0时,不打印日志,设定为1时,使用进度条的方式打印日志,设定为2时,一行一行地打印日志。默认值:2。num_workers (int) - 启动子进程用于读取数据的数量。当 eval_data 为 DataLoader 的实例时,该参数会被忽略。默认值:True。callbacks (Callback|list[Callback]|None) - Callback 的一个实例或实例列表。该参数不给定时,默认会插入 ProgBarLogger 和 ModelCheckpoint 这两个实例。默认值:None。返回:dict, key是 prepare 时Metric的的名称,value是该Metric的值。
result = model.evaluate(eval_dataset, verbose=1)print(result)
Eval begin...
The loss value printed in the log is the current batch, and the metric is the average value of previous step.
step 10000/10000 [==============================] - loss: 6.9142e-06 - acc: 0.9846 - 3ms/step        
Eval samples: 10000
{'loss': [6.914163e-06], 'acc': 0.9846}

7.2 模型预测

7.2.1 批量预测

使用model.predict接口来完成对大量数据集的批量预测。

# 进行预测操作
result = model.predict(eval_dataset)
# 定义画图方法
def show_img(img, predict):plt.figure()plt.title('predict: {}'.format(predict))plt.imshow(img.reshape([28, 28]), cmap=plt.cm.binary)plt.show()# 抽样展示
indexs = [5, 20, 48, 210]for idx in indexs:show_img(eval_dataset[idx][0], np.argmax(result[0][idx]))

在这里插入图片描述

8 部署上线

8.1 保存模型

model.save('finetuning/mnist')

8.2 继续调优训练

from paddle.static import InputSpecnetwork = paddle.vision.models.LeNet(num_classes=10)
# 模型封装,为了后面保存预测模型,这里传入了inputs参数
model_2 = paddle.Model(network, inputs=[InputSpec(shape=[-1, 1, 28, 28], dtype='float32', name='image')])# 加载之前保存的阶段训练模型
model_2.load('finetuning/mnist')# 模型配置
model_2.prepare(paddle.optimizer.Adam(learning_rate=0.0001, parameters=network.parameters()),  # 优化器paddle.nn.CrossEntropyLoss(), # 损失函数paddle.metric.Accuracy()) # 评估函数
callback = paddle.callbacks.VisualDL(log_dir='visualdl_log_dir_LeNet学习率0.0001')
# 模型全流程训练
model_2.fit(train_dataset,  # 训练数据集eval_dataset,   # 评估数据集epochs=2,       # 训练轮次batch_size=64,  # 单次计算数据样本量verbose=1,        # 日志展示形式callbacks=callback)       #可视化
The loss value printed in the log is the current step, and the metric is the average value of previous step.
Epoch 1/2
step 938/938 [==============================] - loss: 0.0038 - acc: 0.9934 - 21ms/step        
Eval begin...
The loss value printed in the log is the current batch, and the metric is the average value of previous step.
step 157/157 [==============================] - loss: 5.2845e-04 - acc: 0.9871 - 9ms/step        
Eval samples: 10000
Epoch 2/2
step 938/938 [==============================] - loss: 9.8578e-04 - acc: 0.9952 - 21ms/step           0.0021 - acc: 
Eval begin...
The loss value printed in the log is the current batch, and the metric is the average value of previous step.
step 157/157 [==============================] - loss: 4.6717e-04 - acc: 0.9873 - 8ms/step       
Eval samples: 10000

效果图

8.3 保存预测模型

# 保存用于后续推理部署的模型
model_2.save('infer/mnist', training=False)

总结

可以看到再采用LeNet卷积网络后数字识别的准确率比项目一中全连接形式准确率提高,精益求精了。

不过这个项目只是单通道的黑白图识别,下面在项目三种我将介绍其余几种热门更加使用的复杂卷积,以及实现彩色图片多分类任务

这篇关于【深度学习项目二】卷积神经网络LeNet实现minst数字识别的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/550655

相关文章

C语言小项目实战之通讯录功能

《C语言小项目实战之通讯录功能》:本文主要介绍如何设计和实现一个简单的通讯录管理系统,包括联系人信息的存储、增加、删除、查找、修改和排序等功能,文中通过代码介绍的非常详细,需要的朋友可以参考下... 目录功能介绍:添加联系人模块显示联系人模块删除联系人模块查找联系人模块修改联系人模块排序联系人模块源代码如下

Java中使用Java Mail实现邮件服务功能示例

《Java中使用JavaMail实现邮件服务功能示例》:本文主要介绍Java中使用JavaMail实现邮件服务功能的相关资料,文章还提供了一个发送邮件的示例代码,包括创建参数类、邮件类和执行结... 目录前言一、历史背景二编程、pom依赖三、API说明(一)Session (会话)(二)Message编程客

Java中List转Map的几种具体实现方式和特点

《Java中List转Map的几种具体实现方式和特点》:本文主要介绍几种常用的List转Map的方式,包括使用for循环遍历、Java8StreamAPI、ApacheCommonsCollect... 目录前言1、使用for循环遍历:2、Java8 Stream API:3、Apache Commons

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

使用Python实现高效的端口扫描器

《使用Python实现高效的端口扫描器》在网络安全领域,端口扫描是一项基本而重要的技能,通过端口扫描,可以发现目标主机上开放的服务和端口,这对于安全评估、渗透测试等有着不可忽视的作用,本文将介绍如何使... 目录1. 端口扫描的基本原理2. 使用python实现端口扫描2.1 安装必要的库2.2 编写端口扫

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

MySQL分表自动化创建的实现方案

《MySQL分表自动化创建的实现方案》在数据库应用场景中,随着数据量的不断增长,单表存储数据可能会面临性能瓶颈,例如查询、插入、更新等操作的效率会逐渐降低,分表是一种有效的优化策略,它将数据分散存储在... 目录一、项目目的二、实现过程(一)mysql 事件调度器结合存储过程方式1. 开启事件调度器2. 创

使用Python实现操作mongodb详解

《使用Python实现操作mongodb详解》这篇文章主要为大家详细介绍了使用Python实现操作mongodb的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、示例二、常用指令三、遇到的问题一、示例from pymongo import MongoClientf

SQL Server使用SELECT INTO实现表备份的代码示例

《SQLServer使用SELECTINTO实现表备份的代码示例》在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误,在SQLServer中,可以使用SELECTINT... 在数据库管理过程中,有时我们需要对表进行备份,以防数据丢失或修改错误。在 SQL Server 中,可以使用 SE

基于Go语言实现一个压测工具

《基于Go语言实现一个压测工具》这篇文章主要为大家详细介绍了基于Go语言实现一个简单的压测工具,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录整体架构通用数据处理模块Http请求响应数据处理Curl参数解析处理客户端模块Http客户端处理Grpc客户端处理Websocket客户端