使用余弦退火(CosineAnnealing)调整学习率的代码实现

目录

一、引言

二、参数介绍

三、代码展示

四、运行结果

五、总结


一、引言

余弦退火学习率是一种优化学习率的方法,通常用于深度学习训练中,以帮助模型更好地收敛。这种学习率调整方法通过在训练周期内逐渐降低学习率,从而在训练早期使用较大的学习率以更快地接近全局最小值,然后在后期使用较小的学习率进行微调,以提高模型的稳定性和泛化能力。

具体化就如下图所示:

学习率如余弦函数一样随着训练周期而改变。 

二、参数介绍

torch.optim.lr_scheduler.CosineAnnealingLR(optimizer,T_max,eta_min=0,last_epoch=-1)

  1. optimizer:

    • 类型torch.optim.Optimizer 对象
    • 含义: 指定要进行学习率调度的优化器。调度器将会修改这个优化器中的学习率。
  2. T_max:

    • 类型: 整数
    • 含义: 一个周期(epoch)的长度,指定了学习率从最大值下降到最小值的周期长度。在余弦退火学习率调度器中,学习率在 T_max 这个周期内按照余弦函数的形状下降。通常,T_max 被设置为一个整数,代表了网络训练的总周期数。
  3. eta_min (可选参数,可选,默认值: 0):

    • 类型: 浮点数
    • 含义: 学习率的下限。在训练过程中,学习率将不会小于这个值。默认为0。
  4. last_epoch (可选参数,可选,默认值: -1):

    • 类型: 整数
    • 含义: 上一个学习率更新的训练周期(epoch)数。指定这个参数允许你在已经进行了一部分训练后恢复学习率调度,或者在某个特定的训练周期开始学习率调度。

三、代码展示

from torch.utils.data import Dataset, DataLoader
import torch
import numpy as np
from torchvision import transforms
from PIL import Image
import time

data_transforms = {
    'train':
        transforms.Compose([
            transforms.Resize([300, 300]),
            transforms.RandomRotation(45),
            transforms.CenterCrop(256),
            transforms.RandomHorizontalFlip(p=0.5),
            transforms.RandomVerticalFlip(p=0.5),
            transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),
            transforms.RandomGrayscale(p=0.1),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
        ]),
    'valid':
        transforms.Compose([
            transforms.Resize([256, 256]),
            transforms.ToTensor(),
            transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
        ]),
}


class food_dataset(Dataset):
    def __init__(self, file_path, transform=None):
        self.file_path = file_path
        self.imgs = []
        self.labels = []
        self.transform = transform
        with open(self.file_path) as f:
            samples = [x.strip().split(' ') for x in f.readlines()]
            for img_path, label in samples:
                self.imgs.append(img_path)
                self.labels.append(label)

    def __len__(self):
        return len(self.imgs)

    def __getitem__(self, item):
        image = Image.open(self.imgs[item])
        if self.transform:
            image = self.transform(image)

        label = self.labels[item]
        label = torch.from_numpy(np.array(label, dtype=np.int64))
        return image, label


training_data = food_dataset(file_path='./train.txt', transform=data_transforms['train'])
testing_data = food_dataset(file_path='./test.txt', transform=data_transforms['valid'])

train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(testing_data, batch_size=64, shuffle=True)

device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else 'cpu'
print(f"Using {device} device")
# 此行根据支持 CUDA 的 GPU 和 MPS 的可用性设置变量。
# 它使用 检查启用了 CUDA 的 GPU 是否可用。如果为 true,则设置为“cuda”。
# 否则,它将使用 检查 MPS 是否可用。如果为 true,则设置为“mps”。如果两个条件都为 false,则设置为“cpu”。

from torch import nn


# 此行从 PyTorch 库中导入(神经网络)模块,该模块提供了用于构建神经网络的各种类和函数。
class CNN(nn.Module):
    def __init__(self):
        super(CNN, self).__init__()  # 此代码定义一个名为从该类继承的类。该方法是类的构造函数,用于初始化对象。
        self.conv1 = nn.Sequential(
            nn.Conv2d(
                in_channels=3,
                out_channels=16,
                kernel_size=5,
                stride=1,
                padding=2,
            ),  # 一个 2D 卷积层,它接受具有 星火模型 个通道的输入,产生 16 个输出通道,使用内核大小为 5x5、步幅为 星火模型 和填充为 2。
            nn.ReLU(),  # 应用逐元素整流线性单元 (ReLU) 激活的激活函数。
            nn.MaxPool2d(kernel_size=2),  # 最大池化层,以 2 的步幅执行 2x2 池化操作。
        )  # 此代码定义了第一个卷积层。它是用于按顺序堆叠多个图层创建的。
        self.conv2 = nn.Sequential(
            nn.Conv2d(16, 32, 5, 1, 2),
            nn.ReLU(),
            nn.Conv2d(32, 32, 5, 1, 2),
            nn.ReLU(),
            nn.MaxPool2d(2),
        )  # 此代码定义了第二个卷积层。它遵循与输入和输出通道大小相似的结构,但具有不同的输入和输出通道大小。

        self.conv3 = nn.Sequential(
            nn.Conv2d(32, 64, 5, 1, 2),
            nn.ReLU(),
        )
        self.out = nn.Linear(64 * 64 * 64, 20)
        # 这一行定义了输出层,它是一个全连接(线性)层。它接受前面卷积层的平坦化输入(32 * 7 * 7)并生成一个大小为10的输出,与分类任务中的类别数匹配。

    def forward(self, x):
        # forward方法定义了模型的前向传播。它指定了输入x如何流经网络的不同层
        x = self.conv1(x)
        x = self.conv2(x)
        x = self.conv3(x)
        x = x.view(x.size(0), -1)
        # 在这种情况下,输入x通过conv1和conv2,然后通过展平操作(view)将输出展平。
        x = self.out(x)
        # 最后,展平后的张量被送入输出层(self.out),并返回结果。
        return x


model = CNN().to(device)
# 一个CNN类的实例被创建并赋值给model变量。.to(device)调用将模型参数移到指定的设备(例如,GPU上的“cuda”或CPU上的“cpu”)进行计算。
print(model)


def train(dataloader, model, loss_fn, optimizer):
    model.train()
    # 通过这个方法将神经网络模型设置为训练模式,以确保在训练期间所需的操作(如批量归一化和丢弃)生效。
    batch_size_num = 1
    # 初始化一个变量 batch_size_num,用于跟踪处理的批次数目。
    for X, y in dataloader:  # 开始遍历训练数据加载器中的每个批次,每个批次包括输入数据 X 和相应的标签 y。
        X, y = X.to(device), y.to(device)
        # 将输入数据 X 和标签 y 移动到指定的设备(可能是GPU或CPU),以便与模型的设备匹配。
        pred = model.forward(X)
        # 使用神经网络模型进行前向传播,得到预测结果 pred。
        loss = loss_fn(pred, y)
        # 使用指定的损失函数 loss_fn 计算模型的预测结果 pred 与真实标签 y 之间的损失。
        optimizer.zero_grad()
        # 使用优化器zero_grad()方法将模型的梯度清零,以准备进行反向传播。
        loss.backward()
        # 使用反向传播计算模型参数的梯度,以便优化器可以更新模型权重以最小化损失。
        optimizer.step()
        # 使用优化器的 step() 方法来执行一步优化,即更新模型的权重。

        loss_value = loss.item()  # 将损失值从 PyTorch 张量中提取出来,并存储在 loss_value 变量中。
        # print(f'loss:{loss_value:>7f} [number:{batch_size_num}]')
        batch_size_num += 1
        # 增加 batch_size_num 的值,以跟踪处理的批次数目。


best_acc = 0


def test(dataloader, model, loss_fn):
    global best_acc

    size = len(dataloader.dataset)
    # 计算测试数据集的总样本数。
    num_batches = len(dataloader)
    # 计算测试数据集的总样本数。
    model.eval()
    # 通过这个方法将神经网络模型设置为评估模式,以关闭一些在训练时启用的操作,例如丢弃。
    test_loss, correct = 0, 0
    # 初始化两个变量 test_loss 和 correct,分别用于累积测试损失和正确分类的样本数量。
    with torch.no_grad():
        # 使用 torch.no_grad() 上下文管理器,将其包裹的代码块中的梯度计算禁用,以减少内存使用和加速计算。
        for X, y in dataloader:
            # 开始遍历测试数据加载器中的每个批次,每个批次包括输入数据 X 和相应的标签 y。
            X, y = X.to(device), y.to(device)
            # 将测试数据移动到指定的设备,以确保与模型的设备匹配。
            pred = model.forward(X)
            # 使用神经网络模型进行前向传播,得到预测结果 pred。
            test_loss += loss_fn(pred, y).item()
            # 计算并累积测试损失,通过使用损失函数 loss_fn 计算模型的预测结果 pred 与真实标签 y 之间的损失。
            correct += (pred.argmax(1) == y).type(torch.float).sum().item()
            # 计算并累积正确分类的样本数量。这里使用了 argmax 函数来找到预测结果中的最大值对应的类别,然后检查是否与真实标签匹配,并将匹配的结果转换为浮点数。
            a = (pred.argmax(1) == y)  # dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号
            b = (pred.argmax(1) == y).type(torch.float)
    test_loss /= num_batches
    # 计算测试的平均损失,将累积的损失值除以批次数目。
    correct /= size
    # 计算准确率,将正确分类的样本数量除以总样本数,然后将其乘以 100 得到百分比形式。
    print(f'Test result:\n Accuracy:{(100 * correct)}%,Avg loss: {test_loss}')
    # 打印测试结果,包括准确率和平均损失。
    acc_s.append(correct)
    loss_s.append(test_loss)

    if correct > best_acc:
        best_acc = correct
        # print(model.state_dict().keys())
        # torch.save(model.state_dict(),'model_parameter.pt')#.pt/pth .t7
        # 保存权重(w,b)
        torch.save(model, 'best.pt')  # 保存模型


loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 使用交叉熵损失函数和Adam优化器来训练模型,并在测试集上评估模型的性能。
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=5, eta_min=0, last_epoch=-1)

epoch = 15
acc_s = []
loss_s = []
for i in range(epoch):
    print(f'Epoch{i + 1}----------------')
    a = time.time()
    train(train_dataloader, model, loss_fn, optimizer)
    test(test_dataloader, model, loss_fn)
    scheduler.step()
    b = time.time()
    print(b - a)
print('Done')

 具体代码的注释都写在了代码中。

四、运行结果

 

 

上面演示结果并没有得到最优模型,如有需要可以增大epoch来继续训练。 

五、总结

你可以根据自己的训练需求和模型选择合适的学习率和训练周期,并进行相应的调整。余弦退火学习率通常在训练深度神经网络时非常有效,但具体参数的选择可能需要一些实验和调整。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值