【无标题】

1.自适应学习率
        自适应学习率(Adaptive Learning Rate):根据参数的梯度自动调整每个参数的学习率,使得不同参数可以有不同的学习速度。

1.1AdaGrad
        AdaGrad:通过计算历史梯度的调整学习率,适用于处理不同参数的不同学习需求。平方和来

根据其历史梯度        AdaGrad的核心思想是对于每个参数,的平方和来调整学习率,从而使得稀疏特征的学习率较高,而频繁更新的特征学习率较低。实在不懂csdn编辑文本怎么写入公式,在此采用图片方法呈现其步骤。

图1.1.1 AdaGrad公式步骤图

        AdaGrad 的优势在于它能自动调整学习率,不同的特征具有不同的学习率,但它也有一个局限性,即学习率会随着训练进行逐渐减小,可能导致收敛过慢。

图 1.1.2  自动调整学习率示例图(摘自参考书)
1.2RMSProp
        RMSProp(Root Mean Squared propagation):另一种自适应学习率方法,它使用指数衰减的方式来计算梯度的平方和,允许模型动态调整学习率。

图1.2.1 RMSProp公式步骤图

1.3Adam
        Adam:结合了动量和RMSProp的思想,使用一阶矩估计(即梯度的指数加权平均)和二阶矩估计(即梯度平方的指数加权平均)来更新参数。

        但是PyTorch中已经调好了参数,即使超参数需要人为决定,但是已经调的挺好基本不需要修改。

1.4学习率调度
        学习率调度(Learning Rate Scheduling):学习率调度根据训练进度调整学习率,常见的策略包括学习率衰减和预热(initialization)。学习率衰减使学习率随时间逐渐减小,而预热则使学习率先增大后减小。

图 1.4.1 AdaGrad 优化的问题 (摘自参考书)
        如图所见,通过使用AdGrad优化后,B→C段顺利又迈出了步子,但是在末尾阶段开始了抽搐,这是由于A→B段纵向的梯度改变累计导致的,但又因为其本身特性,会自动进行调整,又回到正轨。解决这个问题就可以引入学习率调度概念,学习率退火,即让η随着参数更新不断减小,就可以丝滑地走到C点。还有一种方法是预热,让学习率先变大后变小,基本思路是改变超参数。

图1.4.2   学习率衰减的优化效果(摘自参考书)
2.分类
        分类与回归的关系:分类问题可以视为一种特殊的回归问题,其中输出是一个独热向量,表示所属的类别。

        独热向量(One-Hot Encoding):一种表示类别的方法,每个类别用一个长度等于类别数量的向量表示,其中正确类别的位置为1,其余为0。

        假设三各类没有特定关系,可使用独热向量表示类。如果用独热向量计算距离,类两两之间的距离应一致。

        如y为一个含有三元素的向量,输出也应是三个元素,而将他们一一匹配的方式可以通过乘上不同权重,加上偏置,再乘上另一个权重,加上另一个偏置,输入一个特征向量后得到。

图 2.1  网络多个输出示例图(摘自参考书)
2.1Softmax函数
        Softmax函数:Softmax函数用于将模型输出的原始分数转换为概率分布,使得可以计算类别的相对概率。

        Softmax工作原理如下:首先,Softmax函数接收一组原始分数,这些分数可以是模型的输出值,通常没有进行标准化。然后,对每个原始分数应用指数函数(e^x),这会将负数转化为正数,并放大差异。公式为:exp(x_i),其中 x_i 是第 i 个分数。接着,将每个指数化后的分数除以所有分数的总和,以确保所有输出的概率总和为1。公式为:P(y_i) = exp(x_i) / Σ(exp(x_j)),其中 Σ(exp(x_j)) 是所有指数化分数的总和。最后输出,结果是一个概率分布,每个分数的输出值都在0和1之间,且所有输出值加起来等于1。

        特别的,当只有两个类的时候,sigmoid 和 softmax 是等价的。

        分类损失:交叉熵损失(Cross-Entropy Loss)是分类问题中最常用的损失函数,它衡量模型预测的概率分布与真实标签的独热向量之间的差异。

        改变损失函数可以改变优化难度。

3.*批量归一化(Batch Normalization)
        批量归一化旨在“平滑”误差表面,使得优化过程更加稳定和高效。对每个小批量数据的每个特征进行归一化处理,使其具有固定的均值和方差。批量归一化有助于加速训练过程,提高模型的泛化能力,并减少对初始化的敏感性。在网络中引入归一化层,对激活函数的输出进行归一化处理,然后通过可学习的参数进行缩放和平移。

4.实践——HW3(CNN)卷积神经网络-图像分类
        按照步骤一步步实现即可,在此,一键运行notebook的步骤不进行赘述,只做基于datawhale已注释代码的进一步分析。

1.导入库
        numpy和pandas用来数据处理和分析,torch和torch.nn用于深度学习,torchvision.transforms是图像预处理和数据增强的转换工具,PIL.Image用于图像处理,torch.utils.data提供数据加载和处理的工具,包括括 ConcatDataset, DataLoader, Subset,

 Dataset;torchvision.datasets提供一些标准的数据集类和工具,例如 DatasetFolder 和

 VisionDataset;tqdm.auto用于显示进度条。

# 导入必要的库
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,在初始化函数 __init__ 中构建了模型的网络结构。网络包含多个卷积层、批归一化层、ReLU 激活函数和最大池化层,以提取图像特征。随后,特征图经过展平处理并通过全连接层进行分类,最终输出 11 个类别的概率。在 forward 函数中,输入图像通过卷积层提取特征,然后展平为一维向量,并通过全连接层进行分类,输出最终的预测结果。这段代码定义了一个用于图像分类的卷积神经网络(CNN)模型。

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. 定义损失函数和优化器等其他配置
        设置 PyTorch 模型的训练环境和参数:

        设备选择: 根据 GPU 是否可用,设置计算设备为 cuda 或 cpu。
        模型初始化: 创建 Classifier 类的实例,并将模型移到指定设备上。
        量大小: 定义每个训练批次的样本数为 64。
        训练轮数: 设置训练的总轮数为 8。
        提前停止: 如果在 patience(5)轮内没有性能提升,则提前停止训练,以防止过拟合。
        损失函数: 使用交叉熵损失(nn.CrossEntropyLoss)作为分类任务的性能衡量标准。
        优化器: 使用 Adam 优化器,设置学习率为 0.0003 和权重衰减为 1e-5,用于调整模型参数。

# 根据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. 训练模型
        将在每个 epoch 结束时提供训练过程中的平均损失和准确率,以帮助监控模型的训练进展。

# 初始化追踪器,这些不是参数,不应该被更改
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}")
 
    # 保存模型
    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.进行预测
        并保存结果为submission.tcsv文件。

# 构建测试数据集
# "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)        
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值