1. 题目要求&目的
本次实验的主要目的是巩固和应用卷积神经网络(CNN)的理论知识。通过编写代码实现这一深度学习模型,旨在达到理论与实践相结合的“知行合一”的学习效果。此外,实验还旨在加深对神经网络在图像识别任务中应用的理解。
1.1 实验目标
- 数据集:使用公认的Fashion-MNIST数据集进行实验。
- 任务描述:在Fashion-MNIST数据集上,利用卷积神经网络对10种不同类别的服装图像进行分类。
- 性能目标:实现的模型需达到至少90%的分类正确率。
2. 数据预处理
在此部分主要完成了对数据的导入以及标准化等处理,在此次实验中使用了Pytorch框架进行实验。下面是此部分的对应代码:
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5,), (0.5,))
])
train_dataset = datasets.FashionMNIST(root='./data', train=True, download=True, transform=transform)
test_dataset = datasets.FashionMNIST(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)
-
数据加载与转换: 首先,使用了
torchvision
库中的datasets
模块来加载Fashion-MNIST数据集。通过设置train=True
和train=False
,分别加载了训练集和测试集。此外,所有图像数据在加载过程中都应用了两个转换操作,确保它们能以适当的格式供神经网络使用。 -
数据转换流程: 这里定义了一个转换流程,并且使用了
transforms.Compose
来组合两个主要的转换操作:
- ToTensor:此转换将图像数据从PIL格式或NumPy ndarray转换为FloatTensor格式,并自动将图像的像素值从[0, 255]缩放到[0.0, 1.0]的范围,这是为了适配PyTorch的处理方式。
- Normalize:我们对数据进行了标准化处理,设置均值为0.5和标准差为0.5。这个操作进一步将像素值从[0.0, 1.0]转换到[-1.0, 1.0],有助于模型训练时的数值稳定性和收敛速度。
- 数据加载器: 使用
DataLoader
来封装数据集,这样可以更方便的批量处理图像数据,还可以随机打乱训练数据(通过shuffle=True
),以避免模型过拟合。训练集和测试集的批处理大小均设置为64,这意味着每次迭代训练和评估模型时,网络将接收64张图像。
3. 模型设计
此部分为模型的搭建,主要还是使用了pytorch中的一些内置函数以及父类,具体的代码为:
class FashionCNN(nn.Module):
def __init__(self):
super(FashionCNN, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1, 32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.layer2 = nn.Sequential(
nn.Conv2d(32, 64, kernel_size=3, padding=1),
nn.BatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2)
)
self.fc1 = nn.Linear(64 * 7 * 7, 1000)
self.drop = nn.Dropout2d(0.25)
self.fc2 = nn.Linear(1000, 10)
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = out.view(out.size(0), -1) # Flatten the output
out = self.fc1(out)
out = self.drop(out)
out = self.fc2(out)
return out
如上述代码可以发现,我在此次实验中一共为网络定义了几个模块,他们具体解释以及作用为:
-
卷积层:
模型包含两个卷积层序列,每个序列中都包含卷积层、批量归一化层、ReLU激活函数和最大池化层,具体配置如下:- 第一层(Layer1):
- 卷积层:使用
nn.Conv2d
,配置为接收1个通道的输入(单色图像),输出32个通道,核大小为3,边缘填充为1。 - 批量归一化:对32个输出通道进行归一化,以加速训练过程并提高模型稳定性。
- ReLU激活函数:增加非线性处理,有助于捕捉复杂特征。
- 最大池化层:使用
nn.MaxPool2d
,核大小为2,步幅为2,用于降低特征维度并提取主要特征。
- 卷积层:使用
- 第二层(Layer2):
- 类似于第一层,但是此层的卷积层输出通道增加到64,以捕获更细致的特征。
- 第一层(Layer1):
-
全连接层:
- Flatten操作:在传递给全连接层之前,将特征图展平成一维向量。
- 第一个全连接层(fc1):将展平的特征连接到1000个神经元,这一层大幅增加模型的学习能力。
- Dropout层:采用25%的丢弃率,用于减少过拟合,增强模型的泛化能力。
- 第二个全连接层(fc2):最终的输出层,将1000个神经元连接到10个输出类别,对应于Fashion-MNIST的10种服装类别。
-
模型输出:
模型的最终输出是一个10维向量,每个维度对应一个服装类别的预测概率。
值得一提的是如何计算卷积后图像输出形状,它的公式为:
W
out
=
⌊
W
in
−
F
+
2
P
S
⌋
+
1
H
out
=
⌊
H
in
−
F
+
2
P
S
⌋
+
1
\begin{aligned} & W_{\text {out }}=\left\lfloor\frac{W_{\text {in }}-F+2 P}{S}\right\rfloor+1 \\ & H_{\text {out }}=\left\lfloor\frac{H_{\text {in }}-F+2 P}{S}\right\rfloor+1 \end{aligned}
Wout =⌊SWin −F+2P⌋+1Hout =⌊SHin −F+2P⌋+1
这些模块一同构成下图所示的模型网络结构:
model = FashionCNN()
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
模型实例化,并使用pytorch库中的交叉熵损失作为损失函数,优化器采用Adam。
4. 模型训练
def train_model(num_epochs):
model.train()
for epoch in range(num_epochs):
for images, labels in train_loader:
optimizer.zero_grad()
outputs = model(images)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
train_model(50)
-
初始化训练
- 模型状态:首先,通过调用
model.train()
确保模型处于训练模式。
- 模型状态:首先,通过调用
-
训练循环
训练过程包括多个周期(epoch),每个周期都会处理整个训练集一次。本次实验中,设置训练周期为50次。具体步骤如下: -
循环每一个周期:使用
for epoch in range(num_epochs):
来迭代指定次数。-
数据加载:通过
train_loader
加载批次数据,每个批次包含图像及其对应的标签。 -
梯度初始化:在每次批处理前,使用
optimizer.zero_grad()
清除旧的梯度信息,防止梯度累积影响当前批次的更新。 -
前向传播:将图像数据
images
输入模型,计算预测输出outputs
。 -
计算损失:使用损失函数
criterion(outputs, labels)
计算预测输出与实际标签之间的差异。 -
反向传播:调用
loss.backward()
对模型参数进行梯度计算。 -
参数更新:执行
optimizer.step()
根据计算得到的梯度更新模型参数。
-
-
监控训练进度
- 打印损失:在每个训练周期结束时,输出当前周期的损失值。
5. 模型评估和结果
def evaluate_model():
model.eval()
total = 0
correct = 0
with torch.no_grad():
for images, labels in test_loader:
outputs = model(images)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f'Accuracy: {accuracy:.2f}%')
evaluate_model()
-
模型评估设置
-
评估模式:首先,通过调用
model.eval()
将模型设置为评估模式。这是必要的步骤,因为某些网络层(例如Dropout和BatchNorm)在训练和评估时的行为是不同的。评估模式下,这些层会表现出与训练时不同的特性,例如不再随机丢弃神经元。 -
禁用梯度计算:使用
torch.no_grad()
环境,确保在评估过程中不会计算梯度。
-
-
性能计算
-
测试数据遍历:通过
test_loader
逐批加载测试数据集的图像和标签。对每批数据执行以下步骤:-
模型预测:将图像输入模型,获取输出结果
outputs
。 -
确定预测类别:使用
torch.max(outputs.data, 1)
提取每个图像的预测类别。这个函数返回每行(每个图像)最大值的索引,即模型认为最可能的类别。
-
-
统计正确预测:累计测试集中总图像数
total
和正确预测的图像数correct
。比较预测类别predicted
与真实标签labels
,通过(predicted == labels).sum().item()
计算匹配的数量。
-
-
计算准确率
- 计算并打印准确率:最后,计算准确率
accuracy = 100 * correct / total
,将正确预测的比例转换为百分比形式,并打印结果,如Accuracy: {accuracy:.2f}%
。
- 计算并打印准确率:最后,计算准确率
(这里图片之前在本地的markdown里写了不小心删了,但这是没调任何参的第一次运行结果,调好了会再高可能1%)
达到90.34%的正确率,完成了既定的任务。
6. 总结
这次实验通过构建和训练一个卷积神经网络模型,在Fashion-MNIST数据集上进行图像分类,取得了90.34%的正确率。证明了CNN的强大性能。下面是在此次实验中的个人感想(略)