【Datawhale X 李宏毅苹果书 AI夏令营】Task2笔记

第三章:深度学习基础

本章前部分的内容见:【Datawhale X 李宏毅苹果书 AI夏令营】Task1笔记-CSDN博客

3.6 分类

分类与回归的关系

假设三个类本身没有特定的关系,类 1 是 1,类 2 是 2 类 3 是 3。这种情况需要引入独热(one-hot)向量来表示类。

通常,我们使用逻辑回归(而不是线性回归)处理分类问题。

激活函数:softmax

按照上述的设定,分类实际过程是:输入 x,乘上 W,加上 b,通过激活函数 σ,乘上W′,再加上 b′ ,得到向量 yˆ。但实际做分类的时候,往往会把 yˆ 通过 softmax 函数得到 y′,才去计算 y′ 跟 yˆ 之间的距离。

softmax是一种激活函数,激活函数用于对输入信号进行非线性变换。常见的激活函数还有Sigmoid,ReLU等等。

激活函数必须满足以下条件:

  • 可微,优化方法是基于梯度。

  • 单调,保证单层网络是凸函数。

  • 输出值范围,有限则梯度优化更稳定,无限则训练更高效(学习率需要更小)。

softmax常用于多分类问题,计算公式如下:

使用softmax,一言以蔽之是为了归一化。另外,由于使用了 e 的幂函数,softmax 可以使正样本(正数)的结果趋近于 1,使负样本(负数)的结果趋近于 0;且样本的绝对值越大,两极化越明显。

参考:

https://blog.csdn.net/qq_41750911/article/details/124078768

https://blog.csdn.net/qq_43799400/article/details/131202148

分类损失:交叉熵

在分类问题下,使用的是交叉熵 cross entropy ,而非线性回归的均方误差(MSE)。最小化交叉熵其实就是最大化似然(maximize likelihood)。

为什么使用交叉熵作为损失函数L(f)而不是均方误差?可看下图的推导:假设训练集中 1 表示类 1,0 表示类 2,然后计算L(f)的微分(step3)。假设第n个数据是类 1,预测出的数据也是类 1,这意味着微分为0,是合理的。然而假如我预测出来的是类 2,但计算出微分仍然是0,这是不对的,因为离我的目标(希望output是1而不是0)还很遥远。

换句话说,当距离目标很近或者很远的时候,逻辑回归的均方误差会导致步长很小,但是交叉熵可以让步长在举例很远的时候仍然很大。

参考:

李宏毅机器学习【2017】https://www.bilibili.com/video/BV1SA411n7ou/?p=10

附注:分类与回归的对比

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

通过利用卷积神经网络架构,通过一个较小的10种食物的图像的数据集训练一个模型完成图像分类的任务。

关键流程的代码说明

图像预处理/变换

调整PIL图像大小并转换为Tensor

神经网络需要的输入数据类型一般是 FloatTensor 类型,且需要进行标准化,这个过程常常使用 transforms.ToTensor() 方法来实现。

python 复制代码
test_tfm = transforms.Compose([ transforms.Resize((128, 128)), transforms.ToTensor(), ])

数据集加载

训练集、验证集和数据加载器
python 复制代码
# 构建训练数据集 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) 
# 构建验证数据集 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)

模型定义

该分类器通过一系列卷积层(Conv2d)、批归一化层(BatchNorm2d)、激活函数(ReLU)和池化层(MaxPool2d)构建卷积神经网络,用于提取图像特征。随后,这些特征被输入到全连接层进行分类,最终输出11个类别的概率,用于图像分类任务。

构建卷积神经网络的结构:卷积层、批归一化层、激活函数和池化层
python 复制代码
    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个类别的概率
        )
前向传播
python 复制代码
    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)             # 通过全连接神经网络得到最终输出

定义损失函数

分类任务使用交叉熵作为性能衡量标准。

python 复制代码
criterion = nn.CrossEntropyLoss()

训练模型

通过多轮训练(epochs)逐步优化模型的参数,以提高其在验证集上的性能,并保存效果最好的模型。

注意optimizer.zero_grad()是每次都需要做的操作。

python 复制代码
# 初始化追踪器,这些不是参数,不应该被更改
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)

评估模型

在验证集上进行。

python 复制代码
# ---------- 验证阶段 ----------
    # 确保模型处于评估模式,以便某些模块如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)


    # 保存模型
    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

预测

使用跑出来的模型在测试集上预测。

相关推荐
NAGNIP3 小时前
一文搞懂深度学习中的通用逼近定理!
人工智能·算法·面试
冬奇Lab4 小时前
一天一个开源项目(第36篇):EverMemOS - 跨 LLM 与平台的长时记忆 OS,让 Agent 会记忆更会推理
人工智能·开源·资讯
冬奇Lab4 小时前
OpenClaw 源码深度解析(一):Gateway——为什么需要一个"中枢"
人工智能·开源·源码阅读
AngelPP8 小时前
OpenClaw 架构深度解析:如何把 AI 助手搬到你的个人设备上
人工智能
宅小年8 小时前
Claude Code 换成了Kimi K2.5后,我再也回不去了
人工智能·ai编程·claude
九狼8 小时前
Flutter URL Scheme 跨平台跳转
人工智能·flutter·github
ZFSS9 小时前
Kimi Chat Completion API 申请及使用
前端·人工智能
天翼云开发者社区10 小时前
春节复工福利就位!天翼云息壤2500万Tokens免费送,全品类大模型一键畅玩!
人工智能·算力服务·息壤
知识浅谈10 小时前
教你如何用 Gemini 将课本图片一键转为精美 PPT
人工智能
Ray Liang10 小时前
被低估的量化版模型,小身材也能干大事
人工智能·ai·ai助手·mindx