深度学习---最基础卷积神经网络---LeNet实战(附代码)
零、需要获取全代码的直接拉到最下面复制
一、LeNet介绍
LeNet-5是一种经典的卷积神经网络结构,由Yann LeCun等人在1998年提出,并投入实际使用。这个网络最早应用于手写体字符识别应用中,被普遍认为是卷积神经网络(CNN)的开端。
1.LeNet-5的应用
LeNet-5在手写数字识别方面表现出色,尤其是在MNIST数据集上的应用,它能够识别0到9的手写数字。此外,LeNet-5还曾被用于支票上的手写数字识别,每天处理数百万张支票。
2.LeNet-5的历史意义
LeNet-5不仅是CNN的开山之作,而且它的成功应用为后来更复杂的CNN模型(如AlexNet、VGG、ResNet等)的发展奠定了基础。尽管LeNet-5在当今深度学习的发展中已不再是最先进的技术,但它的经典结构和训练方法仍然对深度学习的发展和应用具有重要意义。
二、LeNet网络架构

LeNet-5由7层组成,包括卷积层、池化层和全连接层:
- C1层:卷积层,包含6个5x5大小的卷积核,步长为1,没有填充,输出6个28x28像素的特征图。
- S2层:平均池化层,池化区域大小为2x2,步长为2,输出6个14x14像素的特征图。
- C3层:卷积层,包含16个5x5大小的卷积核,步长为1,没有填充,输出16个10x10像素的特征图。
- S4层:平均池化层,池化区域大小为2x2,步长为2,输出16个5x5像素的特征图。
- C5层:卷积层,包含120个1x1大小的卷积核,输出120个1x1像素的特征图。
- F6层:全连接层,包含84个神经元,使用sigmoid激活函数。
- 输出层:由10个神经元组成的softmax层,用于分类。
- LeNet由两个部分组成:
- 特征提取部分:由两个卷积层和两个平均池化层组成。
- 全连接层:由三个全连接层组成。
- 数据的传输:
- 卷积层输入为4维数据(B,C,W,H)
- 卷积层输出为4维数据(B,C,FW,FH)
- 全连接层输入为2维数据(B,L)
- 全连接层输出为2维数据(B,FL)
参数说明:
- B (Batch Size):批次大小,表示一次输入网络的样本数量。
- C (Channels):通道数,表示每个样本的通道数量,例如,对于彩色图像,通道数通常是3(红、绿、蓝)。
- W (Width):宽度,表示每个样本的宽度。
- H (Height):高度,表示每个样本的高度。
三、LeNet网络参数

1.输入数据
(1*28*28)其中1表示通道数为1,即灰度图,28*28表示图片的长宽为28*28。
2.卷积核
(5*5)表示卷积核大小为5*5,卷积核个数为6,边缘填充为2。
3.池化
(2*2)感受野为2*2,表示池化层输出一个像素为实际的输入的2*2个像素。
4.全连接
将特征平铺为为一列数据,和全连接层神经元进行全连接,得到对应特征组合 ,形成全局特征,通过全局特征可以捕捉到全局信息。在最后一层全连接层中通常会使用分类器进行分类操作。
四、pytorch实现
1.实现流程
LeNet卷积神经网络的实现流程可以简化为以下几个步骤:
1)模型搭建(Model Architecture)
- 定义网络结构:确定LeNet网络的层数和每层的类型,包括卷积层、池化层、全连接层等。
- 选择激活函数:为卷积层选择合适的激活函数,如ReLU或tanh。
- 确定输出层:设置输出层的神经元数量和激活函数,对于分类问题通常使用Softmax。
2)数据预处理(Data Preprocessing)
- 加载数据集:导入训练和测试数据集,如MNIST手写数字数据集。
- 归一化:将图像像素值缩放到0到1之间,以提高模型训练的稳定性和效率。
- 调整尺寸:确保输入数据的尺寸与模型输入层的期望尺寸相匹配。
- 标签编码:将类别标签转换为适合模型训练的格式,如独热编码。
3)模型编译(Model Compilation)
- 选择损失函数:根据任务类型(如分类或回归)选择合适的损失函数。
- 选择优化器:选择一个优化算法,如SGD、Adam等,来更新网络权重。
- 选择评估指标:确定用于评估模型性能的指标,如准确率。
4)模型训练(Model Training)
- 训练模型:使用训练数据对模型进行训练,同时在验证集上评估模型性能。
- 调整超参数:根据训练和验证结果调整学习率、批大小、迭代次数等超参数。
5) 模型测试(Model Testing)
- 评估模型:在测试集上评估模型的最终性能,以确定模型的泛化能力。
- 结果分析:分析模型的预测结果,确定模型的准确性和可能的改进方向。
2.模型搭建
1)类定义
-
定义类:
class LeNet(nn.Module):这行代码定义了一个名为LeNet的类,它继承自nn.Module。nn.Module是PyTorch中所有神经网络模块的基类,提供了一些基础的接口和属性,如参数列表、正向传播等。 -
初始化方法:
def __init__(self):定义了LeNet类的初始化方法。__init__是Python中的构造函数,当创建类的实例时会自动调用。 -
调用父类初始化方法:
super(LeNet, self).__init__()这行代码调用了父类nn.Module的初始化方法。super()函数用于调用父类(超类)的方法,确保父类的初始化逻辑被正确执行。LeNet和self分别是当前类和当前实例的名称,__init__是要调用的方法。
作用是定义LeNet模型的框架,并确保它正确地继承了nn.Module的所有功能。接下来,需要在__init__方法中定义LeNet模型的具体层结构,例如卷积层、池化层和全连接层等。
import torch
from torch import nn
from torchinfo import summary
# 类定义
class LeNet(nn.Module):
def __init__(self):
2)具体层结构
包括卷积层、池化层、展平层和全连接层。
# 定义卷积核参数:channel:通道数,out_channel:输出通道数,padding:边缘填充,stride:步长,kernel_size:卷积核大小
self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)# 第一层网络参数
self.sig = nn.Sigmoid() # 定义激活函数
self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2) # 定义池化层
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, padding=0) # 定义第二次卷积
self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2) # 定义第二次池化层
# 最后一层池化要对特征值进行平铺
self.flatten = nn.Flatten() # 实将多维的输入数据转换为一维向量的层
# 定义全连接层
self.fc1 = nn.Linear(in_features=400,out_features= 120) # 定义线性全连接层,in_features:输入特征值,out_features:输出特征值
self.fc2 = nn.Linear(in_features=120,out_features= 84)
# 最后一层全连接层要使用分类算法,得到分类项
self.fc3 = nn.Linear(in_features=84,out_features= 10)
其中,nn.conv2d()构造了一个卷积核,卷积核参数如代码所示,需要设置的参数基本上在深度学习那一篇文章有讲过,认准英语单词就可以了。
nn.Sigmoid()构造一个激活函数
nn.AvgPoll2d():构造了一个平均池化,参数也讲过。
nn.Flatten():将数据平铺为一列数据以便进行全连接。
nn.Linear():线性全连接层,需要设置输入特征以及输出特征,其中第一个全连接层的输入特征必须和平铺后的特征值数相等。
3)前向传播
前向传播即从数据输入开始,将数据从前传播至输出,在LeNet中,输入一个32*32*1的图像,经过第一个卷积层,得到6个特征图,在经过第一个池化层,减少特征值,在进行第二次卷积以此类推直至最后输出。
# 前向传播
# 输入数据x
def forward(self, x):
x = self.sig(self.conv1(x))# 第一层卷积层 + 激活函数
x = self.pool1(x) # 第一次池化层
x = self.sig(self.conv2(x)) # 第二次卷积层 + 激活函数
x = self.pool2(x) # 第二次池化层
x = self.flatten(x) # 数据平铺
x = self.fc1(x) # 第一次全连接
x = self.fc2(x) # 第二次全连接
x = self.fc3(x) # 第三次全连接
return x # 返回 分类值
4)全部代码
import torch
from torch import nn
from torchinfo import summary
# 类定义
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
# 定义卷积核参数:channel:通道数,out_channel:输出通道数,padding:边缘填充,stride:步长,kernel_size:卷积核大小
self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)# 第一层网络参数
self.sig = nn.Sigmoid() # 定义激活函数
self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2) # 定义池化层
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, padding=0) # 定义第二次卷积
self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2) # 定义第二次池化层
# 最后一层池化要对特征值进行平铺
self.flatten = nn.Flatten() # 实将多维的输入数据转换为一维向量的层
# 定义全连接层
self.fc1 = nn.Linear(in_features=400,out_features= 120) # 定义线性全连接层,in_features:输入特征值,out_features:输出特征值
self.fc2 = nn.Linear(in_features=120,out_features= 84)
# 最后一层全连接层要使用分类算法,得到分类项
self.fc3 = nn.Linear(in_features=84,out_features= 10)
# 输入数据x
def forward(self, x):
x = self.sig(self.conv1(x))
x = self.pool1(x)
x = self.sig(self.conv2(x))
x = self.pool2(x)
x = self.flatten(x)
x = self.fc1(x)
x = self.fc2(x)
x = self.fc3(x)
return x
if __name__ == '__main__':
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = LeNet().to(device)
print(summary(model, (1 ,1, 28, 28)))
3.模型训练
1)数据集基础知识
1.定义数据集
定义数据集并对数据集进行预处理,其中主要为指定根目录、是否加载训练集、对数据集进行预处理变换(包括图像的大小和格式)、下载。
from torchvision.datasets import FashionMNIST # 获取数据集
import numpy as np
import torch.utils.data as Data
# 定义数据集
"""
root: 指定数据集下载和缓存的根目录。
train: 一个布尔值,指示是否加载训练集。设置为 True 表示加载训练集,如果设置为 False,则加载测试集。
transform: 一个变换(transform)的组合,用于对数据集中的图像进行预处理。
transforms.Compose 来组合两个变换
transforms.Resize(size=224): 将所有图像调整到224x224像素的大小。
transforms.ToTensor(): 将 PIL 图像或 NumPy ndarray 转换为 FloatTensor,并且将图像的像素值从 [0, 255] 归一化到 [0.0, 1.0]。
download: 一个布尔值,指示如果数据集不在本地时是否需要下载。
"""
train_data = FashionMNIST(root='./data',
train=True,
transform=transforms.Compose([transforms.Resize(size=224), transforms.ToTensor()]),
download=True)
参数说明:
-
root: 指定数据集下载和缓存的根目录。在这个例子中,数据集将被下载到当前工作目录下的./data文件夹中。 -
train: 一个布尔值,指示是否加载训练集。设置为True表示加载训练集,如果设置为False,则加载测试集。 -
transform: 一个变换(transform)的组合,用于对数据集中的图像进行预处理。这里使用了transforms.Compose来组合两个变换:transforms.Resize(size=224): 将所有图像调整到224x224像素的大小。这是许多深度学习模型,特别是预训练模型,常用的输入尺寸。transforms.ToTensor(): 将 PIL 图像或 NumPyndarray转换为FloatTensor,并且将图像的像素值从 [0, 255] 归一化到 [0.0, 1.0]。
-
download: 一个布尔值,指示如果数据集不在本地时是否需要下载。设置为True表示如果数据集不在root指定的目录中,则会自动下载数据集。
2.批量加载数据
批量加载数据就是将数据打包为一捆一捆,分批给模型进行训练,每次给模型训练一个批次
# 分批加载数据
"""
dataset: 这是要加载的数据集
batch_size: 指定每个批次(batch)中的样本数量。
shuffle: 一个布尔值,指示是否在每个epoch开始时打乱数据。
num_workers: 指定用于数据加载的子进程数量。越多越快,但可能导致系统资源紧张
"""
train_loader = Data.DataLoader(dataset=train_data,
batch_size=64,
shuffle=True,
num_workers=0)
参数说明:
-
dataset: 这是要加载的数据集,train_data是前面代码中创建的 FashionMNIST 训练集的实例。 -
batch_size: 指定每个批次(batch)中的样本数量。在这个例子中,batch_size=64意味着每次迭代将提供64个样本给模型进行训练。 -
shuffle: 一个布尔值,指示是否在每个epoch开始时打乱数据。shuffle=True表示数据将在每个epoch开始时被随机打乱,这有助于模型学习时的泛化能力,避免模型对数据的特定顺序产生依赖。 -
num_workers: 指定用于数据加载的子进程数量。num_workers=0表示数据加载将在主进程中进行,不使用额外的子进程。
3.完整代码
from torchvision.datasets import FashionMNIST # 获取数据集
import numpy as np
from torchvision import transforms # 归一化数据集
import torch.utils.data as Data
# 定义数据集
"""
root: 指定数据集下载和缓存的根目录。
train: 一个布尔值,指示是否加载训练集。设置为 True 表示加载训练集,如果设置为 False,则加载测试集。
transform: 一个变换(transform)的组合,用于对数据集中的图像进行预处理。
transforms.Compose 来组合两个变换
transforms.Resize(size=224): 将所有图像调整到224x224像素的大小。
transforms.ToTensor(): 将 PIL 图像或 NumPy ndarray 转换为 FloatTensor,并且将图像的像素值从 [0, 255] 归一化到 [0.0, 1.0]。
download: 一个布尔值,指示如果数据集不在本地时是否需要下载。
"""
train_data = FashionMNIST(root='./data',
train=True,
transform=transforms.Compose([transforms.Resize(size=224), transforms.ToTensor()]),
download=True)
# 分批加载数据
"""
dataset: 这是要加载的数据集
batch_size: 指定每个批次(batch)中的样本数量。
shuffle: 一个布尔值,指示是否在每个epoch开始时打乱数据。
num_workers: 指定用于数据加载的子进程数量。越多越快,但可能导致系统资源紧张
"""
train_loader = Data.DataLoader(dataset=train_data,
batch_size=64,
shuffle=True,
num_workers=0)
2)数据加载
定义数据集,分别处理训练集和测试集,返回划分好的训练集和测试集。
# 处理训练集和测试集
def train_val_data_process():
# 定义数据集
train_data = FashionMNIST(root='./data',
train=True,
transform=transforms.Compose([transforms.Resize(size=28), transforms.ToTensor()]),
download=True)
# 划分数据集,处理数据集
# 划分数据
train_data, test_data = Data.random_split(train_data,lengths=[round(0.8*len(train_data)),round(0.2*len(train_data))])
# 分批加载训练集和测试集
train_data_loader = Data.DataLoader(dataset=train_data, batch_size=64, shuffle=True,
num_workers=2)
test_data_loader = Data.DataLoader(dataset=test_data, batch_size=64, shuffle=True,
num_workers=2)
return train_data_loader, test_data_loader
3)训练、测试模型参数初始化
1.模型初始化
初始化模型包括导入cpu或者gpu,初始化构造交叉熵损失、构造优化损失器、定义模型、保存模型参数。
# 训练模型
# 传入模型、训练数据、测试数据、训练次数
def train_model_process(model, train_data_loader, test_data_loader,num_epochs):
# 导入设备cup或者gpu
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 损失函数 初始化了一个交叉熵损失
criterion = torch.nn.CrossEntropyLoss()
# 优化器 创建了一个 Adam 优化器,用于更新传入的模型参数,学习率设置为 0.001。梯度下载优化损失
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 定义模型
model = model.to(device)
# 复制当前模型参数
best_model_wts = copy.deepcopy(model.state_dict())
2.参数初始化
参数初始化包括最高参数初始化、训练集、测试集损失列表、训练集、测试集精度列表、当前时间
# 初始化参数
# 最高准确度
best_acc = 0.0
# 训练集损失列表
train_loss_all = []
# 测试集损失列表
test_loss_all = []
# 训练集准确度列表
train_acc_all = []
# 测试集准确度列表
test_acc_all = []
# 当前时间
since = time.time()
4)模型训练---反向传播
反向传播训练按批次进行训练,每一个批次都需要将一些参数进行初始化,以免干扰下一个批次的训练,具体初始化参数有:损失(训练集、测试集)、精度(训练集、测试集)、样本数量。
初始化完一批次的参数,就要在这一个批次进行反向传播。循环将数据特征和标签传入设备,打开训练模式,将特征在传入模型进行前向传播,获取分类概率,根据概论进行softmax分类,得到分类进行计算损失,反向传播之前必须将梯度置为0,然后进行反向传播,更新参数,对损失进行累加(计算平均损失)、对精度进行累加(计算平均精度)、对样本数进行累加()做分母。
# 按轮次训练模型
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(epoch, num_epochs-1))
print('-' * 10)
# 初始化参数
# 训练集损失
train_loss = 0.0
# 训练集精度
train_acc = 0.0
# 测试集损失
test_loss = 0.0
# 测试集精度
test_acc = 0.0
# 样本数量
train_num = 0
test_num = 0
for step,(b_x, b_y) in enumerate(train_data_loader):
b_x = b_x.to(device)# 将特征放到设备里面
b_y = b_y.to(device)# 将标签放入设备里面
model.train()# 模型打开训练模式
# 将数据放入模型进行前向传播
output = model(b_x)
# softmax 分类器,得到最大概率值 --- 即分类标签
pre_lab = torch.argmax(output, dim=1)
# 计算损失----根据标签 --- 是一个张量,表示差异程度
loss = criterion(output, b_y)
# 将梯度初始化为0 防止梯度积累
optimizer.zero_grad()
# 反向传播
loss.backward()
# 更新网络参数
optimizer.step()
# 对损失函数进行累加
train_loss += loss.item() * b_x.size(0)
# 如果预测正确,准确度加1
train_acc += torch.sum(pre_lab == b_y)
# 获取样本数
train_num += b_x.size(0)
4.模型验证
1)模型验证
验证基本上和训练一样,获取数据、打开验证模式、进行前向传播、softmax分类器、计算损失、损失累加、精度累加、样本累加。
不同的是,验证模型不用进行训练,所以不需要进行反向传播。
for step, (b_x, b_y) in enumerate(test_data_loader):
b_x = b_x.to(device)
b_y = b_y.to(device)
model.eval()
output = model(b_x)
pre_lab = torch.argmax(output, dim=1)
loss = criterion(output, b_y)
test_loss += loss.item() * b_x.size(0)
test_acc += torch.sum(pre_lab == b_y)
test_num += b_x.size(0)
2)损失值和精确度计算
在一批次迭代完成后计算平均损失值以及平均准确度可以用到上面累加获得的损失值和精度值进行计算。
# 计算并保存每一次迭代的loss值和精确率
train_loss_all.append(train_loss / train_num)
train_acc_all.append(train_acc / train_num)
test_loss_all.append(test_loss / test_num)
test_acc_all.append(test_acc / test_num)
3)保存最优模型
判断精确度是否最高,保存模型并计算时间
if test_acc_all[-1] > best_acc:
# 保存当前最优准确度
best_acc = test_acc_all[-1]
# 保存当前最优参数
best_model_wts = {name: param.clone().detach() for name, param in model.named_parameters()}
# 计算运行时间
time_use = time.time() - since
print('Training complete in {:.0f}m {:.0f}s'.format(time_use//60, time_use%60))
# 选择最优参数 加载最高准确率下的模型参数
model.load_state_dict(best_model_wts)
torch.save(best_model_wts, 'C:/Users/86184/PycharmProjects/pythonProject3/LeNet/best_model.pth')
# 将数据转换为dataframe
train_process = pd.DataFrame(data={
"epoch": range(num_epochs),
"train_loss_all": train_loss_all,
"train_acc_all": train_acc_all,
"test_loss_all": test_loss_all,
"test_acc_all": test_acc_all
})
return train_process
4)图像绘制
def matplot_acc_loss(train_process):
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(train_process['epoch'], train_process['train_loss_all'], 'ro-',label='train loss')
plt.plot(train_process['epoch'], train_process['test_loss_all'], 'bs-',label='test loss')
plt.legend(loc='best')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.subplot(1, 2, 2)
plt.plot(train_process['epoch'], train_process['train_acc_all'], 'ro-',label='train acc')
plt.plot(train_process['epoch'], train_process['test_acc_all'], 'bs-',label='test acc')
plt.legend(loc='best')
plt.xlabel('Epoch')
plt.ylabel('Acc')
plt.show()
5.实例化模型并运行
首先实例化LeNet模型,然后调用函数获得训练数据和测试数据,接着调用函数进行模型训练,最后调用画图函数进行绘制。
运行结束之后会得到一个pth文件,保存有最优参数。
.pth 文件是 PyTorch 用来保存模型参数的一种文件格式。这个文件包含了模型的权重和偏置,但通常不包含模型的结构信息。
if __name__ == '__main__':
# 将模型实例化
model = LeNet()
# 得到数据
train_data_loader, val_data_loader = train_val_data_process()
# 训练模型
train_process = train_model_process(model, train_data_loader, val_data_loader, 20)
# 画图
matplot_acc_loss(train_process)
得到结果

6.模型测试
1)处理数据
数据处理中和模型训练基本差不了多少,首先获取数据集,主要修改的为把train(训练)改为false,其他的基本不用变,在数据预处理中,将数据一捆的数量改为1,方便一个个进行测试,其他不变,最后返回测试数据。
def test_data_process():
# 定义数据集
test_data = FashionMNIST(root='./data',
train=False,
transform=transforms.Compose([transforms.Resize(size=28), transforms.ToTensor()]),
download=True)
test_data_loader = Data.DataLoader(dataset=test_data, batch_size=1, shuffle=True,
num_workers=2)
return test_data_loader
2)测试模型搭建
测试模型搭建的方法和训练模型不太一样,首先设置设置为cpu或者gpu,然后将模型精度和测试样本初始化为0,特别注意,with torch.no_grad():torch.no_grad() 上下文管理器用于暂时禁用梯度计算。在测试模型时,我们不需要进行反向传播,因此关闭梯度计算可以节省内存和计算资源。关闭梯度计算后,循环遍历测试数据,将数据和标签传入设备,设置为评估模式,然后进行前向传播,softmax分类器得到最大概率的下标,求精度总和和样本总和计算精度。
def test_model_process(model, test_data_loader):
# 设备:cup或者gpu
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
# 参数初始化
test_correct = 0
test_num = 0
# 只进行前向传播,不进行梯度,从而节省内存,加快运行速度
with torch.no_grad():
for test_data_x, test_data_y in test_data_loader:
test_data_x = test_data_x.to(device) # 将数据传输设备
test_data_y = test_data_y.to(device) # 将标签传入设备
# 测试模式
model.eval()
# 前向传播
output = model(test_data_x)
# 查找每一行最大值下标
pre_lab = torch.argmax(output, dim=1)
# 精确度总和
test_correct += torch.sum(pre_lab == test_data_y)
# 样本数总和
test_num += test_data_x.size(0)
# 计算测试准确率
test_acc = test_correct.double().item() / test_num
print("测试准确率:", test_acc)
3)测试实例化
测试实例化,和训练实例化基本差不多,加载模型
这一步很重要,从训练模型得到的pth文件中导入参数,
获取数据集,进行模型测试。
if __name__ == '__main__':
# 加载模型
model = LeNet()
model.load_state_dict(torch.load('best_model.pth', map_location=torch.device('cuda' if torch.cuda.is_available() else 'cpu'), weights_only=True))
# 获取数据集
test_data_loader = test_data_process()
# 模型测试
#test_model_process(model, test_data_loader)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
classes = ['T-shirt/top','Trouser','Pullover','Dress','Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
with torch.no_grad():
for b_x, b_y in test_data_loader:
b_x = b_x.to(device)
b_y = b_y.to(device)
model.eval()
output = model(b_x)
pre_lab = torch.argmax(output, dim=1)
result = pre_lab.item()
label = b_y.item()
print("预测值:", classes[result],"----------- 真实值", classes[label])
7.代码全展
注意:复制先看,在一个文件夹中新建三个py文件,将以下代码复制至对应py文件中。

如图在LeNet文件夹中,新建了model.py、model_test.py和model_train.py,其中plot可以不理,data文件夹是代码执行自己创建的,用于存放数据。
1)model.py
import torch
from torch import nn
from torchinfo import summary
# 类定义
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
# 定义卷积核参数:channel:通道数,out_channel:输出通道数,padding:边缘填充,stride:步长,kernel_size:卷积核大小
self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)# 第一层网络参数
self.sig = nn.Sigmoid() # 定义激活函数
self.pool1 = nn.AvgPool2d(kernel_size=2, stride=2) # 定义池化层
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5, padding=0) # 定义第二次卷积
self.pool2 = nn.AvgPool2d(kernel_size=2, stride=2) # 定义第二次池化层
# 最后一层池化要对特征值进行平铺
self.flatten = nn.Flatten() # 实将多维的输入数据转换为一维向量的层
# 定义全连接层
self.fc1 = nn.Linear(in_features=400,out_features= 120) # 定义线性全连接层,in_features:输入特征值,out_features:输出特征值
self.fc2 = nn.Linear(in_features=120,out_features= 84)
# 最后一层全连接层要使用分类算法,得到分类项
self.fc3 = nn.Linear(in_features=84,out_features= 10)
# 前向传播
# 输入数据x
def forward(self, x):
x = self.sig(self.conv1(x))# 第一层卷积层 + 激活函数
x = self.pool1(x) # 第一次池化层
x = self.sig(self.conv2(x)) # 第二次卷积层 + 激活函数
x = self.pool2(x) # 第二次池化层
x = self.flatten(x) # 数据平铺
x = self.fc1(x) # 第一次全连接
x = self.fc2(x) # 第二次全连接
x = self.fc3(x) # 第三次全连接
return x # 返回 分类值
2)model_train.py
from torchvision.datasets import FashionMNIST # 获取数据集
import numpy as np
from torchvision import transforms # 归一化数据集
import torch.utils.data as Data
from model import LeNet
from datetime import time
import torch
import time
import pandas as pd
import matplotlib.pyplot as plt
# 处理训练集和验证集
def train_val_data_process():
# 定义数据集
train_data = FashionMNIST(root='./data',
train=True,
transform=transforms.Compose([transforms.Resize(size=28), transforms.ToTensor()]),
download=True)
# 划分数据集,处理数据集
# 划分数据
train_data, val_data = Data.random_split(train_data,lengths=[round(0.8*len(train_data)),round(0.2*len(train_data))])
# 分批加载训练集和测试集
train_data_loader = Data.DataLoader(dataset=train_data, batch_size=64, shuffle=True,
num_workers=2)
val_data_loader = Data.DataLoader(dataset=val_data, batch_size=64, shuffle=True,
num_workers=2)
return train_data_loader, val_data_loader
# 训练模型
# 传入模型、训练数据、测试数据、训练批次
def train_model_process(model, train_data_loader, val_data_loader,num_epochs):
# 导入设备cup或者gpu
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# 优化器 创建了一个 Adam 优化器,用于更新传入的模型参数,学习率设置为 0.001。梯度下载优化损失
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 损失函数 初始化了一个交叉熵损失
criterion = torch.nn.CrossEntropyLoss()
# 定义模型
model = model.to(device)
# 复制当前模型参数
best_model_wts = {name: param.clone().detach() for name, param in model.named_parameters()}
# 初始化参数
# 最高准确度
best_acc = 0.0
# 训练集损失列表
train_loss_all = []
# 测试集损失列表
val_loss_all = []
# 训练集准确度列表
train_acc_all = []
# 测试集准确度列表
val_acc_all = []
# 当前时间
since = time.time()
# 按轮次训练模型
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(epoch, num_epochs-1))
print('-' * 10)
# 初始化参数
# 训练集损失
train_loss = 0.0
# 训练集精度
train_acc = 0.0
# 测试集损失
val_loss = 0.0
# 测试集精度
val_acc = 0.0
# 样本数量
train_num = 0
val_num = 0
for step,(b_x, b_y) in enumerate(train_data_loader):
b_x = b_x.to(device)# 将特征放到设备里面
b_y = b_y.to(device)# 将标签放入设备里面
model.train()# 模型打开训练模式
# 将数据放入模型进行前向传播
output = model(b_x)
# softmax 分类器,得到最大概率值 --- 即分类标签
pre_lab = torch.argmax(output, dim=1)
# 计算损失----根据标签 --- 是一个张量,表示差异程度
loss = criterion(output, b_y)
# 将梯度初始化为0 防止梯度积累
optimizer.zero_grad()
# 反向传播
loss.backward()
# 更新网络参数
optimizer.step()
# 对损失函数进行累加
train_loss += loss.item() * b_x.size(0)
# 如果预测正确,准确度加1
train_acc += torch.sum(pre_lab == b_y)
# 获取样本数
train_num += b_x.size(0)
for step, (b_x, b_y) in enumerate(val_data_loader):
b_x = b_x.to(device)
b_y = b_y.to(device)
model.eval()
output = model(b_x)
pre_lab = torch.argmax(output, dim=1)
loss = criterion(output, b_y)
val_loss += loss.item() * b_x.size(0)
val_acc += torch.sum(pre_lab == b_y)
val_num += b_x.size(0)
# 计算并保存每一次迭代的loss值和精确率
train_loss_all.append(train_loss / train_num)
train_acc_all.append(train_acc / train_num)
val_loss_all.append(val_loss / val_num)
val_acc_all.append(val_acc / val_num)
#打印
print("{}Train Loss:{:.4f} Train Acc:{:.4f} Test Loss:{:.4f} Test Acc:{:.4f}".format(
epoch, train_loss_all[-1], train_acc_all[-1], val_loss_all[-1], val_acc_all[-1]
))
if val_acc_all[-1] > best_acc:
# 保存当前最优准确度
best_acc = val_acc_all[-1]
# 保存当前最优参数
best_model_wts = {name: param.clone().detach() for name, param in model.named_parameters()}
# 计算运行时间
time_use = time.time() - since
print('Training complete in {:.0f}m {:.0f}s'.format(time_use//60, time_use%60))
# 选择最优参数 加载最高准确率下的模型参数
torch.save(best_model_wts, 'C:/Users/86184/PycharmProjects/pythonProject3/LeNet/best_model.pth')
"""model.load_state_dict(best_model_wts)
torch.save(best_model_wts, 'C:/Users/86184/PycharmProjects/pythonProject3/LeNet/best_model.pth')
"""
# 将数据转换为dataframe 用于最后的可视化
train_process = pd.DataFrame(data={
"epoch": range(num_epochs),
"train_loss_all": train_loss_all,
"train_acc_all": train_acc_all,
"val_loss_all": val_loss_all,
"val_acc_all": val_acc_all
})
return train_process
# 可视化loss和精确度
def matplot_acc_loss(train_process):
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(train_process['epoch'], train_process['train_loss_all'], 'ro-',label='train loss')
plt.plot(train_process['epoch'], train_process['val_loss_all'], 'bs-',label='test loss')
plt.legend(loc='best')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.subplot(1, 2, 2)
plt.plot(train_process['epoch'], train_process['train_acc_all'], 'ro-',label='train acc')
plt.plot(train_process['epoch'], train_process['val_acc_all'], 'bs-',label='test acc')
plt.legend(loc='best')
plt.xlabel('Epoch')
plt.ylabel('Acc')
plt.show()
if __name__ == '__main__':
# 将模型实例化
model = LeNet()
# 得到数据
train_data_loader, val_data_loader = train_val_data_process()
# 训练模型
train_process = train_model_process(model, train_data_loader, val_data_loader, 20)
# 画图
matplot_acc_loss(train_process)
3)model_test.py
from model import LeNet
from torchvision.datasets import FashionMNIST
import torch
import torch.utils.data as Data
from torchvision import transforms
# 处理测试集
def test_data_process():
# 定义数据集
test_data = FashionMNIST(root='./data',
train=False,
transform=transforms.Compose([transforms.Resize(size=28), transforms.ToTensor()]),
download=True)
test_data_loader = Data.DataLoader(dataset=test_data, batch_size=1, shuffle=True,
num_workers=2)
return test_data_loader
def test_model_process(model, test_data_loader):
# 设备:cup或者gpu
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
# 参数初始化
test_correct = 0
test_num = 0
# 只进行前向传播,不进行梯度,从而节省内存,加快运行速度
with torch.no_grad():
for test_data_x, test_data_y in test_data_loader:
test_data_x = test_data_x.to(device) # 将数据传输设备
test_data_y = test_data_y.to(device) # 将标签传入设备
# 测试模式
model.eval()
# 前向传播
output = model(test_data_x)
# 查找每一行最大值下标
pre_lab = torch.argmax(output, dim=1)
# 精确度总和
test_correct += torch.sum(pre_lab == test_data_y)
# 样本数总和
test_num += test_data_x.size(0)
# 计算测试准确率
test_acc = test_correct.double().item() / test_num
print("测试准确率:", test_acc)
# 模型测试
if __name__ == '__main__':
# 加载模型
model = LeNet()
model.load_state_dict(torch.load('best_model.pth', map_location=torch.device('cuda' if torch.cuda.is_available() else 'cpu'), weights_only=True))
# 获取数据集
test_data_loader = test_data_process()
# 模型测试
#test_model_process(model, test_data_loader)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)
classes = ['T-shirt/top','Trouser','Pullover','Dress','Coat', 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
with torch.no_grad():
for b_x, b_y in test_data_loader:
b_x = b_x.to(device)
b_y = b_y.to(device)
model.eval()
output = model(b_x)
pre_lab = torch.argmax(output, dim=1)
result = pre_lab.item()
label = b_y.item()
print("预测值:", classes[result],"----------- 真实值", classes[label])
魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐


所有评论(0)