深度学习10. CNN经典网络 LeNet-5
迪丽瓦拉
2025-05-29 06:36:16
0

深度学习10. CNN经典网络 LeNet-5

  • 一、 LeNet-5简介
  • 二、网络详解
    • 1. 输入图像
    • 2. 卷积层 C1
    • 3. 池化层S2
    • 4. 卷积层C3
    • 5. 池化层 S4
    • 6. 全连接层F5,F6
    • 7. 输出层
  • 三、 PyTorch的实现
      • 图像展平
      • 损失函数

一、 LeNet-5简介

LeNet-5是一个经典的卷积神经网络模型,1998年被提出,论文题目是 “Gradient-Based Learning Applied to Document Recognition” ,作者为 Yann LeCun, Léon Bottou, Yoshua Bengio, and Patrick Haffner。

LeNet-5是一个用于手写数字识别的深度神经网络模型,由两个卷积层和三个全连接层组成。

LeNet-5是深度神经网络的开创者之一,对后来的深度学习算法发展产生了重要的影响。

LeNet-5的结构如下:

  • 输入层:32x32的图像
  • 卷积层C1:使用6个5x5的卷积核,步长为1,激活函数为sigmoid
  • 池化层S2:使用2x2的最大池化操作,步长为2
  • 卷积层C3:使用16个5x5的卷积核,步长为1,激活函数为sigmoid
  • 池化层S4:使用2x2的最大池化操作,步长为2
  • 全连接层F5:输出120个神经元,激活函数为sigmoid
  • 全连接层F6:输出84个神经元,激活函数为sigmoid
  • 输出层:10个神经元,对应10个手写数字类别,使用softmax激活函数

论文地址

识别动图:

在这里插入图片描述

二、网络详解

1. 输入图像

LeNet-5使用32*32图像。
本文示例将会使用MNIST实现LeNet-5,数据集包含 60000张28x28像素的训练图像和10000张测试图像。

2. 卷积层 C1

C1 用来提取输入图像的特征,输入是一个2828的灰度图像,共6个卷积核,每 个卷积核大小55,卷积核的深度与输入图像的深度相同(即为1)。

卷积核的参数需要进行学习,每个卷积核都学习一个5x5的参数矩阵,这些参数在整个网络训练的过程中进行更新。

在进行卷积计算之前,C1卷积层对输入图像进行了一些预处理。首先,将28x28的输入图像填充成32x32的大小,这样可以使得卷积核的中心始终在输入图像内部移动,从而避免了边缘像素的信息丢失。然后,对填充后的图像进行了局部响应归一化(local response normalization)操作,这个操作可以增强图像特征的对比度。

接着,C1卷积层对输入图像进行6次卷积计算,每次计算都使用一个卷积核。卷积操作的结果是得到6个28x28的特征图,这些特征图分别对应着6个卷积核在输入图像上的响应。

最后,将得到的6个特征图进行叠加,得到的结果是一个6 x 28 x 28的立方体,这个立方体可以被看作是一个三维的特征图。这个特征图将作为下一个卷积层的输入。

使用PyTorch模拟此层可以使用:
self.conv1 = nn.Conv2d(1, 6, kernel_size=5, stride=1)

  • 其中参数1 是输入数据通道数,灰度图为1;
  • 参数6是输出数据通道数,即卷积核数量;
  • kernel_size=5是卷积核大小;
  • stride=1是步长。
    此层的输入为 batch_size x 1 x 28 x 28,输出图像的形状为 batch_size x 6 x 24 x 24,卷积操作后图像大小为24 x 24。

3. 池化层S2

在 LeNet-5 中,池化层 S2 的作用是对卷积层 C1 的输出进行下采样,减少模型的参数数量,提高模型的泛化能力。

S2 层的输入是 C1 层的输出,即经过卷积操作后的特征图,其中每个特征图的大小为 24 x 24 x 6。S2 层采用的是 2x2 的池化窗口,因此每个池化单元会处理一个 2x2 的区域,将其中的四个值取平均作为输出。

这样,经过 S2 层的处理,每个特征图变成了 1 x 6 x 12 x 12。这样做的好处是能够降低特征图的大小和数量,减少了参数数量,避免过拟合。同时,池化操作也能够使特征图的位置对平移更加鲁棒,从而提高模型的泛化能力。

LeNet-5里使用的是最大池化层,下文使用PyTorch模拟LeNet-5将使用平均池化层。
self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)

4. 卷积层C3

它的输入是池化层S2的输出,该层的作用是提取更高级别的特征。

C3层使用 16 个 5 x 5 的卷积核对 S2 的输出进行卷积,每个卷积核对应一个特征图,因此 C3 层输出的通道数为 16。卷积操作的步长为 1,对边缘进行了补零,因此输出的特征图大小为 8 x 8。

C3层的输出经过ReLU激活函数,然后传递到下一层,即池化层S4。

C3 输出的形状是:1 x 16 x 8 x 8。

5. 池化层 S4

S4层的输入是C3层的输出,它对输入进行降采样(最大池化)。S4层使用的池化窗口大小为2×2,步长为2。

S4层会将C3层的输出沿着宽和高两个维度分别缩小一半。

在LeNet-5中,S4层与C5层之间没有使用任何归一化、正则化等操作,只是简单地使用了最大池化降采样,以保留最显著的特征。

S4输出的形状是: 1 x 16 x 4 x 4

6. 全连接层F5,F6

在 LeNet-5 中,全连接层分为 F5 和 F6 两个部分,其中 F5 包含 120 个神经元,F6 包含 84 个神经元。这两个全连接层负责将前面卷积和池化层的特征进行组合,产生分类所需的输出。

F5 层的输入为一个形状为 1x120 的向量,它与每个神经元都进行连接。每个神经元都有 120 个输入连接,其中每个连接都与 C3 层的一个 5x5 的卷积核进行卷积运算,将其结果相加后再加上一个偏置项进行激活。这样,F5 层产生的输出是一个 1x120 的向量。

F6 层与 F5 层类似,输出是1 x 84的向量。权重在训练期间通过反向传播算法进行优化,以最小化训练数据上的损失函数。

7. 输出层

输出层是一个全连接层,用于将前面的特征提取和处理结果映射到目标类别上。输出层的输出是一个大小为 101010 的向量,每个元素表示模型属于对应类别的概率。

输出层的激活函数使用 softmax 函数,它能够将原始的输出值转换为每个类别的概率分布。softmax 函数的公式如下:

softmax(xi)=exi∑jexjsoftmax(x_i) = \frac{e^{x_i}}{\sum_j e^{x_j}}softmax(xi​)=∑j​exj​exi​​

其中 xix_ixi​ 表示输入向量的第 iii 个元素,∑jexj\sum_j e^{x_j}∑j​exj​ 表示所有输入元素的指数函数之和。

对于 LeNet-5 模型中的输出层,假设 y1,y2,...,y10y_1, y_2, ..., y_{10}y1​,y2​,...,y10​ 分别表示该模型属于 0−90-90−9 十个数字的概率,那么该层的输出可以表示为:

[y1,y2,...,y10][y_1, y_2, ..., y_{10}][y1​,y2​,...,y10​]

最终的预测结果是概率最大的那个数字,即 argmax⁡(y1,y2,...,y10)\operatorname{argmax}(y_1, y_2, ..., y_{10})argmax(y1​,y2​,...,y10​)。

三、 PyTorch的实现

import osimport torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
from torch.utils.data import DataLoader# 定义 LeNet-5 模型
class LeNet5(nn.Module):def __init__(self):super(LeNet5, self).__init__()# 定义卷积层C1,输入通道数为1,输出通道数为6,卷积核大小为5x5self.conv1 = nn.Conv2d(1, 6, kernel_size=5, stride=1)# 定义池化层S2,池化核大小为2x2,步长为2self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2)# 定义卷积层C3,输入通道数为6,输出通道数为16,卷积核大小为5x5self.conv2 = nn.Conv2d(6, 16, kernel_size=5, stride=1)# 定义池化层S4,池化核大小为2x2,步长为2self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2)# 定义全连接层F5,输入节点数为16x4x4=256,输出节点数为120self.fc1 = nn.Linear(16 * 4 * 4, 120)# 定义全连接层F6,输入节点数为120,输出节点数为84self.fc2 = nn.Linear(120, 84)# 定义输出层,输入节点数为84,输出节点数为10self.fc3 = nn.Linear(84, 10)def forward(self, x):# 卷积层C1x = self.conv1(x)# print('卷积层C1后的形状:', x.shape)# 池化层S2x = self.pool1(torch.relu(x))# print('池化层S2后的形状:', x.shape)# 卷积层C3x = self.conv2(x)# print('卷积层C3后的形状:', x.shape)# 池化层S4x = self.pool2(torch.relu(x))# print('池化层S4后的形状:', x.shape)# 全连接层F5x = x.view(-1, 16 * 4 * 4)x = self.fc1(x)# print('全连接层F5后的形状:', x.shape)x = torch.relu(x)# 全连接层F6x = self.fc2(x)# print('全连接层F6后的形状:', x.shape)x = torch.relu(x)# 输出层x = self.fc3(x)# print('输出层后的形状:', x.shape)return x# 设置超参数
batch_size = 64
learning_rate = 0.01
epochs = 10# 准备数据
train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)
test_dataset = datasets.MNIST(root='./data', train=False, transform=transforms.ToTensor(), download=True)train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=True)# 实例化模型和优化器
model = LeNet5()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)# 定义模型保存路径和文件名
model_path = 'model.pth'
if os.path.exists(model_path):# 存在,直接加载模型model.load_state_dict(torch.load(model_path))print('Loaded model from', model_path)
else:# 训练模型for epoch in range(epochs):model.train()for images, labels in train_loader:# 将图像展平# images = images.view(images.size(0), -1)images = images.view(-1, 1, 28, 28)# 将数据放入模型optimizer.zero_grad()outputs = model(images)loss = nn.functional.cross_entropy(outputs, labels)loss.backward()optimizer.step()# 在测试集上测试模型model.eval()correct = 0with torch.no_grad():for images, labels in test_loader:# 将图像展平images = images.view(-1, 1, 28, 28)# 将数据放入模型outputs = model(images)_, predicted = torch.max(outputs, 1)correct += (predicted == labels).sum().item()accuracy = 100 * correct / len(test_dataset)print('Epoch [{}/{}], Loss: {:.4f}, Accuracy: {:.2f}%'.format(epoch + 1, epochs, loss.item(), accuracy))torch.save(model.state_dict(), 'model.pth')for i in range(10):img, label = next(iter(test_loader))img = img[i].unsqueeze(0)# 使用模型进行预测model.eval()with torch.no_grad():output = model(img)# 解码预测结果pred = output.argmax(dim=1).item()print(f'Predicted class: {pred}, actual value: {label[i]}')

打印的各层的形状:
在这里插入图片描述
部分内容解释 :

图像展平

images = images.view(-1, 1, 28, 28)

输入图像 images 要进行形状的变换。
首先,使用 view 函数将 images 变换为 4 维张量,其中:

  • 第 1 维的大小被设置为 -1,表示这一维度的大小应该是根据输入数据的总大小和其他维度的大小来自动计算的;
  • 第 2 维是 1,表示输入图像只有一个通道;
  • 第 3 维和第 4 维的大小都是 28,表示输入图像的高和宽都是 28。

损失函数

loss = nn.functional.cross_entropy(outputs, labels)
outputs 是模型的输出,labels 是对应的真实标签。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...