LeNet网络搭建
LeNet网络搭建
项目准备
首先,在某个磁盘中准备一个文件夹,就叫LeNet吧,作为项目的工程目录,然后配置好python环境。
python环境版本
- python==3.8
- Pytorch==1.10.1
- Cudatookit==11.3
- Cudnn==8.2
- torchsummary==1.5.1
- numpy==1.23.2
- pandas==1.3.4
- matplotlib==3.5.0
- sklearn==0.0
定义模型类
创建一个model.py
模型类。
# --model.py--
# 导包
import torch
from torch import nn
from torchsummmary import summary # 展现神经网络的参数
class LeNet(nn.Module):
def __init__(self):
super(LeNet,self).__init__() # 要与类名相同
# 第一层:定义卷积,输入通道数1,输出通道数6,卷积核大小6*6,步长不写默认1,填充2
self.c1 = nn.Conv2d(in_channels=1,out_channels=6,kernel_size=5,padding=2)
# 定义激活函数 sigmoid
self.sig = nn.Sigmoid()
# 第二层: 定义平均池化 感受野(核)是2*2,步长为2
self.s2 = nn.AvgPool2d(kernel_size=2,stride=2)
# 第三层:卷积层
self.c3 = nn.Conv2d(in_channels=6,out_channels=16,kernel_size=5)
# 第四层:定义平均池化
self.s4 = nn.AvgPool2d(kernel_size=2,stride=2)
# 定义平展层
self.flatten = nn.Flatten()
# 第五层: 线性全连接层(输入特征图,输出特征图)
self.f5 = nn.Linear(400,120)
# 第六层: 全连接层
self.f6 = nn.Linear(120,84)
# 第七层:全连接层
self.f7 = nn.Linear(84,10)
# 定义前向传播
def forward(self,x):
# x: 接受输入的数据
x = self.c1(x) # 第一层卷积
x = self.sig(x) # sigmoid激活函数
x = self.s2(x) # 第二层平均池化
x = self.c3(x) # 第三层卷积
x = self.sig(x) # sigmoid激活函数
x = self.s4(x) # 第四层池化
x = self.flatten(x) # 平展层
x = self.f5(x) #第五层全连接层
x = self.f6(x) #第六层全连接层
x = self.f7(x) #第七层全连接层
return x
# 在主函数中,测试下模型是否可行
if __name__ == "__main__":
# 定义设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 实例化模型,并且放进设备中
model = LeNet().to(device)
# 输出模型的数据
# summary(模型类,(输入的特征大小))
print(summary(model,(1,28,28)))
定义训练类
数据准备
在进行训练模型前,需要提前准备好数据,来给模型训练。
这里准备的是服装
的FashionMNIST数据:
- 数据总量:七万张数据集
- 数据集大小:28*28*1
- 分类:10分类
- 训练集:六万
- 测试集:一万
创建一个plot.py
文件,用来加载数据,并且绘制出来。
# --plot.py--
# torchvision.datasets库中包含了很多数据集
from torchvision.datasets import FashionMNIST
from torchvision import transforms # 处理数据集,来归一化
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt
train_data = FashionMNIST(
root='./data', # 指定数据集要下载的路径
train=True, # 要训练集的数据
# 将数据进行归一化操作
tranform=transforms.Compose([
transforms.Resize(size=224), # 将数据转成224大小
transforms.ToTensor() # 将数据转成张量(Tensor)的形式,方面后续模型的应用
]),
download=True # 开始下载数据
)
# 通过DataLoader()类来加载数据集
train_loader = Data.DataLoader(
dataset=train_data,# 要加载的数据
batch_size=64,# 每轮的训练的数据个数是64个
shuffle=True, # 数据是否打乱
num_workders=0 # 加载数据的进程
)
# ---- 数据展示部分 ----
# 获得一个Batch(批次)的数据
# b_x:图片数据 b_y:标签
for step,(b_x,b_y) in enumerate(train_loader):
if step > 0:
break
# 比如64*224*224*1 将1移除,(批次,长,宽,通道数)
batch_x = b_x.squeeze().numpy() # 将四维张量移除第1维(第一个类别),并转换成numpy数据
batch_y = b_y.numpy() # 将张量转换numpy数组,方便画图
class_label = train_data.classes # 训练集的标签
print(class_label)
# 绘制一个Batch可视化的图像
plt.figure(figsize=(12,5))
for ii ini np.arange(len(batch_y)):
plt.subplot(4,16,ii + 1)
plt.imshow(batch_x[ii, :, :],cmap=plt.cm.gray)
plt.title(class_label[batch_y[ii]],size=10)
plt.axis("off")
plt.subplots_adjust(wspace=0.05)
plt.show()
上诉代码如果下载成功(只下载了train数据),那么在项目的根目录下就会创建以下文件目录:
LeNet
-data
-FashionMNIST
-raw
-train-images-idx3-ubyte
-train-images-idx3-ubyte.gz
模型训练的代码模板
训练集模板
这里定义训练集模板,是为了未来的训练方便使用的,因为训练模型的时候,基本上就按这套模板来进行训练,也就说大部分代码结构是一致的。
创建model_train.py
文件做模型训练
import torch
from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.utils.data as Data
import numpy as np
import matplotlib.pyplot as plt
import torch.nn as nn
import pandas as pd
from model import LeNet # 导入自定义模型
# 处理训练集(train) 和 验证集(val)
# 每训练一轮就验证一轮
def train_val_data_process():
# 加载训练数据
train_data = FashionMNIST(
root='./data', # 指定数据集要下载的路径
train=True, # 要训练集的数据
# 将数据进行归一化操作
tranform=transforms.Compose([
transforms.Resize(size=28), # 将数据转成28大小
transforms.ToTensor() # 将数据转成张量(Tensor)的形式,方面后续模型的应用
]),
download=True # 开始下载数据
)
# 划分训练集和验证集 将数据集中的数据随机划分为训练集和验证集
train_data, val_data = Data.random_split(
train_data, # 要划分的数据集
[
round(0.8 * len(train_data)), # 将80%的数据集用作训练集
round(0.2 * len(train_data)) # 将20%的数据集用作验证集
]
)
# 加载数据
train_dataloader = Data.DataLoader(
dataset=train_data,
batch_size=32, # 每轮训练的批次
shuffle=True,
num_workders=2
)
val_dataloader = Data.DataLoader(
dataset=train_data,
batch_size=32, # 每轮训练的批次
shuffle=True,
num_workders=2
)
return train_dataloader, val_dataloader
# 模型训练
def train_model_process(model, train_dataloader, val_dataloader, num_epochs):
# model:训练的模型,train_dataloader:训练集,val_dataloader:验证集,num_epochs训练轮数
# 指定使用的设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# 定义优化器Adam(模型的参数,学习率)
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 定义损失函数 交叉熵损失
criterion = nn.CrossEntropyLoss()
# 将模型放入到设备当中 进行训练
model = model.to(device)
# 复制当前模型的参数
best_model_wts = copy.deepcopy(model.state_dict())
# -- 初始化参数,来记录参数 --
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_corrents = 0 # 初始化训练集精度
val_loss = 0.0 # 初始化验证集损失值
val_corrents = 0 # 初始化验证集精度
train_num = 0 # 该轮次的训练集样本数量
val_num = 0 # 该轮次的验证集样本数量
# 取出每轮中的数据集 进行 训练和计算
for step, (b_x, b_y) in enumerate(train_dataloader):
# b_x:128*28*28*1(批次,长,宽,通道)
# b_y:128*label
b_x = b_x.to(device) # 将训练数据放入设备
b_y = b_y.to(device) # 将标签数据放入设备
model.train() # 开启模型训练模型
# 将每轮数据中的标签标签(batch)放入模型中进行前向传播,得到输出(不是结果)
output = model(b_x)
# 查找每一行中最大值对应的行标(索引),即概率最大
pre_lab = torch.argmax(output, dim=1) # 里面有个softmax的过程
# 计算每个batch的损失
loss = criterion(output, b_y) # 通过模型输出和标签来计算损失值
# 将梯度初始化为0,每轮训练完后
optimzer.zero_grad()
# 反向传播计算
loss.backward()
# 根据反向传播的梯度信息来更新网络参数,降低loss函数计算值
optimzer.step()
# 对损失函数进行累加
train_loss += loss.item() * b_x.size(0)
# 如果预测正确,则准确度+1
train_corrects += torch.sum(pre_lab == b_y.data)
# 当前用于训练的样本数量
train_num += b_x.size(0)
# 模型验证
for step, (b_x, b_y) in enumerate(val_dataloader):
# 将特征放入到验证设备中
b_x = b_x.to(device)
# 将标签放入到验证设备中
b_y = b_y.to(device)
model.eval() # 设置模型为评估(验证)模式
# 前向传播过程,输入一个batch,输出该batch对应的预测
out_put = model(b_x)
# 查找每一行中最大值对应的行标,也就是正确的类别
pre_lab = torch.argmax(output, dim=1)
# 计算每一个batch的损失函数
loss = criterion(output, b_y)
# 对损失函数进行累加
val_loss += loss.item() * b_x.size(0)
# 如果预测正确,则准确度+1
val_corrects += torch.sum(pre_lab == b_y.data)
# 当前用于验证的样本数量
val_num += b_x.size(0)
# 计算并保存每一次迭代的loss值和准确率
train_loss_all.append(train_loss / train_num) # 训练集本轮的平均loss值
train_acc_all.append(train_corrects.double().item() / train_num) # 训练集本轮的平均准确率
val_loss_all.append(val_loss / val_num) # 验证集本轮的平均loss值
val_acc_all.append(val_corrects.double().item() / val_num) # 验证集本轮的平均准确率
print("{} train loss:{:.4f} train acc: {:.4f}".format(epoch, train_loss_all[-1], train_acc_all[-1]))
print("{} val loss:{:.4f} val acc: {:.4f}".format(epoch, val_loss_all[-1], val_acc_all[-1]))
# val_acc_all[-1] 表示当前轮次的准确度
# 寻找最高准确度的权重参数
if val_acc_all[-1] > best_acc: # 如果本轮的准确度比之前的高
best_acc = val_acc_all[-1] # 那就更新准确度
# 保存当前最高 准确度的参数
best_model_wts = copy.deepcopy(model.state_dict())
# 计算训练的耗时 (当前时间-之前的时间)
time_use = time.time() - since
print("训练耗费的时间{:.0f}m{:.0f}s".format(time_use//60,time_use%60))
# 将最优模型参数best_model_wts保存
torch.save(best_model_wts,'best_model.pth') # 保存模型参数 pth是权重文件的后缀
# 保存训练好的模型参数
train_process = pd.DataFrame(data={
"epoch":range(num_epochs),
"train_loss_all":train_loss_all,
"val_loss_all":val_loss_all,
"train_acc_all":train_acc_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="val loss")
plt.legend() # 图例
plt.xlabel("epoch") # x轴标签
plt.ylabel("loss") # y轴标签
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="val acc")
plt.xlabel("epoch") # x轴标签
plt.ylabel("acc") # y轴标签
plt.legend() # 图例
plt.show() # 显示图像
if __name__ == "__main__":
# 将自定义的模型 实例化
LeNet = LeNet()
# 加载数据集
train_dataloader,val_dataloader = train_val_data_process()
# 训练模型 (要训练的模型,训练集,测试集,训练轮数)
train_process = train_model_process(LeNet,train_dataloader,val_dataloader,20)
# 绘制图像
matplot_acc_loss(train_process)
以上代码,可以根据具体情况(硬件设备),适当的更改 train_val_data_process()函数
中的 train_dataloader
和 val_dataloader
的 batch_size
和 num_workders
执行以上代码,本地会多出一个best_model.pth
文件,里面保存的是模型的参数。
测试集模板
创建一个model_test.py
文件
import torch
import torch.utils.data as Data
from torchvision import transforms
from torchvision.datasets importn FashionMNIST
from model import LeNet # 导入自定义的模型
# 定义一个方法,用来加载测试数据
def test_data_process():
# 加载测试数据
test_data = FashionMNIST(
root='./data', # 指定数据集要下载的路径
train=False, # 不使用训练集
# 将数据进行归一化操作
tranform=transforms.Compose([
transforms.Resize(size=28), # 将数据转成28大小
transforms.ToTensor() # 将数据转成张量(Tensor)的形式,方面后续模型的应用
]),
download=True # 开始下载数据
)
# 通过DataLoader类来加载测试数据
test_dataloader = Data.DataLoader(
dataset=test_data,
batch_size=1, # 一张一张的测试
shuffle=True,
num_workders=0
)
return test_dataloader
# 测试模型
def test_model_process(model, test_dataloader):
# 指定设备
device = "cuda" if torch.cuda.is_available() else "cpu"
# 将模型放入设备中 测试训练
model = model.to(device)
# -- 初始化参数 --
test_corrects = 0.0 # 测试准确度
test_num = 0 # 测试样本的数量
# 只进行前向传播,不计算梯度,从而节省内存,加快运行速度
with torch.no_grad(): # 将梯度置为0
for test_data_x,test_data_y in test_dataloader: # 测试的数据 和 标签
# 上面设置了批次为1
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) # 里面有个softmax的过程
# 模型预测的结果 与 标签 比较,也就是比对答案,对,就test_corrects+1
test_corrects += torch.sum(pre_lab == test_data_y.data)
# 将所有的测试样本进行累加
test_num += test_data_x.size(0)
# 计算测试准确率
test_acc = test_corrects.double().item() / test_num
print("测试的准确率为: ",test_acc)
if __name__ == "__main__":
# 加载模型
model = LeNet()
# 加载训练好的模型参数
model.load_state_dict(torch.load('best_model.pth'))
# 加载测试的数据集
test_dataloader = test_data_process()
# 加载模型测试的函数,传入要训练的模型,和 测试的数据集
test_model_process(model,test_dataloader)
查看测试过程中的具体数值
# ...以上代码与上面相同
if __name__ == "__main__":
# 加载模型
model = LeNet()
# 加载训练好的模型参数文件
model.load_state_dict(torch.load('best_model.pth'))
# 加载测试集数据
test_dataloader = test_data_process()
# 指定测试所用的设备
device = "cuda" if torch.cuda.is_available() else "cpu"
# 将模型放入到设备当中
model = model.to(device)
# 衣服的类别类别,顺序不可以换
classes = ['T-shirt/top','Trouser','Pullover','Dress','Coat','Sandal','Shirt','Sneaker','Bag','Ankle boot']
# 将梯度设置为0
with torch.no_grad():
# 遍历测试集中的 数据 和 标签
for b_x,b_y in test_dataloader:
b_x = b_x.to(device) # 将测试数据放入设备
b_y = b_y.to(device) # 将标签数据放入设备
# 设置模型为训练模型
model.eval()
# 模型推理的结果
output = model(b_x)
# 获取最大值的下标,也就是预测结果,结果是个Tensor张量
pre_lab = torch.argmax(output,dim=1) # 里面有softmax的过程
# 取出张量中的下标
result = pre_lab.item() # 预测值的索引
label = b_y.item() # 标签的索引
print("预测值: ",classes[result],"------","真实值: ",classes[label])