聊城网站开发公司,做购物平台网站客户体验活动,企业官网建站流程,成都极客联盟案例3#xff1a;PyTorch实战: CIFAR10图像分类
1 任务目标
1.1 用多层感知机(MLP)和卷积网络(ConvNet)完成CIFAR10分类
使用PyTorch分别实现多层感知机(MLP)和卷积网络(ConvNet)#xff0c;并完成CIFAR10数据集#xff08;http://www.cs.toronto.edu/~kriz/cifar.htmlPyTorch实战: CIFAR10图像分类
1 任务目标
1.1 用多层感知机(MLP)和卷积网络(ConvNet)完成CIFAR10分类
使用PyTorch分别实现多层感知机(MLP)和卷积网络(ConvNet)并完成CIFAR10数据集http://www.cs.toronto.edu/~kriz/cifar.html分类。本案例不提供初始代码请自行配置网络和选取超参数包括层数、卷积核数目、激活函数类型、损失函数类型、优化器等方面。
提交所有代码和一份案例报告要求如下
详细介绍所使用的模型及其结果至少包括超参数选取损失函数、准确率及其曲线比较不同模型配置下的结果至少从三个方面作比较和分析例如层数、卷积核数目、激活函数类型、损失函数类型、优化器等。
1.2 学习PyTorch ImageNet分类示例
请自行学习PyTorch官方提供的ImageNet分类示例代码以便更好地完成后续案例(https://github.com/pytorch/examples/tree/master/imagenet)这部分无需提交代码和报告。
1.3 注意事项 提交所有代码和一份案例报告 禁止任何形式的抄袭。
2 代码设计
2.1 初始化及数据预处理 设置设备 让模型采用gpu torch.cuda 进行训练若 cuda 不可用则采用cpu进行训练。 # 设置设备
device torch.device(cuda if torch.cuda.is_available() else cpu)
print(training on, device)数据预处理以及数据增强 该部分负责在创建数据集时对数据进行数据增强和预处理具体操作如下 - 训练集数据处理- 随机水平翻转RandomHorizontalFlip()以0.5的概率随机水平翻转图像。- 随机旋转RandomRotation(5)在-5度到5度之间随机旋转图像。- 颜色抖动ColorJitter(brightness0.2, contrast0.2, saturation0.2)对图像进行颜色抖动包括亮度、对比度和饱和度的随机变化。- 随机裁剪RandomResizedCrop(32, scale(0.9, 1.0))对图像进行随机裁剪然后重新调整大小到指定的尺寸这里是32x32像素。- 张量转换ToTensor()将图像转换为PyTorch张量。- 图像标准化Normalize对图像进行标准化将像素值缩放到[-1, 1]的范围。测试集数据处理 张量转换ToTensor()将图像转换为PyTorch张量。 图像标准化Normalize对图像进行标准化将像素值缩放到[-1, 1]的范围。 # 数据增强和预处理
transform_train transforms.Compose([transforms.RandomHorizontalFlip(),transforms.RandomRotation(5),transforms.ColorJitter(brightness0.2, contrast0.2, saturation0.2),transforms.RandomResizedCrop(32, scale(0.9, 1.0)),transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])transform_test transforms.Compose([transforms.ToTensor(),transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)),
])数据集创建 CIFAR-10 数据集是一个用于图像分类的常用数据集该数据集包含了10个类别的彩色图像每个类别有6,000张图像共计60,000张图像。每张图像的尺寸是32x32像素。 该部分用于下载CIFAR-10数据集并按指定预处理操作创建训练集和测试集。 torchvision.datasets.CIFAR10: 这是PyTorch中专门用于加载CIFAR-10数据集的类。root./data: 这指定了数据集将被下载到的本地目录。trainTrue和trainFalse: 当trainTrue时表示创建训练集当trainFalse时表示创建测试集。downloadTrue: 如果本地没有找到CIFAR-10数据集设置为True时PyTorch将自动下载并解压缩数据集。transformtransform: 这里的transform参数指定了数据集中图像的预处理操作。 # 加载CIFAR-10数据集并进行划分
train_dataset torchvision.datasets.CIFAR10(root./data, trainTrue, downloadTrue, transformtransform_train)
test_dataset torchvision.datasets.CIFAR10(root./data, trainFalse, downloadTrue, transformtransform_test)创建数据加载器 创建用于训练和测试的数据加载器DataLoader。train_loader用于训练test_loader用于测试。 这些数据加载器在训练和测试过程中会循环提供每个批次的图像和标签方便使用PyTorch的模型进行训练和评估。 torch.utils.data.DataLoader: 这是PyTorch中的一个类用于从数据集中加载批量的数据。batch_size64: 这指定了每个批次加载的图像数量。shuffleTrue和shuffleFalse: 这表示是否在每个epoch开始时随机打乱数据。在训练集中通常希望打乱数据以防止模型学到数据的顺序性而在测试集中则可以保持数据的原始顺序。num_workers2: 这指定了用于加载数据的子进程数量。 # 创建数据加载器
train_loader torch.utils.data.DataLoader(train_dataset, batch_size64, shuffleTrue, num_workers2)
test_loader torch.utils.data.DataLoader(test_dataset, batch_size64, shuffleFalse, num_workers2)显示数据集图片 将一个批次64的数据集图像及标签通过 Matplotlib 显示出来。 # 获取一个批次的图像和标签
for images, labels in train_loader:break # 获取第一个批次后就跳出循环# 将张量转换为NumPy数组
images images.numpy()# 反归一化
images (images * 0.5) 0.5# 定义标签对应的类别名称
class_names [airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck]# 显示图像和标签
plt.figure(figsize(20, 20))
for i in range(64):plt.subplot(8, 8, i 1)plt.imshow(np.transpose(images[i], (1, 2, 0)))plt.title(class_names[labels[i]])plt.axis(off)plt.show()从上图可知图像以及经过了数据增强有的图像经过了颜色抖动、翻转、旋转以及缩放。
2.2 模型建立 建立MLP模型 MLP模型由三个全连接层组成通过ReLU激活函数进行非线性变换最终输出一个10维的张量表示对每个类别的预测得分。 输入层 输入层的大小是32*32*3这对应于CIFAR-10图像的尺寸32x32像素和通道数RGB图像每个像素3个通道。 隐藏层1 第一个全连接层 (self.fc1) 具有512个神经元。激活函数ReLU修正线性单元通过 x torch.relu(self.fc1(x)) 应用。 隐藏层2 第二个全连接层 (self.fc2) 具有256个神经元。激活函数ReLU通过 x torch.relu(self.fc2(x)) 应用。 输出层 输出层 (self.fc3) 具有10个神经元对应于CIFAR-10数据集中的10个类别。激活函数Softmax通过 x F.softmax(self.fc3(x), dim1) 应用。Softmax将网络的原始输出转换为概率分布每个类别的输出值表示该类别的概率。 # 定义MLP模型
class MLP(nn.Module):def __init__(self):super(MLP, self).__init__()self.fc1 nn.Linear(32*32*3, 512)self.fc2 nn.Linear(512, 256)self.fc3 nn.Linear(256, 10)def forward(self, x):x x.view(x.size(0), -1)x torch.relu(self.fc1(x))x torch.relu(self.fc2(x))x self.fc3(x)x F.softmax(x, dim1)return x建立自定义MLP模型 在这个 DefMLP 模型中可以调整层数num_layers,激活函数 activation_func。 输入层 输入层的大小是32*32*3这对应于CIFAR-10图像的尺寸32x32像素和通道数RGB图像每个像素3个通道。 隐藏层 模型包含 num_layers 个隐藏层。每个隐藏层包含一个全连接层 (nn.Linear) 和一个激活函数。全连接层的输入大小在每个隐藏层中更新为512这是因为每个隐藏层都有512个神经元。可以选择使用 ‘relu’ 或 ‘sigmoid’ 作为激活函数通过指定 activation_func 参数来选择。 输出层 输出层 (self.fc) 具有10个神经元对应于CIFAR-10数据集中的10个类别。 前向传播 输入通过 view 操作展平然后通过隐藏层 (self.layers) 处理最后通过输出层 (self.fc) 得到模型的最终输出。 class DefMLP(nn.Module):def __init__(self, num_layers, activation_func):super(DefMLP, self).__init__()layers []input_size 32 * 32 * 3for _ in range(num_layers):layers.append(nn.Linear(input_size, 512))input_size 512 # 更新输入大小if activation_func relu:layers.append(nn.ReLU())elif activation_func sigmoid:layers.append(nn.Sigmoid())else:raise ValueError(Invalid activation function)self.layers nn.Sequential(*layers)self.fc nn.Linear(512, 10)def forward(self, x):x x.view(x.size(0), -1)x self.layers(x)x self.fc(x)return x简单卷积神经网络模型 ConvNet模型包含了两个卷积层和两个全连接层通过ReLU激活函数进行非线性变换并通过最大池化进行下采样。 卷积层1 (self.conv1): 输入通道数为3RGB图像输出通道数为64。使用3x3的卷积核进行卷积操作。使用ReLU激活函数进行非线性变换。使用1个像素的填充padding1来保持特征图的尺寸。 最大池化层 (self.pool): 使用2x2的最大池化核进行池化操作。步长为2以减小特征图的尺寸。 卷积层2 (self.conv2): 输入通道数为64输出通道数为128。使用3x3的卷积核进行卷积操作。使用ReLU激活函数进行非线性变换。使用1个像素的填充padding1来保持特征图的尺寸。 全连接层1 (self.fc1): 输入特征的大小为128x8x8因为经过两次最大池化每次都减小了图像尺寸。输出大小为512。使用ReLU激活函数进行非线性变换。 全连接层2 (self.fc2): 输入大小为512输出大小为10对应于CIFAR-10数据集中的10个类别。 前向传播 (forward 方法): 输入通过卷积层、池化层和全连接层进行前向传播。最后一层输出未经过激活函数因为在训练时一般会使用交叉熵损失函数该函数包含了softmax操作。 # 定义ConvNet模型
class ConvNet(nn.Module):def __init__(self):super(ConvNet, self).__init__()self.conv1 nn.Conv2d(3, 64, kernel_size3, stride1, padding1)self.pool nn.MaxPool2d(kernel_size2, stride2)self.conv2 nn.Conv2d(64, 128, kernel_size3, stride1, padding1)self.fc1 nn.Linear(128 * 8 * 8, 512)self.fc2 nn.Linear(512, 10)def forward(self, x):x self.pool(torch.relu(self.conv1(x)))x self.pool(torch.relu(self.conv2(x)))x x.view(x.size(0), -1)x torch.relu(self.fc1(x))x self.fc2(x)return x多层卷积神经网络模型 该模型相比上一个模型多了一个卷积层拥有3个卷基层。 卷积层1 (self.conv1): 输入通道数为3RGB图像输出通道数为64。使用3x3的卷积核进行卷积操作。使用ReLU激活函数进行非线性变换。使用1个像素的填充padding1来保持特征图的尺寸。 卷积层2 (self.conv2): 输入通道数为64输出通道数为128。使用3x3的卷积核进行卷积操作。使用ReLU激活函数进行非线性变换。使用1个像素的填充padding1来保持特征图的尺寸。 卷积层3 (self.conv3): 输入通道数为128输出通道数为256。使用3x3的卷积核进行卷积操作。使用ReLU激活函数进行非线性变换。使用1个像素的填充padding1来保持特征图的尺寸。 最大池化层 (self.pool): 使用2x2的最大池化核进行池化操作。步长为2以减小特征图的尺寸。 全连接层1 (self.fc1): 输入特征的大小为4096因为经过了三次最大池化每次都减小了图像尺寸。输出大小为512。使用ReLU激活函数进行非线性变换。 全连接层2 (self.fc2): 输入大小为512输出大小为10对应于CIFAR-10数据集中的10个类别。 前向传播 (forward 方法): 输入通过卷积层、池化层和全连接层进行前向传播。最后一层输出未经过激活函数因为在训练时一般会使用交叉熵损失函数该函数包含了softmax操作。 class ConvNet_3layers(nn.Module):def __init__(self):super(ConvNet_3layers, self).__init__()self.conv1 nn.Conv2d(3, 64, kernel_size3, stride1, padding1)self.conv2 nn.Conv2d(64, 128, kernel_size3, stride1, padding1)self.conv3 nn.Conv2d(128, 256, kernel_size3, stride1, padding1)self.pool nn.MaxPool2d(kernel_size2, stride2)self.fc1 nn.Linear(4096, 512)self.fc2 nn.Linear(512, 10)def forward(self, x):x self.pool(torch.relu(self.conv1(x)))x self.pool(torch.relu(self.conv2(x)))x self.pool(torch.relu(self.conv3(x)))x x.view(x.size(0), -1)x torch.relu(self.fc1(x))x self.fc2(x)return x少核卷积神经网络模型 该模型相比以上卷积神经网络卷积核数减半从3-64-128-256变为3-32-64-128。 卷积层1 (self.conv1): 输入通道数为3RGB图像输出通道数为32。使用3x3的卷积核进行卷积操作。使用ReLU激活函数进行非线性变换。使用1个像素的填充padding1来保持特征图的尺寸。 卷积层2 (self.conv2): 输入通道数为32输出通道数为64。使用3x3的卷积核进行卷积操作。使用ReLU激活函数进行非线性变换。使用1个像素的填充padding1来保持特征图的尺寸。 卷积层3 (self.conv3): 输入通道数为64输出通道数为128。使用3x3的卷积核进行卷积操作。使用ReLU激活函数进行非线性变换。使用1个像素的填充padding1来保持特征图的尺寸。 最大池化层 (self.pool): 使用2x2的最大池化核进行池化操作。步长为2以减小特征图的尺寸。 全连接层1 (self.fc1): 输入特征的大小为2048因为经过了三次最大池化每次都减小了图像尺寸。输出大小为512。使用ReLU激活函数进行非线性变换。 全连接层2 (self.fc2): 输入大小为512输出大小为10对应于CIFAR-10数据集中的10个类别。 前向传播 (forward 方法): 输入通过卷积层、池化层和全连接层进行前向传播。最后一层输出未经过激活函数因为在训练时一般会使用交叉熵损失函数该函数包含了softmax操作。 class ConvNet_4(nn.Module):def __init__(self):super(ConvNet_4, self).__init__()self.conv1 nn.Conv2d(3, 32, kernel_size3, stride1, padding1)self.conv2 nn.Conv2d(32, 64, kernel_size3, stride1, padding1)self.conv3 nn.Conv2d(64, 128, kernel_size3, stride1, padding1)self.pool nn.MaxPool2d(kernel_size2, stride2)self.fc1 nn.Linear(2048, 512)self.fc2 nn.Linear(512, 10)def forward(self, x):x self.pool(torch.relu(self.conv1(x)))x self.pool(torch.relu(self.conv2(x)))x self.pool(torch.relu(self.conv3(x)))x x.view(x.size(0), -1)x torch.relu(self.fc1(x))x self.fc2(x)return x建立包含BN层的卷积神经网络模型 该模型在2层卷积层后添加了层批量归一化层。 卷积层1 (self.conv1): 输入通道数为3RGB图像输出通道数为64。使用3x3的卷积核进行卷积操作。使用1个像素的填充padding1来保持特征图的尺寸。卷积后通过批归一化层 (self.bn1) 进行规范化。使用ReLU激活函数进行非线性变换。 最大池化层 (self.pool): 使用2x2的最大池化核进行池化操作。步长为2以减小特征图的尺寸。 卷积层2 (self.conv2): 输入通道数为64输出通道数为128。使用3x3的卷积核进行卷积操作。使用1个像素的填充padding1来保持特征图的尺寸。卷积后通过批归一化层 (self.bn2) 进行规范化。使用ReLU激活函数进行非线性变换。 全连接层1 (self.fc1): 输入特征的大小为128 * 8 * 8因为经过了两次最大池化每次都减小了图像尺寸。输出大小为512。全连接层后通过批归一化层 (self.bn3) 进行规范化。使用ReLU激活函数进行非线性变换。 全连接层2 (self.fc2): 输入大小为512输出大小为10对应于CIFAR-10数据集中的10个类别。 前向传播 (forward 方法): 输入通过卷积层、池化层和全连接层进行前向传播。批归一化层被嵌入在激活函数之前有助于提高训练稳定性和加速收敛。最后一层输出未经过激活函数因为在训练时一般会使用交叉熵损失函数该函数包含了softmax操作。 # 定义卷积神经网络模型包含BN层
class ConvNetBN(nn.Module):def __init__(self):super(ConvNetBN, self).__init__()self.conv1 nn.Conv2d(3, 64, kernel_size3, stride1, padding1)self.bn1 nn.BatchNorm2d(64)self.pool nn.MaxPool2d(kernel_size2, stride2)self.conv2 nn.Conv2d(64, 128, kernel_size3, stride1, padding1)self.bn2 nn.BatchNorm2d(128)self.fc1 nn.Linear(128 * 8 * 8, 512)self.bn3 nn.BatchNorm1d(512)self.fc2 nn.Linear(512, 10)def forward(self, x):x self.pool(torch.relu(self.bn1(self.conv1(x))))x self.pool(torch.relu(self.bn2(self.conv2(x))))x x.view(x.size(0), -1)x torch.relu(self.bn3(self.fc1(x)))x self.fc2(x)return x2.3 模型训练 建立训练函数 本次实验采用了两种训练函数一种是普通的训练函数另一种是采用早停法的训练函数。 train(model, optimizer, criterion, num_epochs)训练测试函数根据输入的模型model、优化器optimizer以及损失函数criterion进行指定轮次num_epochs的训练。训练后在测试集上进行测试并将训练集、测试集上该轮次的准确率和损失记录并返回。 # 训练函数
def train(model, optimizer, criterion, num_epochs):train_losses []train_accuracies []test_losses []test_accuracies []model.to(device)for epoch in range(num_epochs):model.train()running_loss 0.0correct_train 0total_train 0for inputs, labels in train_loader:inputs, labels inputs.to(device), labels.to(device)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_train labels.size(0)correct_train (predicted labels).sum().item()train_accuracy correct_train / total_traintrain_losses.append(running_loss / len(train_loader))train_accuracies.append(train_accuracy)# 在测试集上评估模型model.eval()running_loss 0.0correct_test 0total_test 0with torch.no_grad():for inputs, labels in test_loader:inputs, labels inputs.to(device), labels.to(device)outputs model(inputs)loss criterion(outputs, labels)running_loss loss.item()_, predicted torch.max(outputs.data, 1)total_test labels.size(0)correct_test (predicted labels).sum().item()test_accuracy correct_test / total_testtest_losses.append(running_loss / len(test_loader))test_accuracies.append(test_accuracy)print(fEpoch {epoch 1}/{num_epochs}, fTrain Loss: {train_losses[-1]:.4f}, Train Acc: {train_accuracy:.4f}, fTest Loss: {test_losses[-1]:.4f}, Test Acc: {test_accuracy:.4f})return train_losses, train_accuracies, test_losses, test_accuraciestrain_with_early_stopping(model, optimizer, criterion, num_epochs, patience3)采用早停法的训练函数输出参数里比以上函数多了个patience代表若训练时若测试集上的loss连续3次都未降低则停止训练。 def train_with_early_stopping(model, optimizer, criterion, num_epochs, patience3):train_losses []train_accuracies []test_losses []test_accuracies []model.to(device)best_validation_loss float(inf) # 初始最佳验证集损失为正无穷early_stopping_counter 0 # 连续未减小的epoch计数器for epoch in range(num_epochs):model.train()running_loss 0.0correct_train 0total_train 0for inputs, labels in train_loader:inputs, labels inputs.to(device), labels.to(device)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_train labels.size(0)correct_train (predicted labels).sum().item()train_accuracy correct_train / total_traintrain_losses.append(running_loss / len(train_loader))train_accuracies.append(train_accuracy)# 在验证集上评估模型model.eval()running_loss 0.0correct_test 0total_test 0with torch.no_grad():for inputs, labels in test_loader:inputs, labels inputs.to(device), labels.to(device)outputs model(inputs)loss criterion(outputs, labels)running_loss loss.item()_, predicted torch.max(outputs.data, 1)total_test labels.size(0)correct_test (predicted labels).sum().item()test_accuracy correct_test / total_testtest_losses.append(running_loss / len(test_loader))test_accuracies.append(test_accuracy)print(fEpoch {epoch 1}/{num_epochs}, fTrain Loss: {train_losses[-1]:.4f}, Train Acc: {train_accuracy:.4f}, fTest Loss: {test_losses[-1]:.4f}, Test Acc: {test_accuracy:.4f})# 判断是否进行早停if test_losses[-1] best_validation_loss:best_validation_loss test_losses[-1]early_stopping_counter 0else:early_stopping_counter 1if early_stopping_counter patience:print(fValidation loss has not decreased for {patience} consecutive epochs. Early stopping...)breakreturn train_losses, train_accuracies, test_losses, test_accuracies绘制曲线函数 用于模型训练后生成模型在训练集、测试集的损失和准确率曲线。 def drawlines(train_losses, train_accuracies, test_losses, test_accuracies):# 绘制损失和准确率曲线plt.figure(figsize(10, 4))plt.subplot(1, 2, 1)plt.plot(train_losses, labelTrain Loss)plt.plot(test_losses, labelTest Loss)plt.title(fLoss Curve)plt.xlabel(Epochs)plt.ylabel(Loss)plt.legend()plt.subplot(1, 2, 2)plt.plot(train_accuracies, labelTrain Accuracy)plt.plot(test_accuracies, labelTest Accuracy)plt.title(fAccuracy Curve)plt.xlabel(Epochs)plt.ylabel(Accuracy)plt.legend()各类别测试函数 用于测试模型再各个类别上的准确率并返回分类准确率列表。 def ClassTest(model):model.to(device)model.eval()class_names [airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck]class_correct list(0. for i in range(10))class_total list(0. for i in range(10))for imgs, labels in test_loader:imgs, labels imgs.to(device), labels.to(device)outputs model(imgs)_, preds torch.max(outputs, 1)c (preds labels)c c.squeeze()for i in range(4):label labels[i]class_correct[label] c[i]class_total[label] 1class_accuarcy[] for i in range(10):print(fAccuracy of {class_names[i]:10} : {np.round(100 * class_correct[i].detach().cpu().numpy() / class_total[i], 2)}%)class_accuarcy.append(np.round(100 * class_correct[i].detach().cpu().numpy() / class_total[i], 2))return class_accuarcy2.3.2 模型训练 MLP模型训练 采用交叉熵损失函数训练优化器采用SGD优化器学习率为0.01动量为0.9训练20轮。 model MLP().to(device)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9)# 训练模型
num_epochs 20
train_losses_mlp, train_accuracies_mlp, test_losses_mlp, test_accuracies_mlp train(model, optimizer, criterion, num_epochs)drawlines(train_losses_mlp, train_accuracies_mlp, test_losses_mlp, test_accuracies_mlp)由上图对比可知MLP的训练结果明显差于同层数的卷积神经网络。 MLP对比训练 对比不同MLP层数、激活函数、优化器对MLP网络训练的影响。以下对比实验是在未采用数据增强的数据集训练得到的可以看出出现了明显的过拟合。 由上图可知虽然出现了过拟合但是可以判断出4层relu为激活函数adam优化器的MLP训练结果最好。 CNN模型训练 对比学习率对CNN模型的影响 采用交叉熵为损失函数SGD作为优化器学习率为0.01动量为0.9。 model ConvNet().to(device)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9)# 训练模型
num_epochs 20
train_losses_cnn1, train_accuracies_cnn1, test_losses_cnn1, test_accuracies_cnn1 train(model, optimizer, criterion, num_epochs)drawlines(train_losses_cnn1, train_accuracies_cnn1, test_losses_cnn1, test_accuracies_cnn1)采用交叉熵为损失函数SGD作为优化器学习率为0.001动量为0.9。 model ConvNet().to(device)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.001, momentum0.9)# 训练模型
num_epochs 20
train_losses_cnn2, train_accuracies_cnn2, test_losses_cnn2, test_accuracies_cnn2 train(model, optimizer, criterion, num_epochs)drawlines(train_losses_cnn2, train_accuracies_cnn2, test_losses_cnn2, test_accuracies_cnn2)由上面两组结果对比可知lr0.01的模型完成了拟合并有些许过拟合而lr0.001 的模型欠拟合前者测试集的准确率也更高。 多层CNN模型训练 采用3层的CNN模型训练交叉熵为损失函数SGD作为优化器学习率为0.01动量为0.9与2层的进行对比。 model ConvNet_3layers().to(device)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9)# 训练模型
num_epochs 20
train_losses_cnn3, train_accuracies_cnn3, test_losses_cnn3, test_accuracies_cnn3 train(model, optimizer, criterion, num_epochs)drawlines(train_losses_cnn3, train_accuracies_cnn3, test_losses_cnn3, test_accuracies_cnn3)对比可知相比于2层卷积层3层卷积层的卷积神经网络训练准确率更高。 少核CNN模型训练 采用卷积核核数更少的模型训练交叉熵为损失函数SGD作为优化器学习率为0.01动量为0.9与核数多的模型进行对比。 model ConvNet_4().to(device)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9)# 训练模型
num_epochs 20
train_losses_cnn4, train_accuracies_cnn4, test_losses_cnn4, test_accuracies_cnn4 train(model, optimizer, criterion, num_epochs)drawlines(train_losses_cnn4, train_accuracies_cnn4, test_losses_cnn4, test_accuracies_cnn4)相比于卷积核更多的模型3-64-128-256卷积核更少的模型 3-32-64-128 训练得到的准确率更低。 采用批归一化的卷积网络模型训练 采用添加批量归一化的卷积网络进行训练交叉熵为损失函数SGD作为优化器学习率为0.01动量为0.9。 # 批归一化# 选择模型
model ConvNetBN().to(device)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9)# 训练模型
num_epochs 20
train_losses_CnnBN, train_accuracies_CnnBN, test_losses_CnnBN, test_accuracies_CnnBN train(model, optimizer, criterion, num_epochs)# 绘制曲线
drawlines(train_losses_CnnBN, train_accuracies_CnnBN, test_losses_CnnBN, test_accuracies_CnnBN)相比于未进行批量归一化BN的卷积神经网络该模型训练得到的准确率更高。 采用早停法的ConvNetBN模型训练 采用了交叉熵为损失函数SGD为优化器学习率为0.01动量为0.9进行训练并设置早停步数为3。即如果3步内测试集loss未下降则停止训练。 # 早停法# 选择模型
model ConvNetBN().to(device)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9)# 训练模型
num_epochs 20# 调用训练函数
train_losses_CnnBN_Estop, train_accuracies_CnnBN_Estop, test_losses_CnnBN_Estop, test_accuracies_CnnBN_Estop train_with_early_stopping(model, optimizer, criterion, num_epochs)# 绘制曲线
drawlines(train_losses_CnnBN_Estop, train_accuracies_CnnBN_Estop, test_losses_CnnBN_Estop, test_accuracies_CnnBN_Estop)采用早停法时进行到12轮时就停止了但是模型明显还是处于欠拟合的状态应适当调高限制的步数。 采用早停法Adam优化器的ConvNetBN模型训练 采用了交叉熵为损失函数Adam为优化器学习率为0.01进行训练并设置早停步数为3。 # 早停法Adam优化器# 选择模型
model ConvNetBN().to(device)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer optim.Adam(model.parameters(), lr0.01)# 训练模型
num_epochs 20# 调用训练函数
train_losses_CnnBN_Estop_Adam, train_accuracies_CnnBN_Estop_Adam, test_losses_CnnBN_Estop_Adam, test_accuracies_CnnBN_Estop_Adam train_with_early_stopping(model, optimizer, criterion, num_epochs)# 绘制曲线
drawlines(train_losses_CnnBN_Estop_Adam, train_accuracies_CnnBN_Estop_Adam, test_losses_CnnBN_Estop_Adam, test_accuracies_CnnBN_Estop_Adam)对比可知Adam优化器在该神经网络的表现不如SGD优化器。 ResNet模型训练 采用预训练模型 ResNet18 进行训练交叉熵为损失函数SGD为优化器学习率为0.01动量为0.9进行训练训练20轮。 # 选择模型
model torchvision.models.resnet18(pretrainedTrue)
num_features model.fc.in_features
model.fc nn.Linear(num_features, 10)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9)# 训练模型
num_epochs 20# 调用训练函数
train_losses, train_accuracies, test_losses, test_accuracies train(model, optimizer, criterion, num_epochs)# 绘制曲线
drawlines(train_losses, train_accuracies, test_losses, test_accuracies)由上图可知ResNet训练得到的结果明显好于自己构建的卷积网络准确率有着大幅度的提升。 添加BN层的ResNet模型训练 这个模型是在预训练的ResNet18基础上进行修改的用于适应CIFAR-10图像分类任务。 预训练的ResNet18: 从torchvision库中加载ResNet18的预训练模型 (resnet18_pretrained)。预训练模型包含卷积层、批归一化层、残差块residual blocks和全连接层。 修改全连接层: 获取ResNet18最后一个全连接层的输入特征数量 (num_features)。将原始全连接层替换为适应CIFAR-10类别数的新全连接层 (nn.Linear(num_features, 10))。 添加Batch Normalization层: 使用nn.Sequential定义模型。通过nn.AdaptiveAvgPool2d(1)进行全局平均池化将特征图大小调整为1x1。使用nn.Flatten()将特征张量展平为一维。添加nn.BatchNorm1d(num_features)对全连接层的输入进行批归一化。最后添加新的全连接层 (nn.Linear(num_features, 10))用于输出CIFAR-10的类别分数。 # 加载预训练的ResNet18模型
resnet18_pretrained torchvision.models.resnet18(pretrainedTrue)# 修改最后的全连接层以适应CIFAR-10的类别数10类
num_features resnet18_pretrained.fc.in_features
resnet18_pretrained.fc nn.Linear(num_features, 10)# 添加Batch Normalization层
model nn.Sequential(resnet18_pretrained.conv1,resnet18_pretrained.bn1,resnet18_pretrained.relu,resnet18_pretrained.layer1,resnet18_pretrained.layer2,resnet18_pretrained.layer3,resnet18_pretrained.layer4,nn.AdaptiveAvgPool2d(1),nn.Flatten(), # 将特征张量展平nn.BatchNorm1d(num_features), # Batch Normalization层nn.Linear(num_features, 10), # 新的全连接层
)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer_1 optim.SGD(model.parameters(), lr0.01, momentum0.9)
optimizer_2 optim.SGD(model.parameters(), lr0.001, momentum0.9)# 调用训练函数
train_losses_1, train_accuracies_1, test_losses_1, test_accuracies_1 train(model, optimizer_1, criterion, num_epochs10)
train_losses_2, train_accuracies_2, test_losses_2, test_accuracies_2 train(model, optimizer_2, criterion, num_epochs10)# 绘制曲线
drawlines(train_losses_1train_losses_2, train_accuracies_1train_accuracies_2, test_losses_1test_losses_2, test_accuracies_1test_accuracies_2)该模型训练分成两个部分前10个轮次采用学习率为0.01的SGD进行训练后10个轮次采用学习率为0.001的SGD进行训练。 由图可知改模型训练准确率有着明显提升测试集准确率首次上了90%。 采用Adam作为优化器的ResNet_BN模型训练 将SGD优化器替换为Adam优化器损失函数仍未交叉熵损失。 # 加载预训练的ResNet18模型
resnet18_pretrained torchvision.models.resnet18(pretrainedTrue)# 修改最后的全连接层以适应CIFAR-10的类别数10类
num_features resnet18_pretrained.fc.in_features
resnet18_pretrained.fc nn.Linear(num_features, 10)# 添加Batch Normalization层
model nn.Sequential(resnet18_pretrained.conv1,resnet18_pretrained.bn1,resnet18_pretrained.relu,resnet18_pretrained.layer1,resnet18_pretrained.layer2,resnet18_pretrained.layer3,resnet18_pretrained.layer4,nn.AdaptiveAvgPool2d(1),nn.Flatten(), # 将特征张量展平nn.BatchNorm1d(num_features), # Batch Normalization层nn.Linear(num_features, 10), # 新的全连接层
)# 定义损失函数和优化器
criterion nn.CrossEntropyLoss()
optimizer optim.Adam(model.parameters())# 训练模型
num_epochs 20# 调用训练函数
train_losses, train_accuracies, test_losses, test_accuracies train(model, optimizer, criterion, num_epochs)# 绘制曲线
drawlines(train_losses, train_accuracies, test_losses, test_accuracies)采用Adam作为优化器的训练结果不如采用SGD作为优化器的训练结果。
2.4 模型对比 对比各个模型的能力 分别对比了以下上面训练果的模型的 训练集与测试集 上的 损失和准确率 。 MLP模型SGD优化器CNN模型2层SGD优化器lr0.01CNN模型2层SGD优化器lr0.001CNN模型3层SGD优化器lr0.01CNN模型3层SGD优化器lr0.01卷积核减半CNNBN模型SGD优化器lr0.01CNNBN模型SGD优化器lr0.01早停法CNNBN模型Adam优化器早停法ResNet模型SGD优化器ResNetBN模型SGD优化器ResNetBN模型Adam优化器 epochs list(range(1,21))
labels[MLPSGD,CNNSGD,lr0.01,CNNSGD,lr0.001,CNNSGD,3层,CNNSGD,3层卷积核减半,CNN_BNSGD,CNN_BNSGD,早停法,CNN_BNAdam,早停法,ResNetSGD,ResNet_BNSGD,ResNet_BNAdam]plt.rcParams[font.family] [sans-serif]
plt.rcParams[font.sans-serif] [SimHei]# 绘制 Train Loss 和 Test Loss 对比图
plt.figure(figsize(12, 10))
plt.subplot(2, 2, 1)
for i, train_loss in enumerate(train_losses_all):plt.plot(epochs[:len(train_loss)], train_loss, labellabels[i])
plt.xticks(range(0, 21, 2), range(0, 21, 2))
plt.title(Train Losses Comparison)
plt.xlabel(Epochs)
plt.ylabel(Loss)plt.subplot(2, 2, 2)
for i, test_loss in enumerate(test_losses_all):plt.plot(epochs[:len(test_loss)], test_loss, labellabels[i])
plt.xticks(range(0, 21, 2), range(0, 21, 2))
plt.title(Test Losses Comparison)
plt.xlabel(Epochs)
plt.ylabel(Loss)# 绘制 Train Accuracy 和 Test Accuracy 对比图
plt.subplot(2, 2, 3)
for i, train_accuracy in enumerate(train_accuracies_all):plt.plot(epochs[:len(train_accuracy)], train_accuracy, labellabels[i])
plt.xticks(range(0, 21, 2), range(0, 21, 2))
plt.title(Train Accuracy Comparison)
plt.xlabel(Epochs)
plt.ylabel(Accuracy)plt.subplot(2, 2, 4)
for i, test_accuracy in enumerate(test_accuracies_all):plt.plot(epochs[:len(test_accuracy)], test_accuracy, labellabels[i])
plt.xticks(range(0, 21, 2), range(0, 21, 2))
plt.title(Test Accuracy Comparison)
plt.xlabel(Epochs)
plt.ylabel(Accuracy)
plt.legend()plt.tight_layout()
plt.show()结果如上图所示明显可知ResNetBN模型SGD优化器的结果最好而MLP的结果最差。 对比模型在各个类别上面的准确率 # 类别名称
class_names [airplane, automobile, bird, cat, deer, dog, frog, horse, ship, truck]# 绘制折线图
plt.figure(figsize(10, 6))for i in range(len(class_accuarcy_all)):plt.plot(class_names, class_accuarcy_all[i], labellabels[i])plt.xlabel(Categories)
plt.ylabel(Accuracy(%))
plt.title(Classification Accuracy for Different Models)plt.legend()
plt.show()由上图可知模型普遍对猫、狗的识别准确率较低而对飞机、汽车以及青蛙的识别准确率较高。表现最好的模型仍是ResNet_BNSGD最差的仍是MLP模型。