实验13 使用预训练resnet18实现CIFAR-10分类

news2024/12/4 17:12:33

1.数据预处理

首先利用函数transforms.Compose定义了一个预处理函数transform,里面定义了两种操作,一个是将图像转换为Tensor,一个是对图像进行标准化。然后利用函数torchvision.datasets.CIFAR10下载数据集,这个函数有四个常见的初始化参数:root为数据存储的路径,如果数据已经下载,会直接从这个路径加载数据。train如果为True,表示加载训练集,train如果为False,加载测试集。download如果设置为True,表示如果本地不存在数据集,会自动从互联网上下载。transform指定一个转换函数,对数据进行预处理和数据增强等操作。所以下载训练集train_full时,train赋值为True,下载测试集时,train赋值为False。之后对下载的训练集train_full进行划分,先规定指定的大小,然后利用random_split进行划分,最后就是创建Dataloader,batch_size设为64,得到train_loader,val_loader,test_loader。

代码:

# 检查是否有可用的 GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"Using device: {device}")

# 数据预处理和增强
transform = transforms.Compose([
    transforms.ToTensor(),  # 将图像转换为Tensor
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 图像标准化
])

# 下载 CIFAR-10 数据集
train_full = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
test = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)

# 划分训练集(40,000)和验证集(10,000)
train_size = int(0.8 * len(train_full))  # 80% 用于训练
val_size = len(train_full) - train_size  # 剩余 20% 用于验证
train_data, val_data = random_split(train_full, [train_size, val_size])

# 创建 DataLoader
train_loader = DataLoader(train_data, batch_size=64, shuffle=True)
val_loader = DataLoader(val_data, batch_size=64, shuffle=False)
test_loader = DataLoader(test, batch_size=64, shuffle=False)

2.模型构建

模型构建就比较简单,直接使用使用pytorch定义的库函数,只有一行代码:

model = models.resnet18(pretrained=False),pretrained=False表示不使用在Imagenet上预训练的权重,pretrained=True表示使用在Imagenet上预训练的权重。因为这个模型是训练Imagenet构建的模型,要想让这个模型适应新任务,需要获取最后一层的输入特征数,然后利用一个全连接层将输出改为10。

代码:

# 初始化 ResNet-18 模型
model = models.resnet18(pretrained=True)
# 修改最后一层(全连接层),适应新的任务
num_ftrs = model.fc.in_features  # 获取最后一层的输入特征数
model.fc = torch.nn.Linear(num_ftrs, 10)  # 将输出改为 10 个类别(例如 CIFAR-10)

3.模型训练

创建Runner类,管理训练、评估、测试和预测过程。还是之前的一套东西,首先是一个init函数,用于初始化数据集、损失函数、优化器等。train函数用于计算在训练集上的loss,并反向传播更新参数。evaluate函数用于计算在验证集上的损失,不用反向传播更新模型的参数,同时根据evaluate函数得到的损失判断是否保存最优模型,利用state_dict函数保存最优模型。test函数首先加载最优模型,然后在测试集计算最优模型的准确率。predict函数预测某个图像属于某个类别的概率,虽然resnet最后一层没有softmax,但是也可以根据最后一层得到的10个logits(未经过归一化的原始输出)取最大来判断图像属于某一类(因为这10个值也是有大小关系的,softmax函数不会修改这10个值的大小关系)。

定义学习率=0.01、批次大小=30、损失函数为交叉熵损失nn.CrossEntropyLoss()、优化器为Adam。

实例化Runner,调用train函数,开始训练。

代码:

class Runner:
    def __init__(self, model, train_loader, val_loader, test_loader, criterion, optimizer, device):
        self.model = model.to(device)  # 将模型移到GPU
        self.train_loader = train_loader
        self.val_loader = val_loader
        self.test_loader = test_loader
        self.criterion = criterion
        self.optimizer = optimizer
        self.device = device
        self.best_model = None
        self.best_val_loss = float('inf')
        self.train_losses = []  # 存储训练损失
        self.val_losses = []  # 存储验证损失

    def train(self, epochs=10):
        for epoch in range(epochs):
            self.model.train()
            running_loss = 0.0

            for inputs, labels in self.train_loader:
                # 将数据移到GPU
                inputs, labels = inputs.to(self.device), labels.to(self.device)

                self.optimizer.zero_grad()
                outputs = self.model(inputs)
                loss = self.criterion(outputs, labels)
                loss.backward()
                self.optimizer.step()

                running_loss += loss.item()

            # 计算平均训练损失
            train_loss = running_loss / len(self.train_loader)
            self.train_losses.append(train_loss)

            # 计算验证集上的损失
            val_loss = self.evaluate()
            self.val_losses.append(val_loss)

            print(f'Epoch [{epoch + 1}/{epochs}], Train Loss: {train_loss:.4f}, Val Loss: {val_loss:.4f}')

            # 如果验证集上的损失最小,保存模型
            if val_loss < self.best_val_loss:
                self.best_val_loss = val_loss
                self.best_model = self.model.state_dict()

    def evaluate(self):
        self.model.eval()
        val_loss = 0.0
        with torch.no_grad():
            for inputs, labels in self.val_loader:
                # 将数据移到GPU
                inputs, labels = inputs.to(self.device), labels.to(self.device)

                outputs = self.model(inputs)
                loss = self.criterion(outputs, labels)
                val_loss += loss.item()
        return val_loss / len(self.val_loader)

    def test(self):
        self.model.load_state_dict(self.best_model)
        self.model.eval()
        correct = 0
        total = 0
        with torch.no_grad():
            for inputs, labels in self.test_loader:
                # 将数据移到GPU
                inputs, labels = inputs.to(self.device), labels.to(self.device)

                outputs = self.model(inputs)
                _, predicted = torch.max(outputs, 1)
                total += labels.size(0)
                correct += (predicted == labels).sum().item()

        test_accuracy = correct / total
        print(f'Test Accuracy: {test_accuracy:.4f}')

    def predict(self, image):
        self.model.eval()
        image = image.to(self.device)  # 将图像移到GPU
        with torch.no_grad():
            output = self.model(image)
            _, predicted = torch.max(output, 1)
            return predicted.item()

    def visualize_and_predict(self, index):
        """
        针对训练集中的某一张图片进行预测,并可视化图片。
        :param index: 训练集中的图片索引
        """
        # 获取训练集中的第 index 张图片
        image, label = self.train_loader.dataset[index]

        # 将图像移到GPU(如果需要)
        image = image.unsqueeze(0).to(self.device)  # 增加一个维度作为batch size

        # 可视化图像
        plt.imshow(image.cpu().squeeze().numpy(), cmap='gray')  # 假设是灰度图,若是彩色图像要调整
        plt.title(f"True Label: {label}")
        plt.show()

        # 预测该图片的类别
        predicted_label = self.predict(image)
        print(f"Predicted Label: {predicted_label}")
# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.01)

# 实例化Runner类
runner = Runner(model, train_loader, val_loader, test_loader, criterion, optimizer, device)

# 训练模型
runner.train(epochs=30)
# 绘制损失曲线
plt.figure(figsize=(10, 6))
plt.plot(runner.train_losses, label='Train Loss')
plt.plot(runner.val_losses, label='Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Loss Curve')
plt.legend()
plt.grid()
plt.show()

4.模型评价

调用test函数,计算在测试集上的准确率。

代码:

# 在最优模型上评估测试集准确率
runner.test()

5.模型预测

在训练集任意选取一个图像,获取图像的image和标签label,因为图像已经经过了transform的变换,所以这个图像不需要transform,只需要添加一个维度1作为batch_size,可视化图像和真实标签,然后调用predict函数进行预测,输出真实类别。

代码:

# CIFAR-10 是 RGB 图像,确保正确显示
# 将 Tensor 转换为 numpy 数组并调整维度顺序为 HWC (Height, Width, Channels)
image_np = image.numpy().transpose((1, 2, 0))  # 从 CHW 转为 HWC

# 可视化图像
plt.imshow(image_np)
plt.title(f"True Label: {label}")
plt.show()

# 直接将图像传递给预测函数,不再需要 transform
# 但是要确保图像传入时是正确的 batch size 形状,即增加一个 batch 维度
image_transformed = image.unsqueeze(0).to(device)  # 增加一个维度作为 batch size

# 预测该图片的类别
predicted_label = runner.predict(image_transformed)
print(f"Predicted Label: {predicted_label}")

6.实验结果与分析

不使用预训练权重的损失变化、准确率和预测结果

使用预训练权重的损失变化、准确率和预测结果

通过观察损失变化,我们发现两个模型在训练集上的loss一直在减小,说明模型的参数一直在更新。但是在验证集上的损失一开始是下降的,但是后来不断增大,我觉得是因为模型过拟合了。但是可以发现在没有预训练权重上的最优验证损失是比有预训练权重的模型上的最优验证损失大的。通过保存最优模型,在最优模型上计算准确率,发现在没有预训练权重的模型得到的准确率是0.7332,在使用预训练权重的模型得到的准确率是0.7431。

结论:通过对比在验证集上的最优验证损失和在测试集上的准确率,得到结论使用了预训练的模型效果要更好。

7.总结与心得体会

总结:

1.预训练模型:

预训练模型是指在一个大规模数据集上(如 ImageNet、COCO 等)经过训练的模型。这个模型已经学习到了一些通用的特征,比如图像中的边缘、纹理、颜色、形状等,或者文本中的语法、词汇关系等。这些特征是从数据中自动学习的,并且在很多不同的任务中都有用。

例子:

在图像分类任务中,ResNet、VGG、Inception 等深度神经网络在 ImageNet 上经过训练后,它们可以识别成千上万种不同的物体。由于这些物体特征具有广泛的普适性,我们可以将这些模型用于其他图像分类任务(例如 Cifar-10、Cifar-100),而无需从头开始训练。

在自然语言处理(NLP)中,像 BERT、GPT 等预训练语言模型已经在大量的文本数据上训练过,学习了丰富的语言知识。因此,我们可以将这些模型应用于文本分类、情感分析、问答等任务。

预训练模型的优势:

节省计算资源:训练深度神经网络需要大量的计算资源和时间,尤其是在大规模数据集上。通过使用预训练模型,用户可以避免从零开始训练,直接利用现成的知识。

提高效果:预训练模型已经学习到了一些通用的特征,可以加速学习过程,并且通常能够取得比从头开始训练更好的效果。
2. 迁移学习(Transfer Learning)

迁移学习是一种利用在一个任务上学到的知识,来帮助在另一个相关任务上进行学习的技术。换句话说,它将一个任务中的学习成果迁移到另一个任务中,特别是在目标任务的数据较少时。

迁移学习的核心思想是:如果一个模型在某个任务上已经学到了一些有用的特征,那么这些特征可以迁移到另一个任务上,帮助模型更好地学习。

迁移学习的典型流程:

模型加载:加载一个在大数据集上预训练的模型(如 ResNet、VGG、BERT 等)。

模型微调:对模型的部分层进行微调,或者只训练新添加的层(如分类层)。

应用于新任务:将经过微调的模型应用于新的、可能较小的数据集。

迁移学习的类型

迁移学习有多种不同的方式,常见的有以下几种:

微调(Fine-Tuning):使用预训练模型的权重,并对某些层或整个模型进行微调,以适应新的任务和数据。

通常会冻结前几层(因为它们学习的是通用特征),只训练后几层(专门针对当前任务)。

特征提取(Feature Extraction):使用预训练模型的特征提取能力,将前几层的权重固定,不更新,仅训练新加的全连接层或输出层。

零-shot 学习:在一些任务中,预训练模型被直接应用到目标任务,而不进行微调,特别是当目标任务的标注数据非常少时。

迁移学习的应用:

计算机视觉:在一个大规模的数据集(如 ImageNet)上训练的模型可以用于许多不同的图像分类任务,例如识别猫、狗、车、飞机等物体,或者在医疗影像、无人驾驶等领域中应用。

自然语言处理(NLP):例如,BERT 和 GPT 等模型可以在情感分析、命名实体识别、机器翻译等任务上进行迁移学习。

3. 预训练模型和迁移学习的关系

预训练模型和迁移学习是紧密相关的。迁移学习通常依赖于预训练模型,使用在一个任务中学到的知识来帮助另一个任务。在迁移学习中,预训练模型提供了一个良好的起点,减少了从头开始训练的难度和所需的数据量。

预训练模型与迁移学习的关系:

预训练模型是迁移学习的基础,因为迁移学习的一个关键步骤是使用已经在其他任务上训练好的模型。

迁移学习则是使用这些预训练模型的技术,它通过微调或特征提取等方式,将预训练模型的知识应用到新任务中。

使用torchvision.datasets的常见参数:

root:数据存储的路径。如果数据已经下载,它会直接从该路径加载数据。

train:如果设置为 True,加载训练集;如果设置为 False,加载测试集。

download:如果设置为 True,如果本地不存在数据集,它会自动从互联网上下载。

transform:指定一个转换函数,对数据进行预处理和数据增强等操作。

transforms.Compose 是 torchvision.transforms 模块中的一个函数,用于将多个图像预处理操作组合成一个复合操作。在神经网络训练中,常常需要对输入图像进行多种预处理,例如将图像转换为张量(Tensor)、标准化、数据增强等。transforms.Compose 允许你将这些操作按顺序组合在一起,并一次性应用于输入图像。

心得体会:

这个实验直接调用预训练的resnet18进行CIFAR-10数据集的分类,因为这个模型是在Imagenet数据集上训练得到的,所以适用于新的任务需要微调模型。通过对比没有预训练权重的模型和有预训练权重的模型的训练效果,发现还是有预训练权重得到的结果比较好,因为预训练模型已经学习到了一些通用的特征,可以加速学习过程,通常能够取得比从头开始训练更好的效果。在实际应用中在理解模型内部实现的基础上,直接调用高层API是一个不错的选择,可以减少代码量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2253162.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

P1226 快速幂

【STUACM-算法入门-快速幂】https://www.bilibili.com/video/BV1Hi4y1L7qB?p2&vd_sourcee583d26dc0028b3e6ea220aadf5bc7fe 想先把a的b次方算出来再对p取模是不可能的&#xff0c;因为肯定超出long long 范围。 需要知道&#xff1a;(x*y)mod p (x mod p)*(y mod p) mo…

【力扣热题100】—— Day3.反转链表

你不会永远顺遂&#xff0c;更不会一直年轻&#xff0c;你太安静了&#xff0c;是时候出发了 —— 24.12.2 206. 反转链表 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&…

containerd安装

containerd安装 参考资料前置准备Installing containerdInstalling runcInstalling CNI plugins containerd is available as a daemon for Linux and Windows. It manages the complete container lifecycle of its host system, from image transfer and storage to containe…

底部导航栏新增功能按键

场景需求&#xff1a; 在底部导航栏添加power案件&#xff0c;单击息屏&#xff0c;长按 关机 如下实现图 借此需求&#xff0c;需要掌握技能&#xff1a; 底部导航栏如何实现新增、修改、删除底部导航栏流程对底部导航栏部分样式如何修改。 比如放不下、顺序排列、坑点如…

Python 入门教程(2)搭建环境 | 2.4、VSCode配置Node.js运行环境

文章目录 一、VSCode配置Node.js运行环境1、软件安装2、安装Node.js插件3、配置VSCode4、创建并运行Node.js文件5、调试Node.js代码 一、VSCode配置Node.js运行环境 1、软件安装 安装下面的软件&#xff1a; 安装Node.js&#xff1a;Node.js官网 下载Node.js安装包。建议选择L…

火语言RPA流程组件介绍--键盘按键

&#x1f6a9;【组件功能】&#xff1a;模拟键盘按键 配置预览 配置说明 按键 点击后,在弹出的软键盘上选择需要的按键 执行后等待时间(ms) 默认值300,执行该组件后等待300毫秒后执行下一个组件. 输入输出 输入类型 万能对象类型(System.Object)输出类型 万能对象类型…

【人工智能-基础】SVM中的核函数到底是什么

文章目录 支持向量机(SVM)中的核函数详解1. 什么是核函数?核函数的作用:2. 核技巧:从低维到高维的映射3. 常见的核函数类型3.1 线性核函数3.2 多项式核函数3.3 高斯径向基函数(RBF核)4. 总结支持向量机(SVM)中的核函数详解 支持向量机(SVM,Support Vector Machine)…

万字长文解读深度学习——多模态模型BLIP2

&#x1f33a;历史文章列表&#x1f33a; 深度学习——优化算法、激活函数、归一化、正则化 深度学习——权重初始化、评估指标、梯度消失和梯度爆炸 深度学习——前向传播与反向传播、神经网络&#xff08;前馈神经网络与反馈神经网络&#xff09;、常见算法概要汇总 万字长…

大数据开发治理--大数据AI公共数据集分析

本文以分析公共数据集的数据示例&#xff0c;为您展示如何使用DataWorks进行简单数据分析工作。本教程以申请免费资源为例为您展示详细操作步骤&#xff0c;您也可以使用付费资源&#xff0c;操作类似。 教程简介 阿里云DataWorks基于多种大数据引擎&#xff0c;为数据仓库、…

ESP32-S3模组上跑通ES8388(13)

接前一篇文章&#xff1a;ESP32-S3模组上跑通ES8388&#xff08;12&#xff09; 二、利用ESP-ADF操作ES8388 2. 详细解析 上一回解析了es8388_init函数中的第6段代码&#xff0c;本回继续往下解析。为了便于理解和回顾&#xff0c;再次贴出es8388_init函数源码&#xff0c;在…

【Mac】安装Gradle

1、说明 Gradle 运行依赖 JVM&#xff0c;需要先安装JDK&#xff0c;Gradle 与 JDK的版本对应参见&#xff1a;Java Compatibility IDEA的版本也是有要求Gradle版本的&#xff0c;二者版本对应关系参见&#xff1a;Third-Party Software and Licenses 本次 Gradle 安装版本为…

根据YAML文件创建Conda环境

YAML&#xff08;全称为YAML Ain’t Markup Language&#xff09;是一种轻量级的标记语言。在Python中&#xff0c;YAML文件包含conda环境名和依赖&#xff0c;如图所示。 根据yaml文件创建Conda环境 1.切换路径 找到miniAnaconda或Anaconda&#xff0c;打开Anaconda Powersh…

【分组去重】.NET开源 ORM 框架 SqlSugar 系列

&#x1f4a5; .NET开源 ORM 框架 SqlSugar 系列 &#x1f389;&#x1f389;&#x1f389; 【开篇】.NET开源 ORM 框架 SqlSugar 系列【入门必看】.NET开源 ORM 框架 SqlSugar 系列【实体配置】.NET开源 ORM 框架 SqlSugar 系列【Db First】.NET开源 ORM 框架 SqlSugar 系列…

故障诊断 | Transformer-LSTM组合模型的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | Transformer-LSTM组合模型的故障诊断(Matlab) 源码设计 %% 初始化 clear close all clc disp(此程序务必用2023b及其以上版本的MATLAB!否则会报错!) warning off %

亚马逊云(AWS)使用root用户登录

最近在AWS新开了服务器&#xff08;EC2&#xff09;&#xff0c;用于学习&#xff0c;遇到一个问题就是默认是用ec2-user用户登录&#xff0c;也需要密钥对。 既然是学习用的服务器&#xff0c;还是想直接用root登录&#xff0c;下面开始修改&#xff1a; 操作系统是&#xff1…

Android笔记【12】脚手架Scaffold和导航Navigation

一、前言 学习课程时&#xff0c;对于自己不懂的点的记录。 对于cy老师第二节课总结。 二、内容 1、PPT介绍scaffold 2、开始代码实操 先新建一个screen包&#xff0c;写一个Homescreen函数&#xff0c;包括四个页面。 再新建一个compenent包&#xff0c;写一个displayText…

HookVip4.0.3 | 可解锁各大应用会员

HookVip是一款可以解锁会员的模块工具&#xff0c;需要搭配相应框架结合使用。这款插件工具支持多种框架如LSPosed、LSPatch、太极、应用转生等&#xff0c;并且完全免费&#xff0c;占用内存小。支持的软件包括now要想、神奇脑波、塔罗牌占卜、爱剪辑、人人视频、咪萌桌面宠物…

猎板 PCB特殊工艺:铸就电子行业核心竞争力新高度

在当今竞争激烈且技术驱动的电子制造领域&#xff0c;印制电路板&#xff08;PCB&#xff09;作为电子产品的关键基石&#xff0c;其特殊工艺的发展水平直接影响着整个行业的创新步伐与产品品质。猎板 PCB 凭借在厚铜板、孔口铺铜、HDI 板、大尺寸板以及高频高速板等特殊工艺方…

【教学类-43-25】20241203 数独3宫格的所有可能-使用模版替换(12套样式,空1格-空8格,每套510张,共6120小图)

前期做数独惨宫格的所有排列&#xff0c;共有12套样式&#xff0c;空1格-空8格&#xff0c;每套510张&#xff0c;共6120小图&#xff09; 【教学类-43-24】20241127 数独3宫格的所有可能&#xff08;12套样式&#xff0c;空1格-空8格&#xff0c;每套510张&#xff0c;共6120…

Redis+Caffeine 多级缓存数据一致性解决方案

RedisCaffeine 多级缓存数据一致性解决方案 背景 之前写过一篇文章RedisCaffeine 实现两级缓存实战&#xff0c;文章提到了两级缓存RedisCaffeine可以解决缓存雪等问题也可以提高接口的性能&#xff0c;但是可能会出现缓存一致性问题。如果数据频繁的变更&#xff0c;可能会导…