软件环境:Jupyter notebook
硬件环境:GPU:RTX4060
实验数据集:Fashion-MNIST
本文主要实现了经典入门神经网络LeNet结构
Lenet是一个 7 层的神经网络,包含 3 个卷积层,2 个池化层,1 个全连接层。其中所有卷积层的所有卷积核都为 5x5,步长 strid=1,池化方法都为平均pooling,激活函数为 Sigmoid,网络结构如下:
在此不对LeNet的结构做详细解读 直接开始复现
1.导入必要的包
import torch
from torch import nn
from torchvision.datasets import FashionMNIST
from torchvision import transforms
import torch.utils.data as Data
import copy
import time
2.定义LeNet结构
首先用Class定义一个LeNet类,继承自pytorch的nn.moudle模块。
直接调用Conv2d函数定义卷积层,采用Sigmod函数做激活函数,采用AvgPool2d做平均池化。然后定义三个线性卷积层,完成400个输入到10个输出的连接
然后在类中定义forward前向传播函数,将所有的卷积层池化层激活函数和线性全连接层做一个连接 完全前向传播。
class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2)
self.sig = nn.Sigmoid()
self.pool1 = nn.AvgPool2d(stride=2, kernel_size=2)
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
self.pool2 = nn.AvgPool2d(stride=2, kernel_size=2)
self.flatten = nn.Flatten()
self.Lin1 = nn.Linear(400, 120)
self.Lin2 = nn.Linear(120, 84)
self.Lin3 = nn.Linear(84, 10)
def forward(self, x):
x = self.conv1(x)
x = self.sig(x)
x = self.pool1(x)
x = self.conv2(x)
x = self.sig(x)
x = self.pool2(x)
x = self.flatten(x)
x = self.Lin1(x)
x = self.Lin2(x)
x = self.Lin3(x)
return x
3.相关函数定义
train_val_data_process
transform调用了transforms里的剪裁函数 对图像进行裁剪成28尺寸 并转换成张量。
train_data的直接调用datrasets数据集中的FashionMNIST数据集,root=指定下载路径,train=ture代表下载的是训练集,download表示确定下载。
定义train_size=0.8*int(len(train_data))完成数据集的划分,80%用于训练,20%用于测试,使用Data.random_split完成随即划分。继续定义train_dataloader和val_dataloader用于加载数据,指定批次大小为128,8线程并行处理,shuffle=True代表打乱。
def train_val_data_process():
transform = transforms.Compose([transforms.Resize(size=28), transforms.ToTensor()])
train_data = FashionMNIST(root="D:/jupyter/sklearn", train=True, transform=transform, download=True)
train_size = int(0.8 * len(train_data))
val_size = len(train_data) - train_size
train_data, val_data = Data.random_split(train_data, [train_size, val_size])
train_dataloader = Data.DataLoader(dataset=train_data, batch_size=128, shuffle=True, num_workers=8)
val_dataloader = Data.DataLoader(dataset=val_data, batch_size=128, shuffle=True, num_workers=8)
return train_dataloader, val_dataloader
接下来定义训练过程 也是训练的主函数 包括参数model,训练轮次,dataloader以及保存路径
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")此句表示定义训练设备为gpu(如果gpu可用),反之使用cpu完成训练。定义优化器为Adam优化器,学习率为1e-3。使用交叉熵函数作为损失函数。model=model.to(device)表示将模型移到gpu上。
使用循环开始迭代 ,每次迭代时先输入迭代轮次和当前的时间。初始化训练损失和准确率都为0
使用model.train()将模型转换为训练模式 内层循环遍历数据集所有数据 optimizer.zero_grad()完成梯度清零,防止梯度累计最终导致梯度爆炸。输出output并用loss记录交叉熵损失。loss.backward()根据loss值完成反向传播,optimizer.step()表示根据反向传播的结果进行参数的更新。如此完成训练。
使用model.eval()转换为验证模式。验证模式只需要计算损失和准确率即可,不需要进行反向传播和参数更新。其中准确率的计算如下:
torch.sum(preds == b_y.data)
:比较模型预测的类别与实际标签是否相等,返回一个布尔值的张量。torch.sum
统计 True 的数量,即正确预测的数量。train_corrects
记录训练集上的正确预测总数。train_acc
计算训练集上的准确率,即正确预测的数量除以训练集样本总数。
def train_model_process(model, epochs, train_dataloader, val_dataloader, save_path='LeNet/best_model.pth'):
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)
loss_fn = nn.CrossEntropyLoss()
model = model.to(device)
best_model_wts = copy.deepcopy(model.state_dict())
best_acc = 0.0
for epoch in range(epochs):
start_time = time.time()
print("第 {}/{} 次迭代".format(epoch, epochs - 1))
print(time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(start_time)))
train_loss = 0.0
train_corrects = 0
val_loss = 0.0
val_corrects = 0
# 训练阶段
model.train()
for step, (b_x, b_y) in enumerate(train_dataloader):
b_x = b_x.to(device)
b_y = b_y.to(device)
optimizer.zero_grad()
output = model(b_x)
loss = loss_fn(output, b_y)
loss.backward()
optimizer.step()
train_loss += loss.item() * b_x.size(0)
_, preds = torch.max(output, 1)
train_corrects += torch.sum(preds == b_y.data)
# 验证阶段
model.eval()
with torch.no_grad():
for step, (b_x, b_y) in enumerate(val_dataloader):
b_x = b_x.to(device)
b_y = b_y.to(device)
output = model(b_x)
loss = loss_fn(output, b_y)
val_loss += loss.item() * b_x.size(0)
_, preds = torch.max(output, 1)
val_corrects += torch.sum(preds == b_y.data)
# 计算准确率
train_acc = train_corrects.double() / len(train_dataloader.dataset)
val_acc = val_corrects.double() / len(val_dataloader.dataset)
print("训练 Loss: {:.4f}, 准确率: {:.4f}".format(train_loss, train_acc))
print("验证 Loss: {:.4f}, 准确率: {:.4f}".format(val_loss, val_acc))
# 输出本次epoch的时间
end_time = time.time()
elapsed_time = end_time - start_time
print("耗时: {:.0f}m {:.0f}s".format(elapsed_time // 60, elapsed_time % 60))
# 保存最佳模型
if val_acc > best_acc:
best_acc = val_acc
best_model_wts = copy.deepcopy(model.state_dict())
# 加载并保存最佳模型
model.load_state_dict(best_model_wts)
torch.save(model.state_dict(), save_path)
5.实例化LeNet类并运行
实例化一个LeNet_model,完成数据集的划分和调用
这里我只训练了二十轮,大家可以自行修改epochs的值训练到模型收敛,收敛后准确率应该在0.86。
LeNet_model = LeNet()
train_dataloader, val_dataloader = train_val_data_process()
train_model_process(LeNet_model, epochs=10, train_dataloader=train_dataloader, val_dataloader=val_dataloader)
6.结果展示
共完成了20次训练 可以看到模型收敛 准确率基本在0.86 没有出现过拟合与欠拟合的问题