【深度学习项目二】卷积神经网络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

相关文章

pandas中位数填充空值的实现示例

《pandas中位数填充空值的实现示例》中位数填充是一种简单而有效的方法,用于填充数据集中缺失的值,本文就来介绍一下pandas中位数填充空值的实现,具有一定的参考价值,感兴趣的可以了解一下... 目录什么是中位数填充?为什么选择中位数填充?示例数据结果分析完整代码总结在数据分析和机器学习过程中,处理缺失数

Golang HashMap实现原理解析

《GolangHashMap实现原理解析》HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持高效的插入、查找和删除操作,:本文主要介绍GolangH... 目录HashMap是一种基于哈希表实现的键值对存储结构,它通过哈希函数将键映射到数组的索引位置,支持

Java学习手册之Filter和Listener使用方法

《Java学习手册之Filter和Listener使用方法》:本文主要介绍Java学习手册之Filter和Listener使用方法的相关资料,Filter是一种拦截器,可以在请求到达Servl... 目录一、Filter(过滤器)1. Filter 的工作原理2. Filter 的配置与使用二、Listen

Pandas使用AdaBoost进行分类的实现

《Pandas使用AdaBoost进行分类的实现》Pandas和AdaBoost分类算法,可以高效地进行数据预处理和分类任务,本文主要介绍了Pandas使用AdaBoost进行分类的实现,具有一定的参... 目录什么是 AdaBoost?使用 AdaBoost 的步骤安装必要的库步骤一:数据准备步骤二:模型

使用Pandas进行均值填充的实现

《使用Pandas进行均值填充的实现》缺失数据(NaN值)是一个常见的问题,我们可以通过多种方法来处理缺失数据,其中一种常用的方法是均值填充,本文主要介绍了使用Pandas进行均值填充的实现,感兴趣的... 目录什么是均值填充?为什么选择均值填充?均值填充的步骤实际代码示例总结在数据分析和处理过程中,缺失数

Java对象转换的实现方式汇总

《Java对象转换的实现方式汇总》:本文主要介绍Java对象转换的多种实现方式,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录Java对象转换的多种实现方式1. 手动映射(Manual Mapping)2. Builder模式3. 工具类辅助映

Go语言开发实现查询IP信息的MCP服务器

《Go语言开发实现查询IP信息的MCP服务器》随着MCP的快速普及和广泛应用,MCP服务器也层出不穷,本文将详细介绍如何在Go语言中使用go-mcp库来开发一个查询IP信息的MCP... 目录前言mcp-ip-geo 服务器目录结构说明查询 IP 信息功能实现工具实现工具管理查询单个 IP 信息工具的实现服

SpringBoot基于配置实现短信服务策略的动态切换

《SpringBoot基于配置实现短信服务策略的动态切换》这篇文章主要为大家详细介绍了SpringBoot在接入多个短信服务商(如阿里云、腾讯云、华为云)后,如何根据配置或环境切换使用不同的服务商,需... 目录目标功能示例配置(application.yml)配置类绑定短信发送策略接口示例:阿里云 & 腾

SpringBoot项目中报错The field screenShot exceeds its maximum permitted size of 1048576 bytes.的问题及解决

《SpringBoot项目中报错ThefieldscreenShotexceedsitsmaximumpermittedsizeof1048576bytes.的问题及解决》这篇文章... 目录项目场景问题描述原因分析解决方案总结项目场景javascript提示:项目相关背景:项目场景:基于Spring

解决Maven项目idea找不到本地仓库jar包问题以及使用mvn install:install-file

《解决Maven项目idea找不到本地仓库jar包问题以及使用mvninstall:install-file》:本文主要介绍解决Maven项目idea找不到本地仓库jar包问题以及使用mvnin... 目录Maven项目idea找不到本地仓库jar包以及使用mvn install:install-file基