|
1.搭建一个CNN网络,使用MNIST手写数字数据集进行训练与测试,并体现模型最终结果,CNN网络的具体框架可参考下图,也可自己设计:

图4-1 CNN架构图
搭建了一个三层卷积的 CNN 网络,网络结构如下:
卷积层1:32 个 3×3 卷积核,Batch Normalization,ReLU 激活函数
池化层1:2×2 最大池化
卷积层2:64 个 3×3 卷积核,Batch Normalization,ReLU 激活函数
池化层2:2×2 最大池化
卷积层3:128 个 3×3 卷积核,Batch Normalization,ReLU 激活函数
池化层3:2×2 最大池化
全连接层 1:256 个神经元,LeakyReLU 激活函数,Dropout(0.5)
全连接层 2:128 个神经元,LeakyReLU 激活函数,Dropout(0.5)
输出层:10个神经元(对应 0-9 数字分类)
以下是网络实现的代码片段:
|
class ImprovedCNN(nn.Module):
def __init__(self):
super(ImprovedCNN, self).__init__()
self.conv1 = nn.Conv2d(1, 32, kernel_size=3, padding=1)
self.bn1 = nn.BatchNorm2d(32)
self.conv2 = nn.Conv2d(32, 64, kernel_size=3, padding=1)
self.bn2 = nn.BatchNorm2d(64)
self.conv3 = nn.Conv2d(64, 128, kernel_size=3, padding=1)
self.bn3 = nn.BatchNorm2d(128)
self.pool = nn.MaxPool2d(2, 2)
self.dropout1 = nn.Dropout(0.25)
self.dropout2 = nn.Dropout(0.5)
self.fc1 = nn.Linear(128 * 3 * 3, 256)
self.fc2 = nn.Linear(256, 128)
self.fc3 = nn.Linear(128, 10)
self.relu = nn.ReLU()
self.leaky_relu = nn.LeakyReLU(0.1)
def forward(self, x):
x = self.relu(self.bn1(self.conv1(x)))
x = self.pool(x)
x = self.relu(self.bn2(self.conv2(x)))
x = self.pool(x)
x = self.relu(self.bn3(self.conv3(x)))
x = self.pool(x)
x = self.dropout1(x)
x = x.view(-1, 128 * 3 * 3)
x = self.leaky_relu(self.fc1(x))
x = self.dropout2(x)
x = self.leaky_relu(self.fc2(x))
x = self.fc3(x)
return x
|
- 尝试使用不同的数据增强方法、优化器、损失函数、学习率、batch size和迭代次数来进行训练,记录训练过程,评估模型性能,保存最佳模型。
为了提高模型的泛化能力,我实现了多种数据增强方法,包括随机旋转、随机仿射变换、随机透视变换、颜色抖动和随机擦除等。同时,对数据进行了归一化处理,确保输入数据具有相似的分布。
以下是实现的代码片段:
|
def get_transforms(augment=False):
if augment:
return transforms.Compose([
transforms.RandomRotation(15),
transforms.RandomAffine(degrees=0, translate=(0.15, 0.15), scale=(0.85, 1.15)),
transforms.RandomPerspective(distortion_scale=0.2, p=0.5),
transforms.ColorJitter(brightness=0.2, contrast=0.2),
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)),
transforms.RandomErasing(p=0.2, scale=(0.02, 0.1), value='random')
])
else:
return transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,))
])
|
我尝试了不同的优化器、损失函数、学习率、batch size 和迭代次数来训练模型,并记录了训练过程和模型性能。
优化器:使用 AdamW 优化器,结合权重衰减防止过拟合。
损失函数:采用带有标签平滑的交叉熵损失函数,提高模型的泛化能力。
学习率调度:使用 ReduceLROnPlateau 调度器,当验证集损失停滞时降低学习率。
训练配置:
Batch Size:128
学习率:0.001
迭代次数:20
权重衰减:1e-4
训练过程中,我记录了训练损失、验证损失、训练准确率和验证准确率,并在验证集上取得最佳性能时保存模型。
以下是实现的代码片段:
|
# 训练模型
epochs = 20
train_losses, train_accuracies, val_losses, val_accuracies = train_model(
model, train_loader, val_loader, criterion, optimizer, device, epochs, scheduler
)
# 保存最佳模型
if val_acc > best_val_acc:
best_val_acc = val_acc
save_model(model, 'best_mnist_model.pth', best_val_acc)
print(f'New best model saved with val acc: {best_val_acc:.2f}%')
|
部分训练过程和模型性能截图如图4-2所示:

图4-2 部分训练过程和模型性能截图
训练完成后,我使用测试集评估模型性能,并可视化了训练过程和混淆矩阵,以便更直观地了解模型在各个数字上的分类表现。
以下是实现的代码片段:
|
# 评估模型
test_loss, test_acc = evaluate_model(model, test_loader, device, criterion)
print(f'Test Loss: {test_loss:.4f} | Test Acc: {test_acc:.2f}%')
# 可视化训练结果
visualize_training(train_losses, train_accuracies, val_losses, val_accuracies, test_loss, test_acc, device, model, test_loader)
|
混淆矩阵如图4-3所示:

图4-3 混淆矩阵
混淆矩阵是一个 10×10 的矩阵,用于展示模型对每个类别的分类效果:
行:表示实际标签(True Label)。
列:表示预测标签(Predicted Label)。
对角线:表示正确预测的样本数,数值越高表示模型对该类别的识别能力越强。
非对角线元素:表示错误预测的样本数,例如第 i 行第 j 列的元素表示实际为 i 但被预测为 j 的样本数。
通过混淆矩阵,可以直观地看出模型在哪些类别上容易混淆,有助于分析模型的弱点并进行针对性的改进。
混淆矩阵实现代码如下所示:
|
plt.subplot(2, 2, 3)
confusion_matrix = np.zeros((10, 10))
test_loader = main().test_loader # 获取测试数据加载器
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)
_, predicted = output.max(1)
for t, p in zip(target.view(-1), predicted.view(-1)):
confusion_matrix[t.long(), p.long()] += 1
plt.imshow(confusion_matrix, interpolation='nearest', cmap=plt.cm.Blues)
plt.title('Confusion Matrix')
plt.colorbar()
tick_marks = np.arange(10)
plt.xticks(tick_marks, range(10))
plt.yticks(tick_marks, range(10))
plt.xlabel('Predicted')
plt.ylabel('True')
|
训练损失和验证损失的变化曲线如图4-4所示:

图4-4 训练损失和验证损失的变化曲线
这张图展示了随着训练轮次(Epoch)的增加,训练损失和验证损失的变化趋势。其中:
训练损失:应该随着训练轮次的增加而逐渐下降。
验证损失:开始时会下降,但如果出现过拟合,验证损失可能会在某个点后开始上升。
测试损失:用红色虚线表示,是模型在测试集上的最终损失值。
通过观察这张图,可以判断模型是否过拟合或欠拟合,以及训练是否收敛。
实现代码如下所示:
|
plt.subplot(2, 2, 1)
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.axhline(y=test_loss, color='r', linestyle='--', label='Test Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.title('Training and Validation Loss')
|
训练准确率和验证准确率曲线如图4-5所示:

图4-5 训练准确率和验证准确率曲线
这张图展示了随着训练轮次的增加,训练准确率和验证准确率的变化趋势。其中:
训练准确率:应该随着训练轮次的增加而逐渐上升。
验证准确率:开始时会上升,但如果出现过拟合,验证准确率可能会停滞或下降。
测试准确率:用红色虚线表示,是模型在测试集上的最终准确率。
通过观察这张图,可以评估模型的学习能力和泛化能力。
实现代码如下所示:
|
plt.subplot(2, 2, 2)
plt.plot(train_accuracies, label='Train Accuracy')
plt.plot(val_accuracies, label='Validation Accuracy')
plt.axhline(y=test_acc, color='r', linestyle='--', label='Test Accuracy')
plt.xlabel('Epoch')
plt.ylabel('Accuracy (%)')
plt.legend()
plt.title('Training and Validation Accuracy')
|
3.使用画图工具将自己的学号逐个写出,使用保存的最佳模型对每个数字进行推理,比较模型对每个数字的准确率预测,也可以尝试实现一个实时识别手写数字的demo。
|