本实例的功能是创建LeNet-5模型,对MNIST手写数字数据集进行训练和测试,并在训练过程中绘制损失曲线和准确率曲线。
实例4-2:创建并使用LeNet-5模型(源码路径:daima\4\can.py)
1. LeNet-5模型介绍
LeNet-5是一种经典的卷积神经网络(CNN)模型,由Yann LeCun等人于1998年提出,主要用于手写数字识别任务。它是第一个在实际任务中取得成功的卷积神经网络模型之一,为后来的深度学习发展奠定了基础。
LeNet-5模型的设计主要基于两个关键思想:稀疏连接和权值共享,它采用了一种层次化的结构,通过卷积、池化和全连接层来提取特征并进行分类。
下面是LeNet-5模型的详细结构:
(1)输入层
- 输入图像为灰度图像,大小为32x32。
(2)第一层:卷积层
- 输入大小:32x32x1
- 卷积核大小:5x5
- 卷积核数量:6
- 激活函数:ReLU(Rectified Linear Unit)
- 输出大小:28x28x6
(3)第二层:池化层
- 输入大小:28x28x6
- 池化窗口大小:2x2
- 池化方式:最大池化(Max Pooling)
- 输出大小:14x14x6
(4)第三层:卷积层
- 输入大小:14x14x6
- 卷积核大小:5x5
- 卷积核数量:16
- 激活函数:ReLU
- 输出大小:10x10x16
(5)第四层:池化层
- 输入大小:10x10x16
- 池化窗口大小:2x2
- 池化方式:最大池化
- 输出大小:5x5x16
(6)第五层:全连接层
- 输入大小:5x5x16=400
- 输出大小:120
- 激活函数:ReLU
(7)第六层:全连接层:
- 输入大小:120
- 输出大小:84
- 激活函数:ReLU
(8)第七层:全连接层(输出层)
- 输入大小:84
- 输出大小:10(对应10个类别,用于手写数字的分类)
- 激活函数:无(使用softmax激活函数进行多类别分类)
LeNet-5模型的整体结构是由多个卷积层、池化层和全连接层组成的,其中卷积层和池化层用于特征提取,全连接层用于分类。通过稀疏连接和权值共享的设计,LeNet-5模型在参数量和计算复杂度上较小,适用于处理小尺寸图像的任务。
2. 具体实现
实例文件can.py的具体实现流程如下所示。
(1)导入所需的库和模块,对应的实现代码如下所示:
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision.datasets import MNIST
from torchvision.transforms import transforms
import matplotlib.pyplot as plt
(2)定义LeNet-5模型,具体说明如下:
- LeNet5类继承自nn.Module,是LeNet-5模型的定义。
- 模型包括两个部分:特征提取层(features)和分类器层(classifier)。
- 特征提取层包括两个卷积层和池化层,用于提取图像的特征。
- 分类器层包括三个全连接层,用于对提取的特征进行分类。
- forward方法定义了前向传播的过程。
对应的实现代码如下所示:
# 定义LeNet-5模型
class LeNet5(nn.Module):
def __init__(self, num_classes=10):
super(LeNet5, self).__init__()
self.features = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5, stride=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5, stride=1),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.classifier = nn.Sequential(
nn.Linear(16 * 4 * 4, 120),
nn.ReLU(),
nn.Linear(120, 84),
nn.ReLU(),
nn.Linear(84, num_classes)
)
def forward(self, x):
x = self.features(x)
x = torch.flatten(x, 1)
x = self.classifier(x)
return x
(3)数据预处理和加载,首先定义了数据的预处理操作,包括将图像转换为张量、归一化等;然后使用MNIST类加载训练集和测试集,并应用预处理操作。对应的实现代码如下所示。
# 数据预处理和加载
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = MNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = MNIST(root='./data', train=False, download=True, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)
(4)创建模型和优化器,首先创建LeNet-5模型的实例,然后定义损失函数(交叉熵损失)和优化器(Adam优化器)。对应的实现代码如下所示。
# 创建模型和优化器
model = LeNet5()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
(5)开始训练模型,具体实现流程如下:
- 使用循环迭代训练模型。
- 将模型设为训练模式,遍历训练集的批次,计算训练损失和训练准确率,并更新模型的参数。
- 将模型设为评估模式,遍历测试集的批次,计算测试损失和测试准确率。
对应的实现代码如下所示。
# 训练模型
num_epochs = 10
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)
train_losses = []
test_losses = []
train_accuracies = []
test_accuracies = []
for epoch in range(num_epochs):
model.train()
running_loss = 0.0
correct_train = 0
total_train = 0
for images, labels in train_loader:
images = images.to(device)
labels = labels.to(device)
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted_train = torch.max(outputs.data, 1)
total_train += labels.size(0)
correct_train += (predicted_train == labels).sum().item()
train_loss = running_loss / len(train_loader)
train_accuracy = 100 * correct_train / total_train
train_losses.append(train_loss)
train_accuracies.append(train_accuracy)
# 在测试集上评估模型
model.eval()
running_loss = 0.0
correct_test = 0
total_test = 0
with torch.no_grad():
for images, labels in test_loader:
images = images.to(device)
labels = labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
running_loss += loss.item()
_, predicted_test = torch.max(outputs.data, 1)
total_test += labels.size(0)
correct_test += (predicted_test == labels).sum().item()
test_loss = running_loss / len(test_loader)
test_accuracy = 100 * correct_test / total_test
test_losses.append(test_loss)
test_accuracies.append(test_accuracy)
print(
f"Epoch {epoch + 1}/{num_epochs} Train Loss: {train_loss:.4f} Train Accuracy: {train_accuracy:.2f}% Test Loss: {test_loss:.4f} Test Accuracy: {test_accuracy:.2f}%")
(6)绘制损失曲线和准确率曲线,使用Matplotlib库绘制训练过程中的损失曲线和准确率曲线。对应的实现代码如下所示。
# 绘制损失曲线和准确率曲线
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(range(1, num_epochs + 1), train_losses, label='Train')
plt.plot(range(1, num_epochs + 1), test_losses, label='Test')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(range(1, num_epochs + 1), train_accuracies, label='Train')
plt.plot(range(1, num_epochs + 1), test_accuracies, label='Test')
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.legend()
plt.tight_layout()
plt.show()
(7)输出最终准确率,打印输出测试集上的最终准确率。对应的实现代码如下所示。
# 最终准确率
print(f"Final Accuracy on test set: {test_accuracies[-1]:.2f}%")
执行后会使用Matplotlib库绘制训练过程中的损失曲线和准确率曲线图,效果如图4-3所示。