余弦退火调整学习率策略 CosineAnnealingLR

发布时间:2023年12月23日

学习率直接控制参数更新的速度,并不是一成不变的,过大过小都会影响训练效果,所以在训练过程中需要对学习率进行调整

一般地,在训练的前期需要较大的学习率,后期学习率较小

常见的方法,如:StepLR在设置好的迭代次数处进行学习率的衰减,LinearLR使得学习率线性地衰减

下面我将介绍一种很常见的学习率调整策略-----余弦退火策略 CosineAnnealingLR

它的思路不同于以往的学习率调整策略,? ?作者认为神经网络在刚开始训练时,对模型参数进行初始化的结果是非常不稳定的,故在刚开始时需要选用小的学习率。但是小的学习率会让训练过程非常缓慢,因此这里会采用?以较低学习率逐渐增大至较高学习率的方式实现网络训练的 “ 热身 ” 阶段,称为 warm up stage?

CosineAnnealingLR?

使用梯度下降算法来优化目标函数的时候,当越来越接近Loss值的全局最小值时,学习率应该变得更小来使得模型尽可能接近这一点, 这个时候过大的学习率会使得权重的梯度一直来回震荡,很难使训练的损失值达到全局最低, 所以学习率不能一直都较大,后期需要下降,可以通过余弦函数来降低学习率

余弦退火(cosine annealing)通过余弦函数来降低学习率,余弦函数中随着 x 的增加余弦值首先缓慢下降,然后加速下降,再次缓慢下降

上述过程就称为 余弦退火
?

代码实现

import torch.optim as optim  # 定义了 torch.optim 模块

# 定义了类 CosineAnnealingWarmupRestarts ,该类继承自 optim.lr_scheduler._LRScheduler
class CosineAnnealingWarmupRestarts(optim.lr_scheduler._LRScheduler):
    """
        optimizer (Optimizer): Wrapped optimizer.
        first_cycle_steps (int): First cycle step size.
        cycle_mult(float): Cycle steps magnification. Default: -1.
        max_lr(float): First cycle's max learning rate. Default: 0.1.
        min_lr(float): Min learning rate. Default: 0.001.
        warmup_steps(int): Linear warmup step size. Default: 0.
        gamma(float): Decrease rate of max learning rate by cycle. Default: 1.
        last_epoch (int): The index of last epoch. Default: -1.
    """
    # 构造函数,接受一些参数用于初始化对象
    def __init__(self,
                 optimizer: torch.optim.Optimizer,  # 被包装的优化器对象
                 first_cycle_steps: int,            # 第一个周期的步数
                 cycle_mult: float = 1.,            # 周期步数的倍数,默认为1
                 max_lr: float = 0.1,               # 第一个周期的最大学习率,默认为0.1
                 min_lr: float = 0.001,             # 最小学习率,默认为 0.001
                 warmup_steps: int = 0,             # 线性预热步数,默认为0
                 gamma: float = 1.,                # 按周期递减的最大学习率的衰减率,默认为1
                 last_epoch: int = -1              # 上一个周期的索引,默认为-1
                 ):
        # 用于确保预热步数 warmup_steps 小于第一个周期的步数 first_cycle_steps
        assert warmup_steps < first_cycle_steps

        self.first_cycle_steps = first_cycle_steps  # first cycle step size
        self.cycle_mult = cycle_mult    # cycle steps magnification
        self.base_max_lr = max_lr   # first max learning rate
        self.max_lr = max_lr    # max learning rate in the current cycle
        self.min_lr = min_lr    # min learning rate
        self.warmup_steps = warmup_steps    # warmup step size
        self.gamma = gamma  # decrease rate of max learning rate by cycle

        self.cur_cycle_steps = first_cycle_steps    # first cycle step size
        self.cycle = 0  # cycle count
        self.step_in_cycle = last_epoch     # step size of the current cycle
        # 将构造函数的参数值赋给对象属性,用于在后续的方法中使用和更新        
        
        # 调用父类的构造函数,初始化父类的属性
        super(CosineAnnealingWarmupRestarts, self).__init__(optimizer, last_epoch)

        # set learning rate min_lr 
        self.init_lr()  # 调用了自定义的init__lr 方法,用于初始化学习率

    # 自定义函数 init_lr :将最小学习率 min_lr 赋给优化器对象中的每个参数组的学习率,并将最小学习率添加到 base_lrs 列表中
    def init_lr(self):
        self.base_lrs = []
        for param_group in self.optimizer.param_groups:
            param_group['lr'] = self.min_lr
            self.base_lrs.append(self.min_lr)

    # get_lr()方法的定义:它根据当前步数和预定义的参数计算学习率
    def get_lr(self):
        if self.step_in_cycle == -1: # 如果当前步数为-1,则返回初始学习率
            return self.base_lrs
        elif self.step_in_cycle < self.warmup_steps: # 如果当前步数小于预热步数,则根据线性预热计算学习率
            return [(self.max_lr - base_lr)*self.step_in_cycle / self.warmup_steps + base_lr for base_lr in self.base_lrs]
        else:  # 否则根据余弦退火计算学习率
            return [base_lr + (self.max_lr - base_lr) \
                    * (1 + math.cos(math.pi * (self.step_in_cycle-self.warmup_steps) \
                                    / (self.cur_cycle_steps - self.warmup_steps))) / 2
                    for base_lr in self.base_lrs]
    # 这是step()方法的定义:根据给定的周期索引epoch更新当前步数和周期计数
    def step(self, epoch=None):
        if epoch is None:
            epoch = self.last_epoch + 1
            self.step_in_cycle = self.step_in_cycle + 1
            if self.step_in_cycle >= self.cur_cycle_steps:
                self.cycle += 1
                self.step_in_cycle = self.step_in_cycle - self.cur_cycle_steps
                self.cur_cycle_steps = int((self.cur_cycle_steps - self.warmup_steps) * self.cycle_mult) + self.warmup_steps
        else:
            if epoch >= self.first_cycle_steps:
                if self.cycle_mult == 1.:
                    self.step_in_cycle = epoch % self.first_cycle_steps
                    self.cycle = epoch // self.first_cycle_steps
                else:
                    n = int(math.log((epoch / self.first_cycle_steps * (self.cycle_mult - 1) + 1), self.cycle_mult))
                    self.cycle = n
                    self.step_in_cycle = epoch - int(self.first_cycle_steps * (self.cycle_mult ** n - 1) / (self.cycle_mult - 1))
                    self.cur_cycle_steps = self.first_cycle_steps * self.cycle_mult ** (n)
            else:
                self.cur_cycle_steps = self.first_cycle_steps
                self.step_in_cycle = epoch

        # 根据当前周期计数和衰减率更新最大学习率,并将学习率应用到优化器对象的每个参数组中
        self.max_lr = self.base_max_lr * (self.gamma**self.cycle)
        self.last_epoch = math.floor(epoch)
        for param_group, lr in zip(self.optimizer.param_groups, self.get_lr()):
            param_group['lr'] = lr

文章来源:https://blog.csdn.net/Kelly_Ai_Bai/article/details/135172684
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。