1. 理解卷积神经网络(CNN)。
2. 掌握数据预处理和增强技术:学习如何通过数据增强技术(如旋转、缩放、剪切等)来增加模型的泛化能力,减少过拟合。
3. 应用正则化技术:通过实验,掌握 Dropout、L2 正则化等技术在卷积神经网络中的应用,以降低模型的过拟合风险。
4. 优化模型性能:通过调整网络结构和超参数(如卷积层数量、卷积核大小、池化层、激活函数等),优化模型的准确率。
5. 理解过拟合现象:通过实验,理解过拟合产生的原因,以及如何通过不同的方法来识别和避免过拟合。
6. 模型调优:学习如何通过调整学习率、批大小、优化器等超参数来提高模型的训练效率和性能。
硬件:12th Gen Intel(R) Core(TM) i5-12500H 2.50 GHz
软件:PyCharm Community Edition 2023.2.1
1.数据加载与预处理: 使用 FashionMNIST 数据集进行分类任务,通过随机水平翻转、旋转、颜色抖动等增强技术,提升数据集的多样性。所有图片进行归一化处理,以加速训练和提升稳定性。
2.模型构建与优化: 设计一个增强版卷积神经网络(CNN),包括四个卷积层和相应的池化、批量归一化操作,以确保模型能够学习到不同层次的特征。同时,增加全连接层并应用 Dropout 技术,提升模型的泛化能力。
3.学习率调度与超参数调整: 使用余弦退火学习率调度器进行学习率的动态调整。通过不同的优化器(SGD 和 Adam)和不同的学习率组合,训练并测试模型的性能。
1.PyTorch库:
(1)用于构建和训练神经网络。
(2)导入PyTorch的神经网络模块,用于定义神经网络层和模型。
(3)导入PyTorch的函数式接口,提供了一些常用的函数,如激活函数。
(4)导入PyTorch的优化器模块,用于定义优化算法,如SGD、Adam等,用于训练模型。
2.torchvision库:
(1)常用于计算机视觉任务的库,提供了数据集、预处理工具和模型。
(2)导入torchvision的transforms模块,用于对图像数据进行预处理,如缩放、裁剪、归一化等。
3.matplotlib库:
导入matplotlib的pyplot模块,用于绘制图像和图表,常用于数据可视化。
1.数据预处理和增强:
(1)随机旋转:transforms.RandomRotation(10)对训练数据集中的每张图片进行随机旋转,旋转角度在-10度到10度之间。这有助于模型学习到旋转不变的特征。
(2)随机裁剪:transforms.RandomResizedCrop(28, scale=(0.8, 1.2))对图片进行随机裁剪并调整大小到28x28像素。scale参数控制裁剪大小相对于原图的比例,这里的比例在0.8到1.2之间,意味着裁剪后的图片大小会在原图的80%到120%之间。
(3)转换为张量:transforms.ToTensor()将PIL图像或NumPy数组转换为PyTorch张量,并自动将像素值从[0, 255]缩放到[0.0, 1.0]。
(4)归一化:transforms.Normalize((0.1307,), (0.3081,))对张量进行归一化处理,使用均值0.1307和标准差0.3081。这通常是基于数据集的统计信息,有助于模型训练的稳定性和收敛速度。
2.加载数据集:
(1)加载数据集:
对于训练集:
通过torchvision.datasets.FashionMNIST函数加载 FashionMNIST 数据集的训练部分。这个数据集是一个流行的用于图像分类的数据集,包含各种时尚物品的图像。
root='./data'指定了数据集存储的路径,如果数据集不存在于该路径下,且download=True,那么会自动从网络上下载数据集并保存到指定路径。
transform=transform_train应用了之前定义的针对训练数据的预处理和数据增强操作。这些操作包括随机旋转、随机裁剪并调整大小、转换为张量以及归一化等。这些操作可以增加数据的多样性,提高模型的泛化能力,使其能够更好地适应不同的输入变化。
对于测试集:
同样使用torchvision.datasets.FashionMNIST加载测试数据部分,train=False明确表示加载的是测试集。
应用transform_test预处理操作,该操作主要是将图像转换为张量并进行归一化,确保测试数据的处理方式与训练数据一致,以便模型在相同的条件下进行评估。
(2)创建数据加载器
对于训练集:
torch.utils.data.DataLoader创建了一个数据加载器对象trainloader。
batch_size=64意味着每次从训练集中取出 64 个样本组成一个批次。这样做的好处是可以利用现代硬件(如 GPU)的并行计算能力,提高训练效率。
shuffle=True在每个训练周期(epoch)开始时随机打乱训练数据的顺序。这有助于模型更好地学习数据的分布,避免模型过度依赖特定的数据顺序,从而提高模型的泛化能力。
num_workers=2指定使用两个子进程来加载数据。这可以加快数据加载的速度,特别是在处理大规模数据集时,可以减少数据加载对训练过程的瓶颈影响。
对于测试集:
类似地,创建了测试集的数据加载器testloader。
batch_size=64保持与训练集一致,方便在测试时进行批量处理。
shuffle=False因为在测试阶段不需要打乱数据顺序,通常是按照固定的顺序依次进行评估。
num_workers=2同样用于提高数据加载效率。
1.定义卷积神经网络模型:
__init__方法:
(1)构建了CNN模型的网络结构。
(2)定义了两个卷积层:conv1和conv2。conv1的输入通道数为1,适用于单通道的灰度图像,输出通道数为16,卷积核大小为3且设置了填充为1,以保持输入输出尺寸的一致性;conv2的输入通道数为16(conv1的输出通道数),输出通道数为32,卷积核大小和填充设置与conv1相同。
(3)定义了一个最大池化层pool,池化核大小为2且步长为2,用于对卷积层输出的数据进行下采样,减少数据维度的同时提取重要特征。 (4)定义了两个全连接层:fc1和fc2。fc1的输入维度是经过卷积和池化操作后得到的特征维度,输出维度为128;fc2的输入维度为128,输出维度为10,对应于分类任务中的类别数。
forward方法:
(1)定义了数据在CNN模型中的前向传播路径。
(2)首先,输入数据x经过conv1卷积层进行卷积操作,然后通过relu激活函数增加非线性特性,接着经过pool池化层进行下采样。
(3)之后,数据再经过conv2卷积层进行卷积操作,同样经过relu激活函数和pool池化层。
(4)经过两次卷积和池化后,数据通过x.view(-1, 32 * 7 * 7)操作将其维度调整为适合全连接层fc1的输入形式。
(5)然后,数据依次经过fc1全连接层和relu激活函数进行处理,再经过fc2全连接层进行进一步的特征映射。
(6)最后,经过softmax函数对输出结果进行处理,将输出转换为各个类别上的概率分布,dim=1表示在维度1上进行softmax操作,即针对每个样本的不同类别计算概率,以便用于后续的分类决策。
2.定义损失函数和优化器,这里使用Adam优化器:
(1)损失函数:使用交叉熵损失函数(nn.CrossEntropyLoss()),这是多分类问题中常用的损失函数。它衡量模型预测的概率分布与真实标签的概率分布之间的差异。
(2)优化器:使用Adam优化器(optim.Adam()),这是一种自适应学习率的优化算法,它结合了动量和RMSProp的概念。优化器用于在训练过程中调整模型的参数,以最小化损失函数。
(3)参数设置:优化器的lr=0.001参数设置了学习率,这是每次参数更新时步长的大小。
1.训练循环设置:设置了训练的轮数为30轮(可调整),每一轮代表对整个训练数据集的一次完整遍历。
2.每轮训练的初始化:在每一轮开始时,将running_loss(累计损失)初始化为0.0,correct(正确分类的样本数)和total(总样本数)初始化为0,用于统计本轮训练的相关指标。
3.数据加载和模型训练:对于每一批次的数据(通过for i, data in enumerate(trainloader, 0)遍历数据加载器trainloader):
(1)从数据中分离出输入inputs和标签labels。
(2)首先将优化器的梯度清零(optimizer.zero_grad()),以避免上一批次的梯度影响当前批次的训练。
(3)将输入数据传入模型得到输出outputs,然后根据模型输出和真实标签计算损失loss。
(4)通过loss.backward()进行反向传播,计算模型参数的梯度。
(5)最后使用optimizer.step()根据计算得到的梯度更新模型参数。
(6)在每一批次训练过程中,累计损失running_loss加上当前批次的损失值loss.item(),同时统计正确分类的样本数correct和总样本数total。
4.记录训练指标:
(1)每一轮训练结束后,将本轮的平均损失添加到train_losses列表中,用于记录训练过程中的损失变化情况。
(2)将本轮在训练集上的准确率添加到train_accuracies列表中,用于评估模型在训练集上的性能提升情况。
1.实验结果:
图1 在不同学习率和优化器下的loss-acc图像
图2 混淆矩阵热力图
2.结果分析:
(1)整体观察:可以看到不同的预测标签和真实标签之间的对应关系。对角线上的数值表示正确预测的数量,非对角线元素表示错误预测的情况。
(2)优化器和学习率的影响:对于某些类别(如第一个类别有3个正确预测,类别5有2个错误预测到类别6等),不同的优化器和学习率组合可能会导致不同的预测结果。
(3)损失曲线:
SGD和Adam对比:
在学习率为0.001时,Adam优化器的训练损失和测试损失整体上比SGD优化器下降得更快且更平稳。例如,在大约20个epoch后,Adam的训练损失已经接近0.5,而SGD的训练损失还在1.0左右。
当学习率变为0.01时,SGD的训练损失和测试损失曲线波动较大,说明模型可能不稳定。而Adam的损失曲线虽然也有一定波动,但相对较小。
学习率影响:
对于SGD优化器,学习率从0.001增加到0.01时,损失曲线上升且波动剧烈,说明学习率过大导致模型难以收敛。
对于Adam优化器,学习率从0.001增加到0.01时,损失曲线也有上升趋势,但波动不如SGD大,不过也表明学习率增大对模型性能有负面影响。
(4)准确率曲线:
SGD和Adam对比:
在学习率为0.001时,Adam优化器的训练准确率和测试准确率在前期上升速度比SGD快,且最终的测试准确率也较高。
当学习率变为 0.01 时,SGD 的准确率曲线波动剧烈且最终准确率较低,而 Adam 的准确率曲线波动相对较小且最终准确率仍高于 SGD。
学习率影响:
对于SGD优化器,学习率从0.001增加到0.01时,准确率明显下降,说明学习率过大不利于模型学习到正确的模式。
对于Adam优化器,学习率从0.001增加到001时,准确率也有所下降,但下降幅度小于SGD,说明Adam对学习率的敏感度相对较低。
(5)混淆矩阵:是T 恤/top、裤子/trouser、套头衫/pullover、连衣裙/dress、外套/coat、凉鞋/sandal、衬衫/shirt、运动鞋/Sneaker、包/Bag、短靴/Ankle boot这 10 种商品的 10x10 混淆矩阵图。图中不同位置的元素表示预测类别与实际类别的数量关系,颜色越深代表值越大。从图中可以看出,对衬衫/shirt 的识别正确率最高,为966;其次是包/Bag,为 983;再次是对T恤/top的识别,为 985。而其他几类的识别准确度较低,例如对短靴/Ankle boot的识别只有96。
在本次实验中,深入探讨了卷积神经网络(CNN)在图像分类任务中的应用,特别是在FashionMNIST数据集上的表现。通过实验,不仅理解了CNN的基本概念,还掌握了数据预处理和增强技术,这对于提高模型的泛化能力和减少过拟合至关重要。实验中,采用了随机旋转和裁剪等数据增强方法,以及归一化处理,以确保数据的一致性和模型训练的稳定性。
实验过程中,注意到了过拟合现象,并探讨了其产生的原因及避免方法。通过调整学习率、批大小和优化器等超参数来进行模型调优,以提高模型的训练效率和性能。在训练过程中,使用了Adam优化器,并观察了不同学习率对模型性能的影响。
最终,通过绘制损失曲线和准确率曲线,以及生成混淆矩阵热力图,对模型的性能进行了评估和可视化。实验结果表明,Adam优化器在大多数情况下比SGD优化器表现更好,尤其是在较高学习率下,Adam的稳定性和收敛速度都优于SGD。
总的来说,这次实验不仅加深了对CNN在图像分类任务中应用的理解,还提高了在模型设计、训练和调优方面的实践能力。通过实验,学会了如何通过不同的技术和方法来提高模型的泛化能力和性能。
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
# 数据预处理和增强
transform_train = transforms.Compose([
transforms.RandomRotation(10),
transforms.RandomResizedCrop(28, scale=(0.8, 1.2)),
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
# 加载数据集
trainset = torchvision.datasets.FashionMNIST(root='./data', train=True,
download=True, transform=transform_train)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=64,
shuffle=True, num_workers=2)
testset = torchvision.datasets.FashionMNIST(root='./data', train=False,
download=True, transform=transform_test)
testloader = torch.utils.data.DataLoader(testset, batch_size=64,
shuffle=False, num_workers=2)
# 定义卷积神经网络模型
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Conv2d(1, 16, kernel_size=3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.conv2 = nn.Conv2d(16, 32, kernel_size=3, padding=1)
self.fc1 = nn.Linear(32 * 7 * 7, 128)
self.fc2 = nn.Linear(128, 10)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 32 * 7 * 7)
x = F.relu(self.fc1(x))
x = self.fc2(x)
return F.softmax(x, dim=1)
# 实例化模型
net = CNN()
# 定义损失函数和优化器,这里使用Adam优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)
# 用于存储训练损失和准确率
train_losses = []
train_accuracies = []
test_accuracies = []
# 训练模型
for epoch in range(30): # 可以根据需要调整训练轮数
running_loss = 0.0
correct = 0
total = 0
for i, data in enumerate(trainloader, 0):
inputs, labels = data
optimizer.zero_grad()
outputs = net(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()
train_losses.append(running_loss / len(trainloader))
train_accuracies.append(100 * correct / total)
# 在测试集上评估模型
correct = 0
total = 0
with torch.no_grad():
for data in testloader:
images, labels = data
outputs = Inet(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
test_accuracies.append(100 * correct / total)
print('[%d] Train Loss: %.3f, Train Accuracy: %.2f%%, Test Accuracy: %.2f%%' % (
epoch + 1, train_losses[-1], train_accuracies[-1], test_accuracies[-1]))
# 绘制损失曲线
plt.figure(figsize=(10, 5))
plt.plot(range(1, len(train_losses) + 1), train_losses, label='Train Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training Loss Curve')
plt.legend()
plt.show()
# 绘制准确率曲线
plt.figure(figsize=(10, 5))
plt.plot(range(1, len(train_accuracies) + 1), train_accuracies, label='Train Accuracy')
plt.plot(range(1, len(test_accuracies) + 1), test_accuracies, label='Test Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.title('Accuracy Curves')
plt.legend()
plt.show()