【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种食物的图像的数据集训练一个模型完成图像分类的任务。

关键流程的代码说明

  1. 图像预处理/变换

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

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

test_tfm = transforms.Compose([ transforms.Resize((128, 128)), transforms.ToTensor(), ])
  1. 数据集加载

训练集、验证集和数据加载器
# 构建训练数据集 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)
  1. 模型定义

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

构建卷积神经网络的结构:卷积层、批归一化层、激活函数和池化层
    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)             # 通过全连接神经网络得到最终输出
  1. 定义损失函数

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

criterion = nn.CrossEntropyLoss()
  1. 训练模型

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

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

# 初始化追踪器,这些不是参数,不应该被更改
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)
  1. 评估模型

在验证集上进行。

# ---------- 验证阶段 ----------
    # 确保模型处于评估模式,以便某些模块如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
  1. 预测

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值