Datawhale X 李宏毅苹果书 AI夏令营 (进阶)Task2- 自适应学习率、分类与实践

目录

一、自适率

1、AdaGrad

2、RMSProp

3、Adam

总结

二、分类

1、分类与回归的关系

2、带有softmax的分类

3、分类损失

总结

三、实践任务:HW3(CNN)卷积神经网络-图像分类

15分钟速通

Step1:  准备算力

Step2:一键运行Notebook

​编辑

Step3:  停止实例

代码详解

1. 导入所需要的库/工具包

2. 数据准备与预处理

3. 定义模型

4. 定义损失函数和优化器等其他配置

5. 训练模型

6. 评估模型

7. 进行预测

总结 


一、自适率

在深度学习中,训练神经网络时可能会遇到损失不再下降的情况,这可能是由于模型接近临界点,但并不一定意味着已经到达局部最小值或鞍点。梯度的大小并不总是与损失下降停滞直接相关,有时即使梯度较大,损失也可能不再减少。传统的梯度下降方法在面对复杂的误差表面时可能效果不佳,需要更精细的优化策略,如自适应学习率,这种方法能够根据参数的梯度变化动态调整每个参数的学习率,以帮助模型更有效地收敛到最优解。 

自适应学习率是深度学习优化算法中的一个重要概念,它能够根据模型训练的实时反馈自动调整学习率,以提高训练效率和模型性能。自适应学习率算法包括AdaGrad、RMSProp和Adam等,它们通过记录参数更新过程中的历史梯度信息来动态调整学习率。 

1、AdaGrad

核心思想: AdaGrad是一种自适应学习率的方法,它通过累积所有过往梯度的平方来调整每个参数的学习率。这种方法使得对于经常更新的参数,学习率会逐渐减小,而对于不经常更新的参数,学习率会相对较大。

优点

  • 特别适用于稀疏数据,因为稀疏特征的梯度变化不大,AdaGrad会增加这些特征的学习率。
  • 可以应用于在线学习,因为每次更新只需要当前的梯度信息。

缺点

  • 学习率会随着时间逐渐减小,这可能导致训练过程提前结束,尤其是在参数更新频繁的情况下。
  • 对于大规模数据集,累积的梯度平方和可能会非常大,导致学习率变得非常小。

更新规则

\theta _{t}=\theta_{t-1}-\frac{\eta }{\sqrt{G_{t}+\epsilon }}g_{t}

其中,Gt​ 是累积的梯度平方和,gt 是当前梯度,ϵ 是为了防止分母为零的小常数。

2、RMSProp

核心思想: RMSProp是AdaGrad的一个改进版本,它通过使用梯度的指数加权移动平均来调整学习率,而不是累积所有过去的梯度信息。这种方法使得学习率不会随着时间显著减小。

优点

  • 学习率不会像AdaGrad那样单调递减,因此可以避免提前结束训练的问题。
  • 可以更灵活地调整学习率,适应不同的训练阶段。

缺点

  • 需要调整额外的超参数,如衰减系数ββ。
  • 对于稀疏数据,可能不如AdaGrad有效。

更新规则

s_{t}=\beta s_{t-1}+\left (1-\beta \right )g_{t}^{2}

\theta _{t}=\theta_{t-1}-\frac{\eta }{\sqrt{s_{t}+\epsilon }}g_{t}

其中,st 是平方梯度的指数加权移动平均。

3、Adam

核心思想: Adam结合了动量法和RMSProp的思想,通过计算梯度的一阶矩估计(均值)和二阶矩估计(方差),并进行偏差校正,以实现自适应学习率。

优点

  • 结合了动量法和RMSProp的优点,能够加速收敛并适应不同的参数更新需求。
  • 通过偏差校正,减少了初始时刻估计偏差的影响。

缺点

  • 需要调整更多的超参数,如β1β1​、β2β2​和ϵϵ。
  • 在某些情况下,可能会比RMSProp和SGD更难调试。

更新规则: 

m_{t}=\beta_{1} m_{t-1}+\left (1-\beta_{1} \right )g_{t}

v_{t}=\beta_{2} v_{t-1}+\left (1-\beta_{2} \right )g_{t}^{2}

\hat{m}_{t}=\frac{m_{t}}{1-\beta _{1}^{t}}

\hat{v}_{t}=\frac{v_{t}}{1-\beta _{2}^{t}}

\theta _{t}=\theta_{t-1}-\eta \frac{\hat{m}_{t}}{\sqrt{\hat{v_{t}}}+\epsilon}

其中,mt 和 vt​ 分别是梯度的一阶和二阶矩估计,mt^​ 和 vt^​ 是偏差校正后的估计。

总结

AdaGrad算法通过累积历史梯度的平方来调整每个参数的学习率,适合处理稀疏数据,但可能会导致学习率过早减小。RMSProp算法则通过计算参数的指数加权平均梯度来调整学习率,解决了AdaGrad学习率衰减过快的问题。而Adam算法结合了Momentum和RMSProp的思想,同时考虑了梯度的历史信息和平方梯度的历史信息,能够更有效地调整学习率,是目前最常用的自适应学习率优化方法之一。

在实际应用中,自适应学习率算法已经被证明在多个领域如图像分类、自然语言处理、语音识别、推荐系统等任务中取得了良好的效果。例如,使用Adam算法训练的深度卷积神经网络在图像分类任务中取得了显著的性能提升。

然而,自适应学习率算法也存在一些问题,如算法参数的选择和调整需要一定的经验和实验验证,对于某些特定问题可能无法达到最优效果。未来的研究可能会进一步探索自适应学习率算法的优化策略和扩展应用领域,以期在更多的深度学习应用场景中取得更好的性能和效果。


二、分类

深度学习中的分类问题是一种监督学习任务,其目标是将输入数据分配到两个或多个类别中

1、分类与回归的关系

回归(Regression):回归问题是预测连续值的问题。例如,根据房屋的大小、位置和其他特征来预测其价格。

分类(Classification):分类问题是预测离散标签的问题。例如,根据电子邮件的内容来预测它是否为垃圾邮件。

关系
- 回归和分类都是预测模型,但它们的目标变量类型不同。
- 分类可以看作是回归的一个特例,其中目标变量是离散的。
- 有些技术可以同时用于回归和分类,例如决策树和神经网络。
- 逻辑回归是一种特殊的线性回归,用于二分类问题,它通过sigmoid函数将连续输出转换为概率。

2、带有softmax的分类

Softmax函数:Softmax函数是一种将实数向量转换为概率分布的函数。对于一个具有K个类的分类问题,softmax将一个K维的实数向量转换为一个概率向量,每个元素代表对应类别的概率。 

 Softmax的数学表达

softmax\left ( z \right )_{i}=\frac{e^{z_{i}}}{\sum_{j-1}^{K}e^{z_{j}}}
其中,z 是输入向量, zi 是向量中的第 i 个元素, K 是类别的数量。

在深度学习中,softmax通常用于多分类问题的输出层。它将神经网络的输出(即最后一个隐藏层的激活)转换为概率分布。这使得模型能够输出每个类别的预测概率,而不仅仅是一个胜者通吃的预测。

3、分类损失

在深度学习中,分类问题通常涉及到的损失函数有均方误差(MSE)和交叉熵(Cross-Entropy)。以下是对这两种损失函数以及它们在分类问题中的应用的详细解释:

均方误差损失:MSE损失是回归问题中常用的损失函数,它计算预测值与真实值之间差异的平方的平均值。MSE损失的公式为:

MSE=\frac{1}{n}\sum_{i=1}^{n}\left (y_{i}-\hat{y}_{i} \right )^{2}
其中,yi是真实值,yi^ 是预测值,n 是样本数量。

MSE损失对大误差的惩罚更重,因为它将误差平方,这有助于模型在训练过程中减少较大的预测误差。MSE在整个定义域上连续可微,适合使用梯度下降等基于梯度的优化算法。

交叉熵损失: 交叉熵损失是分类问题中常用的损失函数,它衡量的是模型预测的概率分布与真实标签的概率分布之间的差异。对于二分类问题,交叉熵损失(BCE)的公式为:

BCE=-\left [ y\log\left (\hat{ y} \right ) +\left ( 1-y \right )log\left (1-\hat{ y} \right ) \right ]
对于多分类问题,交叉熵损失(Categorical Cross-Entropy, CE)的公式为:

CE=-\sum_{c=1}^{C}y_{c}\log \left ( \hat{y}_{c} \right )
其中, y 是真实标签的one-hot编码,y^ 是模型预测的概率分布,C 是类别的数量。

在分类问题中,交叉熵相较于均方误差更常用,这是为什么?下面从优化的角度进行解释

1. 概率解释:交叉熵损失直接针对概率分布进行优化,这与分类问题的目标(预测类别的概率)自然对应。而MSE损失没有这种概率解释,它更多用于连续值预测。

2. 数值稳定性:在使用softmax函数时,直接使用MSE损失可能会导致数值稳定性问题,因为softmax的输出值可能非常小,这会导致MSE计算时出现数值下溢。而交叉熵损失通过结合softmax和负对数似然损失(Negative Log Likelihood, NLL),在数值上更稳定。

3. 梯度更新:交叉熵损失在softmax激活后对梯度的影响更符合分类问题的需求,它能够更有效地更新模型参数,尤其是在类别不平衡的情况下。

4. 误差反馈:在分类问题中,我们更关心的是模型能否正确地将样本分到正确的类别,而不是预测值与真实值之间的具体差距。交叉熵损失能够更直接地反映出模型在分类上的表现。

总结

分类是预测离散标签的任务,与预测连续值的回归任务相对。在深度学习中,softmax函数常用于多分类问题的输出层,将网络输出转换为概率分布。分类损失,特别是交叉熵损失,用于衡量模型预测与真实标签之间的差异,并在训练过程中最小化以优化模型性能。


三、实践任务:HW3(CNN)卷积神经网络-图像分类

本次实践为《深度学习详解》的Homework3 CNN的内容。实践中利用卷积神经网络架构,通过一个较小的10种食物的图像的数据集训练一个模型完成图像分类的任务。

15分钟速通

Step1:  准备算力

Step2:一键运行Notebook

 1、获得的数据集和代码文件

启动创建的实例,等待几分钟直到启动完成,然后点击进入JupyterLab。接着,点击“Terminal”打开命令行窗口,输入以下代码并按下回车键。稍等片刻,数据集和代码文件(notebook)将会自动下载,大约需要一分钟。

git lfs install
git clone https://www.modelscope.cn/datasets/Datawhale/LeeDL-HW3-CNN.git

下载完成后的结果: 

2、一键运行代码

点击打开LeeDL-HW3-CNN文件夹,选择HW3-ImageClassification.ipynb,点击一键运行代码

正在训练:

等待约12分钟后即可获得结果。通过单元格(cell)查看模型的训练准确率。生成的`submission.csv`文件包含分类结果,可提交至Kaggle进行评估。

代码将打印出当前模型的结构,并绘制两个数据集的图像分析和分类结果分布图,以帮助更好地理解模型的表现。

模型网络架构:

绘制验证集t-SNE可视化图

Step3:  停止实例

代码详解

1. 导入所需要的库/工具包

这段代码导入了进行图像处理和深度学习任务所需的各种Python库和模块,涵盖了数据处理、神经网络构建、数据集操作、图像转换和显示进度条等功能,为后续的模型训练和评估做好准备。

# 导入必要的库
import numpy as np
import pandas as pd
import torch
import os
import torch.nn as nn
import torchvision.transforms as transforms
from PIL import Image
# “ConcatDataset” 和 “Subset” 在进行半监督学习时可能是有用的。
from torch.utils.data import ConcatDataset, DataLoader, Subset, Dataset
from torchvision.datasets import DatasetFolder, VisionDataset
# 这个是用来显示进度条的。
from tqdm.auto import tqdm
import random

此外,为了确保实验的可重复性,设置随机种子,并对CUDA进行配置以确保确定性:

# 设置随机种子以确保实验结果的可重复性
myseed = 6666

# 确保在使用CUDA时,卷积运算具有确定性,以增强实验结果的可重复性
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False

# 为numpy和pytorch设置随机种子
np.random.seed(myseed)
torch.manual_seed(myseed)

# 如果使用CUDA,为所有GPU设置随机种子
if torch.cuda.is_available():
    torch.cuda.manual_seed_all(myseed)

2. 数据准备与预处理

数据准备包括从指定路径加载图像数据,并对其进行预处理。作业中对图像的预处理操作包括调整大小和将图像转换为Tensor格式。为了增强模型的鲁棒性,可以对训练集进行数据增强。相关代码如下:

# 在测试和验证阶段,通常不需要图像增强。
# 我们所需要的只是调整PIL图像的大小并将其转换为Tensor。
test_tfm = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor(),
])

# 不过,在测试阶段使用图像增强也是有可能的。
# 你可以使用train_tfm生成多种图像,然后使用集成方法进行测试。
train_tfm = transforms.Compose([
    # 将图像调整为固定大小(高度和宽度均为128)
    transforms.Resize((128, 128)),
    # TODO:你可以在这里添加一些图像增强的操作。

    # ToTensor()应该是所有变换中的最后一个。
    transforms.ToTensor(),
])
class FoodDataset(Dataset):
    """
    用于加载食品图像数据集的类。

    该类继承自Dataset,提供了对食品图像数据集的加载和预处理功能。
    它可以自动从指定路径加载所有的jpg图像,并对这些图像应用给定的变换。
    """

    def __init__(self, path, tfm=test_tfm, files=None):
        """
        初始化FoodDataset实例。

        参数:
        - path: 图像数据所在的目录路径。
        - tfm: 应用于图像的变换方法(默认为测试变换)。
        - files: 可选参数,用于直接指定图像文件的路径列表(默认为None)。
        """
        super(FoodDataset).__init__()
        self.path = path
        # 列出目录下所有jpg文件,并按顺序排序
        self.files = sorted([os.path.join(path, x) for x in os.listdir(path) if x.endswith(".jpg")])
        if files is not None:
            self.files = files  # 如果提供了文件列表,则使用该列表
        self.transform = tfm  # 图像变换方法

    def __len__(self):
        """
        返回数据集中图像的数量。

        返回:
        - 数据集中的图像数量。
        """
        return len(self.files)

    def __getitem__(self, idx):
        """
        获取给定索引的图像及其标签。

        参数:
        - idx: 图像在数据集中的索引。

        返回:
        - im: 应用了变换后的图像。
        - label: 图像对应的标签(如果可用)。
        """
        fname = self.files[idx]
        im = Image.open(fname)
        im = self.transform(im)  # 应用图像变换

        # 尝试从文件名中提取标签
        try:
            label = int(fname.split("/")[-1].split("_")[0])
        except:
            label = -1  # 如果无法提取标签,则设置为-1(测试数据无标签)

        return im, label
# 构建训练和验证数据集
# "loader" 参数定义了torchvision如何读取数据
train_set = FoodDataset("./hw3_data/train", tfm=train_tfm)
# 创建训练数据加载器,设置批量大小、是否打乱数据顺序、是否使用多线程加载以及是否固定内存地址
train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True, num_workers=0, pin_memory=True)
# 构建验证数据集
# "loader" 参数定义了torchvision如何读取数据
valid_set = FoodDataset("./hw3_data/valid", tfm=test_tfm)
# 创建验证数据加载器,设置批量大小、是否打乱数据顺序、是否使用多线程加载以及是否固定内存地址
valid_loader = DataLoader(valid_set, batch_size=batch_size, shuffle=True, num_workers=0, pin_memory=True)

3. 定义模型

这段代码定义了一个图像分类器类(Classifier),继承自PyTorch的nn.Module。该分类器通过一系列卷积层、批归一化层、激活函数和池化层构建卷积神经网络(CNN),用于提取图像特征。随后,这些特征被输入到全连接层进行分类,最终输出11个类别的概率,用于图像分类任务。

class Classifier(nn.Module):
    """
    定义一个图像分类器类,继承自PyTorch的nn.Module。
    该分类器包含卷积层和全连接层,用于对图像进行分类。
    """
    def __init__(self):
        """
        初始化函数,构建卷积神经网络的结构。
        包含一系列的卷积层、批归一化层、激活函数和池化层。
        """
        super(Classifier, self).__init__()
        # 定义卷积神经网络的序列结构
        self.cnn = nn.Sequential(
            nn.Conv2d(3, 64, 3, 1, 1),  # 输入通道3,输出通道64,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(64),        # 批归一化,作用于64个通道
            nn.ReLU(),                 # ReLU激活函数
            nn.MaxPool2d(2, 2, 0),      # 最大池化,池化窗口大小2,步长2,填充0
            
            nn.Conv2d(64, 128, 3, 1, 1), # 输入通道64,输出通道128,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(128),        # 批归一化,作用于128个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),      # 最大池化,池化窗口大小2,步长2,填充0
            
            nn.Conv2d(128, 256, 3, 1, 1), # 输入通道128,输出通道256,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(256),        # 批归一化,作用于256个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),      # 最大池化,池化窗口大小2,步长2,填充0
            
            nn.Conv2d(256, 512, 3, 1, 1), # 输入通道256,输出通道512,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(512),        # 批归一化,作用于512个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),       # 最大池化,池化窗口大小2,步长2,填充0
            
            nn.Conv2d(512, 512, 3, 1, 1), # 输入通道512,输出通道512,卷积核大小3,步长1,填充1
            nn.BatchNorm2d(512),        # 批归一化,作用于512个通道
            nn.ReLU(),
            nn.MaxPool2d(2, 2, 0),       # 最大池化,池化窗口大小2,步长2,填充0
        )
        # 定义全连接神经网络的序列结构
        self.fc = nn.Sequential(
            nn.Linear(512*4*4, 1024),    # 输入大小512*4*4,输出大小1024
            nn.ReLU(),
            nn.Linear(1024, 512),        # 输入大小1024,输出大小512
            nn.ReLU(),
            nn.Linear(512, 11)           # 输入大小512,输出大小11,最终输出11个类别的概率
        )

    def forward(self, x):
        """
        前向传播函数,对输入进行处理。
        
        参数:
        x -- 输入的图像数据,形状为(batch_size, 3, 128, 128)
        
        返回:
        输出的分类结果,形状为(batch_size, 11)
        """
        out = self.cnn(x)               # 通过卷积神经网络处理输入
        out = out.view(out.size()[0], -1)  # 展平输出,以适配全连接层的输入要求
        return self.fc(out)             # 通过全连接神经网络得到最终输出

4. 定义损失函数和优化器等其他配置

这段代码实现了图像分类模型的初始化和训练配置,目的是准备好训练环境和参数。它选择合适的设备(GPU或CPU),设置模型、批量大小、训练轮数、提前停止策略,定义了损失函数和优化器,为后续的模型训练奠定了基础。

# 根据GPU是否可用选择设备类型
device = "cuda" if torch.cuda.is_available() else "cpu"

# 初始化模型,并将其放置在指定的设备上
model = Classifier().to(device)

# 定义批量大小
batch_size = 64

# 定义训练轮数
n_epochs = 8

# 如果在'patience'轮中没有改进,则提前停止
patience = 5

# 对于分类任务,我们使用交叉熵作为性能衡量标准
criterion = nn.CrossEntropyLoss()

# 初始化优化器,您可以自行调整一些超参数,如学习率
optimizer = torch.optim.Adam(model.parameters(), lr=0.0003, weight_decay=1e-5)

5. 训练模型

这段代码实现了一个图像分类模型的训练和验证循环,目的是通过多轮训练(epochs)逐步优化模型的参数,以提高其在验证集上的性能,并保存效果最好的模型。训练阶段通过前向传播、计算损失、反向传播和参数更新来优化模型,验证阶段评估模型在未见过的数据上的表现。如果验证集的准确率超过了之前的最好成绩,保存当前模型,并在连续多轮验证性能未提升时提前停止训练。

# 初始化追踪器,这些不是参数,不应该被更改
stale = 0
best_acc = 0

for epoch in range(n_epochs):
    # ---------- 训练阶段 ----------
    # 确保模型处于训练模式
    model.train()

    # 这些用于记录训练过程中的信息
    train_loss = []
    train_accs = []

    for batch in tqdm(train_loader):
        # 每个批次包含图像数据及其对应的标签
        imgs, labels = batch
        # imgs = imgs.half()
        # print(imgs.shape,labels.shape)

        # 前向传播数据。(确保数据和模型位于同一设备上)
        logits = model(imgs.to(device))

        # 计算交叉熵损失。
        # 在计算交叉熵之前不需要应用softmax,因为它会自动完成。
        loss = criterion(logits, labels.to(device))

        # 清除上一步中参数中存储的梯度
        optimizer.zero_grad()

        # 计算参数的梯度
        loss.backward()

        # 为了稳定训练,限制梯度范数
        grad_norm = nn.utils.clip_grad_norm_(model.parameters(), max_norm=10)

        # 使用计算出的梯度更新参数
        optimizer.step()

        # 计算当前批次的准确率
        acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()

        # 记录损失和准确率
        train_loss.append(loss.item())
        train_accs.append(acc)

    train_loss = sum(train_loss) / len(train_loss)
    train_acc = sum(train_accs) / len(train_accs)

    # 打印信息
    print(f"[ 训练 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {train_loss:.5f}, acc = {train_acc:.5f}")

6. 评估模型

训练完成后,需要在测试集上评估模型的性能。通过计算准确率来衡量模型在测试集上的表现。

# ---------- 验证阶段 ----------
    # 确保模型处于评估模式,以便某些模块如dropout被禁用,模型能够正常工作
    model.eval()

    # 这些用于记录验证过程中的信息
    valid_loss = []
    valid_accs = []

    # 按批次迭代验证集
    for batch in tqdm(valid_loader):
        # 每个批次包含图像数据及其对应的标签
        imgs, labels = batch
        # imgs = imgs.half()

        # 我们在验证阶段不需要梯度。
        # 使用 torch.no_grad() 加速前向传播过程。
        with torch.no_grad():
            logits = model(imgs.to(device))

        # 我们仍然可以计算损失(但不计算梯度)。
        loss = criterion(logits, labels.to(device))

        # 计算当前批次的准确率
        acc = (logits.argmax(dim=-1) == labels.to(device)).float().mean()

        # 记录损失和准确率
        valid_loss.append(loss.item())
        valid_accs.append(acc)
        # break

    # 整个验证集的平均损失和准确率是所记录值的平均
    valid_loss = sum(valid_loss) / len(valid_loss)
    valid_acc = sum(valid_accs) / len(valid_accs)

    # 打印信息
    print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")

    # 更新日志
    if valid_acc > best_acc:
        with open(f"./{_exp_name}_log.txt", "a"):
            print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f} -> 最佳")
    else:
        with open(f"./{_exp_name}_log.txt", "a"):
            print(f"[ 验证 | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")
# 李老师的课程原文件里面确实缺少write,如果想要log文件里面有内容,可以按照下面的参考,此部分不是重点
#if valid_acc > best_acc:
#    with open(f"./{_exp_name}_log.txt", "a") as log_file:
#        log_file.write(f"[ Valid | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f} -> best\n")
#    print(f"[ Valid | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f} -> best")
#else:
#    with open(f"./{_exp_name}_log.txt", "a") as log_file:
#        log_file.write(f"[ Valid | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}\n")
#    print(f"[ Valid | {epoch + 1:03d}/{n_epochs:03d} ] loss = {valid_loss:.5f}, acc = {valid_acc:.5f}")




    # 保存模型
    if valid_acc > best_acc:
        print(f"在第 {epoch} 轮找到最佳模型,正在保存模型")
        torch.save(model.state_dict(), f"{_exp_name}_best.ckpt")  # 只保存最佳模型以防止输出内存超出错误
        best_acc = valid_acc
        stale = 0
    else:
        stale += 1
        if stale > patience:
            print(f"连续 {patience} 轮没有改进,提前停止")
            break

7. 进行预测

最后的代码构建一个测试数据集和数据加载器,以便高效地读取数据。实例化并加载预训练的分类器模型,并将其设置为评估模式。在不计算梯度的情况下,遍历测试数据,使用模型进行预测,并将预测标签存储在列表中。将预测结果与测试集的ID生成一个DataFrame,并将其保存为submission.csv文件。

# 构建测试数据集
# "loader"参数指定了torchvision如何读取数据
test_set = FoodDataset("./hw3_data/test", tfm=test_tfm)
# 创建测试数据加载器,批量大小为batch_size,不打乱数据顺序,不使用多线程,启用pin_memory以提高数据加载效率
test_loader = DataLoader(test_set, batch_size=batch_size, shuffle=False, num_workers=0, pin_memory=True)

# 实例化分类器模型,并将其转移到指定的设备上
model_best = Classifier().to(device)

# 加载模型的最优状态字典
model_best.load_state_dict(torch.load(f"{_exp_name}_best.ckpt"))

# 将模型设置为评估模式
model_best.eval()

# 初始化一个空列表,用于存储所有预测标签
prediction = []

# 使用torch.no_grad()上下文管理器,禁用梯度计算
with torch.no_grad():
    # 遍历测试数据加载器
    for data, _ in tqdm(test_loader):
        # 将数据转移到指定设备上,并获得模型的预测结果
        test_pred = model_best(data.to(device))
        # 选择具有最高分数的类别作为预测标签
        test_label = np.argmax(test_pred.cpu().data.numpy(), axis=1)
        # 将预测标签添加到结果列表中
        prediction += test_label.squeeze().tolist()

# 创建测试csv文件
def pad4(i):
    """
    将输入数字i转换为长度为4的字符串,如果长度不足4,则在前面补0。
    :param i: 需要转换的数字
    :return: 补0后的字符串
    """
    return "0" * (4 - len(str(i))) + str(i)

# 创建一个空的DataFrame对象
df = pd.DataFrame()
# 使用列表推导式生成Id列,列表长度等于测试集的长度
df["Id"] = [pad4(i) for i in range(len(test_set))]
# 将预测结果赋值给Category列
df["Category"] = prediction
# 将DataFrame对象保存为submission.csv文件,不保存索引
df.to_csv("submission.csv", index=False)        

总结 

本次实验的目标是利用卷积神经网络(CNN)架构来训练一个模型,以识别一个包含10种不同食物类别的小型图像数据集。实验过程中,首先对数据集进行了预处理,包括图像的归一化和数据增强,以提高模型的泛化能力。随后,设计了一个简单的CNN模型,该模型包括多个卷积层、激活层、池化层和全连接层。最终,模型在测试集上展现了良好的泛化能力,准确率令人较为满意。通过本次实验验证了CNN在图像分类任务中的有效性。尽管数据集规模较小,但通过合理的网络设计和训练策略,能够构建一个性能良好的分类器。未来的工作可以包括使用更大的数据集、尝试不同的网络架构、以及进一步的调参来提高模型的性能。

  • 9
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

YHa_a

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值