qidao123.com技术社区-IT企服评测·应用市场

标题: 从零开始:用PyTorch构建CIFAR-10图像分类模型到达接近1的准确率 [打印本页]

作者: 宝塔山    时间: 7 天前
标题: 从零开始:用PyTorch构建CIFAR-10图像分类模型到达接近1的准确率
为了加强代码可读性,代码均使用Chatgpt给每一行代码都参加了注释,方便各人在本文代码的底子上举行改进优化。
本文是搭建了一个稍微优化了一下的模型,训练200个epoch,准确率到达了99.74%,简朴完成了一下CIFAR-10数据集的实验,再调调参可能就到100%了吧,算是一个入门学习吧 ~
完整代码链接: MachineLearningCourseWork
数据: data5 - CIFAR10
完整代码ipynb格式文件: work5-CIFAR10.ipynb
【本文参考资料】

  

1. 数据集介绍

1.1 数据集概述

数据集名称:CIFAR-10
官方链接:https://www.cs.toronto.edu/~kriz/cifar.html
数据集简介:
CIFAR-10 数据集是一个广泛用于图像分类使命的基准数据集,由 Alex Krizhevsky 在 2009 年创建,是计算机视觉范畴最常用的数据集之一。它包罗 60000 张 32x32 彩色图像,分为 10 个差别的种别,每个种别有 6000 张图片,此中 50000 张用于训练模型,10000 张用于测试模型性能。

1.2 数据集结构


1.3 数据集格式


1.4 数据集应用场景


1.5 数据集特点

优势:

范围性:


2. 实验流程

2.1 导包

  1. import torch  # 导入 PyTorch 框架,用于构建和训练深度学习模型
  2. import torch.nn as nn  # 导入 PyTorch 的神经网络模块,用于定义神经网络结构
  3. import torch.optim as optim  # 导入 PyTorch 的优化器模块,用于定义优化算法(如 SGD、Adam 等)
  4. import torchvision  # 导入 torchvision 库,提供图像处理和预训练模型等功能
  5. import torchvision.transforms as transforms  # 导入 torchvision 的数据预处理模块,用于对图像数据进行变换(如归一化、裁剪等)
  6. from torch.utils.data import DataLoader  # 导入 PyTorch 的数据加载器模块,用于批量加载和管理数据集
  7. from datetime import datetime
  8. import time
  9. # 画图相关
  10. from sklearn.metrics import confusion_matrix
  11. import seaborn as sns
  12. import matplotlib.pyplot as plt
  13. import numpy as np # 数据计算
  14. from torch.nn.functional import softmax #softmax函数
复制代码
2.2 数据获取与加载

  1. # 定义一个数据预处理的组合操作
  2. transform = transforms.Compose([
  3.     transforms.ToTensor(),  # 将图像数据从 PIL 图像格式转换为 PyTorch 张量(Tensor),并将其值归一化到 [0, 1] 范围
  4.     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 对图像的每个通道进行标准化处理,均值为 0.5,标准差为 0.5
  5. ])
  6. # 加载 CIFAR-10 训练数据集
  7. trainset = torchvision.datasets.CIFAR10(
  8.     root='./data4',  # 数据集存储的根目录路径
  9.     train=True,  # 指定加载训练集
  10.     download=False,  # 不从网络下载数据集(假设数据已经下载到本地)
  11.     transform=transform  # 应用上面定义的预处理操作
  12. )
  13. # 加载 CIFAR-10 测试数据集
  14. testset = torchvision.datasets.CIFAR10(
  15.     root='./data4',  # 数据集存储的根目录路径
  16.     train=False,  # 指定加载测试集
  17.     download=False,  # 不从网络下载数据集(假设数据已经下载到本地)
  18.     transform=transform  # 应用上面定义的预处理操作
  19. )
复制代码
  1. # 定义每个批次的大小
  2. batch_size = 64  # 每个批次包含 64 张图像
  3. # 创建训练数据的 DataLoader
  4. trainloader = DataLoader(
  5.     trainset,  # 指定加载的训练数据集
  6.     batch_size=batch_size,  # 每个批次的大小
  7.     shuffle=True,  # 在每个 epoch 开始时随机打乱数据顺序
  8.     num_workers=2  # 使用 2 个子进程加载数据,加速数据读取
  9. )
  10. # 创建测试数据的 DataLoader
  11. testloader = DataLoader(
  12.     testset,  # 指定加载的测试数据集
  13.     batch_size=batch_size,  # 每个批次的大小
  14.     shuffle=False,  # 测试时不需要打乱数据顺序
  15.     num_workers=2  # 使用 2 个子进程加载数据,加速数据读取
  16. )
复制代码
2.3 界说网络

这里我界说的是一个比力简朴的卷积神经网络模型,包罗特征提取层和分类器层,但是团体的效果也还行了,可以改成ResNet18或ResNet34
  1. # 定义一个简单的卷积神经网络模型
  2. class TestNet(nn.Module):
  3.     def __init__(self, dropout_rate=0.5):
  4.         super(TestNet, self).__init__()  # 调用父类的初始化方法
  5.         # 特征提取部分
  6.         self.features = nn.Sequential(  # 定义特征提取层的序列
  7.             nn.Conv2d(3, 64, kernel_size=3, padding=1),  # 第一层卷积,输入通道数为3,输出通道数为64,卷积核大小为3x3,边缘填充为1
  8.             nn.BatchNorm2d(64),  # 对64个通道进行批量归一化
  9.             nn.ReLU(inplace=True),  # ReLU激活函数,inplace=True表示直接在原变量上操作,节省内存
  10.             nn.Conv2d(64, 64, kernel_size=3, padding=1),  # 第二层卷积,输入输出通道数均为64
  11.             nn.BatchNorm2d(64),  # 批量归一化
  12.             nn.ReLU(inplace=True),  # ReLU激活
  13.             nn.MaxPool2d(2, 2),  # 最大池化,池化窗口大小为2x2,步长为2
  14.             nn.Dropout2d(p=dropout_rate/2),  # 二维Dropout,随机丢弃一半的特征图,防止过拟合
  15.             nn.Conv2d(64, 128, kernel_size=3, padding=1),  # 第三层卷积,输入通道数为64,输出通道数为128
  16.             nn.BatchNorm2d(128),  # 批量归一化
  17.             nn.ReLU(inplace=True),  # ReLU激活
  18.             nn.Conv2d(128, 128, kernel_size=3, padding=1),  # 第四层卷积,输入输出通道数均为128
  19.             nn.BatchNorm2d(128),  # 批量归一化
  20.             nn.ReLU(inplace=True),  # ReLU激活
  21.             nn.MaxPool2d(2, 2),  # 最大池化
  22.             nn.Dropout2d(p=dropout_rate),  # Dropout
  23.             nn.Conv2d(128, 256, kernel_size=3, padding=1),  # 第五层卷积,输入通道数为128,输出通道数为256
  24.             nn.BatchNorm2d(256),  # 批量归一化
  25.             nn.ReLU(inplace=True),  # ReLU激活
  26.             nn.Conv2d(256, 256, kernel_size=3, padding=1),  # 第六层卷积,输入输出通道数均为256
  27.             nn.BatchNorm2d(256),  # 批量归一化
  28.             nn.ReLU(inplace=True),  # ReLU激活
  29.             nn.MaxPool2d(2, 2),  # 最大池化
  30.         )
  31.         
  32.         # 分类器部分
  33.         self.classifier = nn.Sequential(  # 定义分类器层的序列
  34.             nn.Linear(256 * 4 * 4, 512),  # 全连接层,输入特征数为256*4*4,输出特征数为512
  35.             nn.BatchNorm1d(512),  # 一维批量归一化
  36.             nn.ReLU(inplace=True),  # ReLU激活
  37.             nn.Dropout(p=dropout_rate),  # Dropout
  38.             nn.Linear(512, 256),  # 全连接层,输入特征数为512,输出特征数为256
  39.             nn.BatchNorm1d(256),  # 批量归一化
  40.             nn.ReLU(inplace=True),  # ReLU激活
  41.             nn.Dropout(p=dropout_rate/2),  # Dropout
  42.             nn.Linear(256, 10)  # 最后一层全连接层,输出10个类别
  43.         )
  44.     def forward(self, x):
  45.         x = self.features(x)  # 将输入数据通过特征提取层
  46.         x = torch.flatten(x, 1)  # 将特征图展平为一维向量,从第1维开始展平
  47.         x = self.classifier(x)  # 将展平后的特征通过分类器层
  48.         return x  # 返回最终的输出
复制代码
2.4 加强的数据预处理

  1. # 定义训练集的数据预处理操作
  2. transform_train = transforms.Compose([
  3.     transforms.RandomCrop(32, padding=4),  # 随机裁剪,裁剪大小为32x32,边缘填充为4
  4.     transforms.RandomHorizontalFlip(),  # 随机水平翻转
  5.     transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),  # 随机调整亮度、对比度和饱和度
  6.     transforms.ToTensor(),  # 将图像转换为张量
  7.     transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),  # 标准化,均值和标准差分别为CIFAR-10数据集的均值和标准差
  8. ])
  9. # 定义测试集的数据预处理操作
  10. transform_test = transforms.Compose([
  11.     transforms.ToTensor(),  # 将图像转换为张量
  12.     transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)),  # 标准化
  13. ])
复制代码
  1. net = TestNet() # 初始化
  2. device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") # GPU
  3. net.to(device)
复制代码
2.5 模型训练

  1. # 开始训练模型
  2. start_time = time.time()  # 记录训练开始时间
  3. for epoch in range(10):  # 训练 10 个 epoch
  4.     running_loss = 0.0  # 初始化每轮的累计损失
  5.     for i, data in enumerate(trainloader, 0):  # 遍历训练数据加载器
  6.         inputs, labels = data[0].to(device), data[1].to(device)  # 将输入数据和标签移动到指定设备(如 GPU)
  7.         optimizer.zero_grad()  # 清零梯度,避免梯度累积
  8.         outputs = net(inputs)  # 前向传播,计算模型输出
  9.         loss = criterion(outputs, labels)  # 计算损失值
  10.         loss.backward()  # 反向传播,计算梯度
  11.         optimizer.step()  # 更新模型参数
  12.         running_loss += loss.item()  # 累加损失值
  13.         # 每 100 批次打印一次详细信息
  14.         if i % 100 == 99:  
  15.             avg_loss = running_loss / 100  # 计算平均损失
  16.             print(f'[Epoch {epoch + 1}/{10}, Batch {i + 1}/{len(trainloader)}] '
  17.                   f'Average Loss: {avg_loss:.4f} | '
  18.                   f'Current Batch Loss: {loss.item():.4f} | '
  19.                   f'Running Loss: {running_loss:.4f}')
  20.             running_loss = 0.0  # 重置累计损失
  21.     scheduler.step()  # 更新学习率
  22. end_time = time.time()  # 记录训练结束时间
  23. elapsed_time = end_time - start_time  # 计算总耗时
  24. print(f'Finished Training. Total time taken: {elapsed_time:.2f} seconds')
  25. # 保存模型
  26. timestamp = datetime.now().strftime("%y-%m-%d-%H-%M-%S")  # 获取当前时间戳
  27. model_name = f"CIFAR10Net-{timestamp}.pth"
  28. torch.save(net.state_dict(), model_name)
  29. print(f"Model saved successfully as {model_name}")
复制代码
2.6 测试准确率

  1. # 初始化正确预测的数量和测试数据的总数
  2. correct_predictions = 0
  3. total_samples = 0
  4. # 禁用梯度计算,节省内存和计算资源
  5. with torch.no_grad():
  6.     for data in testloader:  # 遍历测试数据加载器
  7.         images, labels = data[0].to(device), data[1].to(device)  # 将图像和标签移动到指定设备
  8.         outputs = net(images)  # 前向传播,获取模型的输出
  9.         _, predicted_labels = torch.max(outputs.data, 1)  # 获取预测的类别(概率最高的类别)
  10.         total_samples += labels.size(0)  # 累加测试数据的总数
  11.         correct_predictions += (predicted_labels == labels).sum().item()  # 累加正确预测的数量
  12. # 计算准确率
  13. accuracy = 100 * correct_predictions / total_samples
  14. print(f'Accuracy: {accuracy:.2f}%')  # 打印准确率
复制代码
2.7 收集所有样本的猜测概率

  1. import numpy as np
  2. from torch.nn.functional import softmax
  3. # 初始化两个列表,用于保存所有样本的预测概率和真实标签
  4. all_preds = []  # 保存所有样本的预测概率
  5. all_labels = []  # 保存所有真实标签
  6. # 使用torch.no_grad()上下文管理器,这可以暂时禁用计算图中的梯度计算,节省内存和计算资源
  7. with torch.no_grad():
  8.     for data in testloader:  # 遍历测试数据加载器testloader
  9.         images, labels = data[0].to(device), data[1].to(device)  # 将图像和标签数据移动到指定的设备(如GPU)
  10.         outputs = net(images)  # 通过神经网络模型net获取预测输出
  11.         probabilities = softmax(outputs, dim=1)  # 将输出转换为概率分布,dim=1表示在类别维度上进行softmax操作
  12.         all_preds.append(probabilities.cpu().numpy())  # 将概率分布转移到CPU并转换为numpy数组,然后添加到all_preds列表中
  13.         all_labels.append(labels.cpu().numpy())  # 将标签数据转移到CPU并转换为numpy数组,然后添加到all_labels列表中
  14. # 合并所有批次的结果,使用numpy的concatenate函数沿着第一个轴(axis=0)合并数组
  15. all_preds = np.concatenate(all_preds, axis=0)  # 形状 [N, 10],其中N是样本总数,10是类别数
  16. all_labels = np.concatenate(all_labels, axis=0)  # 形状 [N, ],其中N是样本总数
复制代码
2.8 评价一下

2.8.1 混淆矩阵

  1. from sklearn.metrics import confusion_matrix  # 导入混淆矩阵计算函数
  2. import seaborn as sns  # 导入seaborn库,用于绘制热图
  3. import matplotlib.pyplot as plt  # 导入matplotlib库,用于绘图
  4. # 初始化两个列表,用于存储模型预测的标签和真实标签
  5. all_preds, all_labels = [], []
  6. # 使用torch.no_grad()来禁用梯度计算,因为在测试阶段不需要计算梯度
  7. with torch.no_grad():
  8.     # 遍历测试数据加载器
  9.     for data in testloader:
  10.         # 将图像数据和标签数据移动到设备上(如GPU)
  11.         images, labels = data[0].to(device), data[1].to(device)
  12.         # 将图像数据输入模型进行预测
  13.         outputs = net(images)
  14.         # 获取模型输出中概率最大的索引,即预测的类别
  15.         _, predicted = torch.max(outputs, 1)
  16.         # 将预测的类别和真实类别添加到列表中
  17.         all_preds.extend(predicted.cpu().numpy())
  18.         all_labels.extend(labels.cpu().numpy())
  19. # 计算混淆矩阵
  20. cm = confusion_matrix(all_labels, all_preds)
  21. # 设置图形的大小
  22. plt.figure(figsize=(10, 8))
  23. # 使用seaborn库绘制热图,显示混淆矩阵
  24. # annot=True表示在每个单元格中显示数值,fmt='d'表示数值格式为整数
  25. # cmap='viridis'指定使用viridis配色方案
  26. sns.heatmap(cm, annot=True, fmt='d', cmap='viridis')
  27. plt.title('Confusion Matrix')
  28. plt.xlabel('Predicted Labels')
  29. plt.ylabel('True Labels')
  30. plt.show()
复制代码

2.8.2 准确 - 召回

  1. from sklearn.metrics import precision_recall_curve, auc
  2. import matplotlib.pyplot as plt
  3. import numpy as np
  4. n_classes = 10  # CIFAR10数据集有10个类别
  5. # 初始化字典来存储每个类别的精确度、召回率和平均精确度
  6. precision = dict()
  7. recall = dict()
  8. average_precision = dict()
  9. # 遍历所有类别
  10. for i in range(n_classes):
  11.     # 提取第i类的概率作为预测分数,并生成对应的二分类标签
  12.     precision[i], recall[i], _ = precision_recall_curve(
  13.         (all_labels == i).astype(int),
  14.         all_preds[:, i]
  15.     )
  16.     average_precision[i] = auc(recall[i], precision[i])
  17. plt.figure(figsize=(12, 10), dpi=100)
  18. colors = plt.cm.viridis(np.linspace(0, 1, n_classes))
  19. for i in range(n_classes):
  20.     plt.plot(recall[i], precision[i], color=colors[i], lw=2, label=f'Class {i} (AP = {average_precision[i]:.2f})')
  21. plt.xlabel('Recall', fontsize=14, fontfamily='serif')
  22. plt.ylabel('Precision', fontsize=14, fontfamily='serif')
  23. plt.title('Precision-Recall Curve for CIFAR10', fontsize=16, fontfamily='serif')
  24. plt.legend(loc='best', fontsize=12, frameon=True)
  25. plt.grid(True, linestyle='--', alpha=0.6)
  26. plt.xlim([0.0, 1.0])
  27. plt.ylim([0.0, 1.05])
  28. plt.savefig('precision_recall_curve.png', bbox_inches='tight')
  29. plt.show()
复制代码

2.8.3 ROC曲线

  1. from sklearn.preprocessing import label_binarize
  2. from sklearn.metrics import roc_curve, auc
  3. import matplotlib.pyplot as plt
  4. import numpy as np
  5. # ROC曲线
  6. all_labels_binarized = label_binarize(all_labels, classes=np.arange(n_classes))
  7. all_preds_binarized = all_preds
  8. fpr = dict()
  9. tpr = dict()
  10. roc_auc = dict()
  11. for i in range(n_classes):
  12.     fpr[i], tpr[i], _ = roc_curve(all_labels_binarized[:, i], all_preds_binarized[:, i])
  13.     roc_auc[i] = auc(fpr[i], tpr[i])
  14. plt.figure(figsize=(12, 10), dpi=100)
  15. plt.gca().set_facecolor('#ADD8E6')
  16. colors = plt.cm.Reds(np.linspace(0.3, 1, n_classes))
  17. for i in range(n_classes):
  18.     plt.plot(fpr[i], tpr[i], color=colors[i], lw=2, label=f'Class {i} (AUC = {roc_auc[i]:.2f})')
  19. plt.plot([0, 1], [0, 1], 'k--', lw=2, label='Random Guessing')
  20. plt.xlabel('False Positive Rate', fontsize=14, fontfamily='serif')
  21. plt.ylabel('True Positive Rate', fontsize=14, fontfamily='serif')
  22. plt.title('ROC Curve for CIFAR10', fontsize=16, fontfamily='serif')
  23. plt.legend(loc='best', fontsize=12, frameon=True)
  24. plt.grid(True, linestyle='--', alpha=0.6, color='white')
  25. plt.xlim([0.0, 1.0])
  26. plt.ylim([0.0, 1.05])
  27. plt.savefig('roc_curve_red.png', bbox_inches='tight')
  28. plt.show()
复制代码



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 qidao123.com技术社区-IT企服评测·应用市场 (https://dis.qidao123.com/) Powered by Discuz! X3.4