基于LeNet5网络的MNIST数据集分类实验报告
基于LeNet5网络的MNIST数据集分类实验报告
实验目的
理解卷积神经网络:学习卷积神经网络(CNN)的基本原理和架构,包括卷积层、池化层和全连接层的作用。
数据预处理:掌握如何对图像数据进行预处理,包括标准化、缩放和增强,以适应卷积神经网络的输入要求。
模型构建:学习如何使用深度学习框架构建卷积神经网络模型,用于MNIST数据集分类。
模型训练与调优:实践如何训练卷积神经网络模型,包括选择损失函数、优化器,调整学习率,以及应用技术如正则化和dropout来防止过拟合。
性能评估:学习如何评估模型的分类性能,使用诸如准确率、召回率等指标来衡量。
环境
硬件:12th Gen Intel(R) Core(TM) i5-12500H 2.50 GHz
软件:PyCharm Community Edition 2023.2.1
内容与要求
理论学习:学习卷积神经网络(CNN)的基础理论,包括卷积层、池化层、全连接层的作用,以及激活函数的选择。
环境搭建:设置深度学习环境,安装必要的软件和库,准备实验所需的工具和框架(如TensorFlow或PyTorch)。
数据处理:加载数据集,执行必要的数据预处理步骤,如图像大小调整、归一化,以及数据增强。
模型构建:使用深度学习框架构建卷积神经网络模型,定义网络结构,包括卷积层、池化层和全连接层。
模型训练与测试:训练模型以区分猫和狗的图像,并在测试集上评估模型的性能,记录并分析准确率、损失等指标。
结果分析:对训练过程和最终测试结果进行分析,探讨模型的性能并识别可能的改进空间。
过程与分析
准备工作
1.导入模块功能
导入必要的库:首先导入了PyTorch框架,这是进行深度学习开发的主要库。此外,还导入了用于构建神经网络模型的torch.nn模块,用于优化模型的torch.optim模块,以及用于加载和预处理数据集的torchvision库。
数据加载和预处理:通过torchvision.datasets和torchvision.transforms,代码可以加载图像数据集,并对其进行预处理,如缩放、归一化等,以便模型能够更好地学习。
构建神经网络模型:使用torch.nn模块,可以定义一个或多个神经网络层,构建一个完整的模型。
模型优化:通过torch.optim模块,可以选择一个优化器(如SGD、Adam等),用于在训练过程中更新模型的权重,以最小化损失函数。
可视化:导入的matplotlib.pyplot库用于在训练过程中绘制损失曲线或其他可视化图表,帮助理解模型的训练进度和性能。
2.定义LeNet-5网络模型
在__init__
方法中,定义了网络的各个层,包括卷积层、池化层和全连接层。
(1)有两个卷积层,分别用于提取图像的特征。第一个卷积层接收输入通道为1(通常对应灰度图像),输出通道为6,卷积核大小为5。第二个卷积层接收输入通道为6,输出通道为16,卷积核大小也为5。
(2)有两个池化层,用于降低特征图的尺寸,减少计算量和参数数量,同时保留重要的特征信息。
(3)有三个全连接层,用于将经过卷积和池化操作后的特征图展平并映射到最终的输出类别数。其中最后一个全连接层输出10个类别,可能对应数字0到9的分类任务。
在forward方法中,定义了数据在网络中的前向传播过程。数据依次经过卷积、激活函数、池化、展平以及全连接层的处理,最终得到输出结果。
预处理步骤
1.数据预处理
使用transforms.Compose将多个数据转换操作组合在一起,依次应用于输入数据。其中包含以下几个主要的转换操作:
(1)transforms.Resize((32, 32)):将输入的图像调整为32x32的尺寸。这是为了使图像尺寸与后续要使用的LeNet-5网络模型的输入要求相匹配,确保模型能够正确处理图像数据。
(2)transforms.ToTensor():将输入的图像数据转换为PyTorch的张量格式。张量是PyTorch中用于表示数据的基本数据结构,方便进行各种数学运算和深度学习操作。
(3)transforms.Normalize((0.1307,), (0.3081,)):对图像数据进行归一化处理。
这里使用了MNIST数据集的均值和标准差进行归一化,归一化可以使数据的分布更加稳定,有助于提高模型的训练效果和泛化能力。
2.加载数据集
通过调用datasets.MNIST函数分别加载训练集和测试集:
(1)root='./data'指定了数据集存储的路径。
(2)train=True表示加载训练集,train=False表示加载测试集。download=True表示如果数据集在指定路径不存在,则自动从网络上下载。
(3)transform=transform应用前面定义的数据预处理操作,对图像数据进行调整尺寸、转换为张量和归一化等处理。
使用torch.utils.data.DataLoader创建数据加载器:
(1)对于训练集数据加载器train_loader,设置batch_size=64表示每次从训练集中读取64个样本组成一个批次,shuffle=True表示在每个epoch开始时随机打乱训练数据的顺序,有助于提高模型的泛化能力和训练效果。
(2)对于测试集数据加载器test_loader,同样设置batch_size=64,但shuffle=False因为在测试时不需要打乱数据顺序。
初始化模型训练所需组件
1.定义损失函数和优化器
对于损失函数:
criterion = nn.CrossEntropyLoss()定义了交叉熵损失函数。在分类问题中,交叉熵损失常用于衡量模型预测的概率分布与真实标签之间的差异。它鼓励模型输出的概率分布尽可能接近真实的类别分布,使得模型在训练过程中不断调整参数以减小损失。
对于优化器:
(1)optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)定义了随机梯度下降(SGD)优化器。它接收模型的参数(通过model.parameters()获取)作为优化的对象。
(2)lr=0.01指定了学习率,学习率控制着每次参数更新的步长大小。较小的学习率可能导致训练过程缓慢但更稳定,而较大的学习率可能使模型在训练初期快速收敛,但也可能导致不稳定甚至无法收敛。
(3)momentum=0.9设置了动量参数。动量可以加速模型的收敛过程,尤其是在处理复杂的损失曲面时。它通过考虑之前的梯度方向,使得参数更新在正确的方向上更有惯性,避免在局部最优解附近震荡。
模型训练
1.训练模型
(1)循环训练多个轮次:通过一个循环进行50个训练轮次(epochs),每个轮次遍历整个训练集。
(2)训练阶段:
初始化变量:每次轮次开始时,初始化损失和正确预测计数器。
数据批处理:遍历训练数据加载器中的每个批次。
前向传播:计算模型对当前批次数据的预测。
计算损失:使用损失函数计算预测和实际标签之间的差异。
反向传播:根据损失进行反向传播,计算梯度。
优化器更新:使用优化器根据梯度更新模型的权重。
记录损失和准确率:计算并记录本轮次的平均损失和准确率。
(3)测试阶段:
不更新梯度:使用torch.no_grad()禁用梯度计算,以减少内存消耗和计算时间。
评估模型:与训练阶段类似,但不对模型参数进行更新,仅用于评估模型在测试集上的性能。
记录测试损失和准确率:计算并记录测试集上的平均损失和准确率。
(4)打印训练和测试结果:每个轮次结束后,打印出当前轮次的训练损失、训练准确率、测试损失和测试准确率。
结果可视化
1.绘制训练和测试的损失函数
(1)创建图表:初始化一个大小为10x5英寸的图表。
(2)绘制训练损失曲线:使用训练过程中记录的损失值绘制一条曲线,并标记为“Training Loss”。
(3)绘制测试损失曲线:使用测试过程中记录的损失值绘制另一条曲线,并标记为“Test Loss”。
(4)设置图表标题和标签:为图表设置标题“Loss Curves”,并为x轴(轮次)和y轴(损失值)添加标签。
(5)显示图例:添加图例以区分训练和测试的损失曲线。
(6)显示图表:使用plt.show()显示图表。
2.绘制训练和测试的准确率曲线
(1)创建图表:初始化一个大小为10x5英寸的图表。
(2)绘制训练准确率曲线:使用训练过程中记录的准确率值绘制一条曲线,并标记为“Training Accuracy”。
(3)绘制测试准确率曲线:使用测试过程中记录的准确率值绘制另一条曲线,并标记为“Test Accuracy”。
(4)设置图表标题和标签:为图表设置标题“Accuracy Curves”,并为x轴(轮次)和y轴(准确率百分比)添加标签。
(5)显示图例:添加图例以区分训练和测试的准确率曲线。
(6)显示图表:使用plt.show()显示图表。
3.实验结果
图1 基于LeNet5网络的MNIST数据集分类的损失函数与准确率
4.优化器和损失函数的选择
SGD优化器和交叉熵损失函数是深度学习中常见的选择。SGD优化器通过计算梯度来更新模型的参数,以最小化损失函数。交叉熵损失函数适用于多分类问题,能够衡量模型预测的概率分布与真实标签的概率分布之间的差异。
5.超参数的调整
超参数如学习率和动量对模型的性能有很大影响。学习率决定了参数更新的步长,如果学习率过大,模型可能无法收敛;如果学习率过小,训练可能会非常缓慢。动量则可以帮助模型更快地收敛,减少震荡。在本实验中,使用了固定的学习率0.001和动量0.9,但在未来的实验中,可以尝试调整这些超参数。
总结
实验基于LeNet5网络对MNIST数据集进行分类。首先导入了必要的库,包括PyTorch框架及其相关模块,以及用于可视化的matplotlib.pyplot库。接着定义了LeNet-5网络模型,包含卷积层、池化层和全连接层。在数据预处理阶段,使用transforms.Compose组合了调整图像尺寸、转换为张量和归一化等操作,并通过datasets.MNIST函数加载数据集,再使用DataLoader创建数据加载器。
然后定义了交叉熵损失函数和SGD优化器,设置了学习率和动量等参数。在模型训练阶段,通过循环进行了50个轮次的训练,每个轮次包括训练阶段和测试阶段,在训练阶段计算损失、反向传播并更新模型权重,在测试阶段评估模型性能,记录并打印每个轮次的训练损失、训练准确率、测试损失和测试准确率。
最后进行结果可视化,绘制了训练和测试的损失曲线以及准确率曲线。实验结果表明SGD优化器和交叉熵损失函数是合适的选择,同时超参数如学习率和动量对模型性能有重要影响,未来可尝试调整这些超参数。
源代码
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
class LeNet5(nn.Module):
def __init__(self):
super(LeNet5, self).__init__()
# 卷积层1:输入通道1,输出通道6,卷积核大小5x5
self.conv1 = nn.Conv2d(1, 6, 5)
self.pool1 = nn.MaxPool2d(2, 2)
# 卷积层2:输入通道6,输出通道16,卷积核大小5x5
self.conv2 = nn.Conv2d(6, 16, 5)
self.pool2 = nn.MaxPool2d(2, 2)
# 全连接层1:输入特征16*5*5,输出特征120
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
# 全连接层3:输入特征84,输出特征10(MNIST数据集有10个类别)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
x = self.pool1(torch.relu(self.conv1(x)))
x = self.pool2(torch.relu(self.conv2(x)))
x = x.view(-1, 16 * 5 * 5)
x = torch.relu(self.fc1(x))
x = torch.relu(self.fc2(x))
x = self.fc3(x)
return x
transform = transforms.Compose([
transforms.Resize((32, 32)),
transforms.ToTensor(), # 将图像转换为张量
transforms.Normalize((0.1307,), (0.3081,)) # 标准化
])
train_dataset = datasets.MNIST('data/', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('data/', train=False, download=True, transform=transform)
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_dataset, batch_size=64, shuffle=False)
model = LeNet5()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
def train(model, train_loader, optimizer, criterion, epochs):
for epoch in range(epochs):
running_loss = 0.0
correct = 0
total = 0
for i, data in enumerate(train_loader, 0):
inputs, labels = data
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'Epoch [{epoch + 1}/{epochs}], Loss: {running_loss / len(train_loader):.3f}, Train Accuracy: {100 * correct / total:.2f}%')
def evaluate(model, test_loader):
correct = 0
total = 0
with torch.no_grad():
for data in test_loader:
images, labels = data
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'Accuracy on test set: {100 * correct / total:.2f}%')
train(model, train_loader, optimizer, criterion, epochs=5)
evaluate(model, test_loader)