Learning Rate Scheduler(二)

书接上回,上篇文章介绍了 7 种学习率的调整策略,PyTorch 1.11 版本中共有 14 种,本篇文章接着介绍剩下的 7 种学习率调整策略。

  1. lr_scheduler.CosineAnnealingLR
  2. lr_scheduler.ChainedScheduler
  3. lr_scheduler.SequentialLR
  4. lr_scheduler.ReduceLROnPlateau
  5. lr_scheduler.CyclicLR
  6. lr_scheduler.OneCycleLR
  7. lr_scheduler.CosineAnnealingWarmRestarts

1. CosineAnnealingLR

CosineAnnealingLR 叫做余弦退火。

CosineAnnealingLR(optimizer, T_max, eta_min=0, last_epoch=- 1, verbose=False)

T_max 表示从优化器设置的学习率到 eta_min 学习率需要多少个 step。学习率达到 eta_min 时,会重新逐渐达到最高学习率,循环往复。这样的学习率变化,在某些情况下有助于模型跳出局部最优解。这是因为学习率是会变化的,当达到局部最优时,较大的学习率有助于跳出。

我们接下来设置最大学习率(优化器设置的)0.1,最小学习率为 0.01,T_max 为 20,即:20 个 step 从最大学习率到最小学习率,或者从最小学习率到最大学习率。

import torch
from torch.optim.lr_scheduler import CosineAnnealingLR
import torch.optim as optim
import torch.nn as nn
import torch
import matplotlib.pyplot as plt


if __name__ == '__main__':

    # 模型参数
    parameter = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
    # 优化器
    optimizer = optim.SGD(parameter, lr=0.1)
    # 调度器
    scheduler = CosineAnnealingLR(optimizer=optimizer, T_max=20, eta_min=0.01)

    learning_rates = [0.1]
    for _ in range(100):
        # 先优化器更新
        optimizer.step()
        # 再调度器更新
        scheduler.step()
        # 存储学习率
        print('%.5f' % scheduler.get_last_lr()[0])
        learning_rates.append(scheduler.get_last_lr()[0])

    # 绘制学习率变化
    plt.plot(range(101), learning_rates)
    plt.title('CosineAnnealingLR')
    plt.grid()
    plt.show()

2. ChainedScheduler

ChainedScheduler 的参数为多个 scheduler 组成的列表,可用于组合多个 Scheduler 以实现较为复杂的学习率调整策略。

ChainedScheduler(schedulers)

我们接下来将 StepLR 和 ExponentialLR 组合起来,作为学习率的调整策略。

import torch
from torch.optim.lr_scheduler import ChainedScheduler
from torch.optim.lr_scheduler import StepLR
from torch.optim.lr_scheduler import ExponentialLR
import torch.optim as optim
import torch.nn as nn
import torch
import matplotlib.pyplot as plt


if __name__ == '__main__':

    # 模型参数
    model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
    # 优化器
    optimizer = optim.SGD(model_parameters, lr=0.1)
    # 调度器
    scheduler1 = StepLR(optimizer=optimizer, step_size=10, gamma=0.1)
    scheduler2 = ExponentialLR(optimizer=optimizer, gamma=0.9)
    scheduler = ChainedScheduler([scheduler1, scheduler2])

    learning_rates = [0.1]
    for _ in range(100):
        # 先优化器更新
        optimizer.step()
        # 再调度器更新
        scheduler.step()
        # 存储学习率
        print('%.5f' % scheduler.get_last_lr()[0])
        learning_rates.append(scheduler.get_last_lr()[0])

    # 绘制学习率变化
    plt.plot(range(101), learning_rates)
    plt.title('ChainedScheduler')
    plt.grid()
    plt.show()

3. SequentialLR

ChainedScheduler 管理的多个学习率调节器在每一 step 都会被执行。所以,可以理解这些 schedulers 是并行执行的。SequentialLR 则是顺序执行设置的多个 scheduler,即:第一个执行多少 epoch 之后,第二个再开始执行。

这里需要注意的是,第一个 scheduler 执行完毕之后,学习率会重新回到优化器设置的初始学习率再使用第二个 scheduler 进行衰减。最后一个 scheduler 会持续到训练结束。

import torch
from torch.optim.lr_scheduler import SequentialLR
from torch.optim.lr_scheduler import StepLR
from torch.optim.lr_scheduler import ExponentialLR
import torch.optim as optim
import torch.nn as nn
import torch
import matplotlib.pyplot as plt


if __name__ == '__main__':

    # 模型参数
    model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
    # 优化器
    optimizer = optim.SGD(model_parameters, lr=0.1)
    # 调度器
    scheduler1 = ExponentialLR(optimizer=optimizer, gamma=0.9)
    scheduler2 = StepLR(optimizer=optimizer, step_size=10, gamma=0.1)
    scheduler = SequentialLR(optimizer, schedulers=[scheduler1, scheduler2], milestones=[20])

    learning_rates = [0.1]
    for _ in range(100):
        # 先优化器更新
        optimizer.step()
        # 再调度器更新
        scheduler.step()
        # 存储学习率
        print('%.5f' % scheduler.get_last_lr()[0])
        learning_rates.append(scheduler.get_last_lr()[0])

    # 绘制学习率变化
    plt.plot(range(101), learning_rates)
    plt.title('SequentialLR')
    plt.grid()
    plt.show()

4. ReduceLROnPlateau

训练时,有时模型会停止,即:损失一直降不下去,通常此时我们会降低学习率,以更小的学习率继续训练。ReduceLROnPlateau 就是一个监控损失变化,从而对学习率进行衰减的一种调度策略。

ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10, threshold=0.0001, threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-08, verbose=False)
  1. mode 参数一般我们使用默认的 min 模式;
  2. factor 表示当需要对学习率进行衰减时的计算因子:new_lr = old_lr * factor;
  3. patience 表示多少个 epoch 损失没有下降时,对学习率进行衰减;
  4. threshold 表示阈值,其和 threshold_mode 一起决定损失阈值。threshold_mode 默认是 rel 模式,即:当 loss 不小于 best + threshold 时,就开始统计 patience;
  5. cooldown 表示学习率降低之后,多少个 epoch 内不再监控损失变化;
  6. eps 表示学习率下降低于该值,则学习率不再衰减;
  7. min_lr 表示学习率下降的最小值,低于该值就不再下降。
import torch
from torch.optim.lr_scheduler import ReduceLROnPlateau
import torch.optim as optim
import torch.nn as nn
import torch
import matplotlib.pyplot as plt


if __name__ == '__main__':

    # 模型参数
    model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
    # 优化器
    optimizer = optim.SGD(model_parameters, lr=0.1)
    # 调度器
    # cooldown 学习修改之后,经过多少个 epoch 之后才进行正常的统计是否损失下降等
    # factor 当损失无法继续下降时,学习率 new_lr = lr * factor
    # patience 表示 patience epoch 仍然不降低损失则对学习率进行衰减
    scheduler = ReduceLROnPlateau(optimizer, factor=0.2, patience=2, cooldown=2, verbose=True)

    learning_rates = [0.1]
    torch.manual_seed(0)
    losses = torch.randint(1, 100, [100,]) / 100
    learning_rates = [0.1]
    for loss in losses:
        # 先优化器更新
        optimizer.step()
        # 再调度器更新
        scheduler.step(loss)
        # 记录此刻学习率
        learning_rates.append(scheduler.optimizer.param_groups[0]['lr'])

    # 横轴损失,纵轴学习率
    plt.plot(range(101), learning_rates)
    plt.title('ReduceLROnPlateau')
    plt.grid()
    plt.show()

程序输出的内容内容显示的是,在多少个 epoch 时学习率变成哪个值。

Epoch 00005: reducing learning rate of group 0 to 2.0000e-02.
Epoch 00010: reducing learning rate of group 0 to 4.0000e-03.
Epoch 00015: reducing learning rate of group 0 to 8.0000e-04.
Epoch 00020: reducing learning rate of group 0 to 1.6000e-04.
Epoch 00026: reducing learning rate of group 0 to 3.2000e-05.
Epoch 00031: reducing learning rate of group 0 to 6.4000e-06.
Epoch 00036: reducing learning rate of group 0 to 1.2800e-06.
Epoch 00041: reducing learning rate of group 0 to 2.5600e-07.
Epoch 00047: reducing learning rate of group 0 to 5.1200e-08.
Epoch 00052: reducing learning rate of group 0 to 1.0240e-08.

上图表示在哪些 epoch 时,学习率进行了衰减。

5. CyclicLR

CyclicLR 指的是循环的学习率调整策略。我们只需要设置一个学习率区间,CyclicLR 会以恒定的频率在这个区间内循环。

CyclicLR(optimizer, base_lr, max_lr, step_size_up=2000, step_size_down=None, mode='triangular', gamma=1.0, scale_fn=None, scale_mode='cycle', cycle_momentum=True, base_momentum=0.8, max_momentum=0.9, last_epoch=- 1, verbose=False)
  1. base_lr 初始学习率,base_lr 一般为 max_lr 的 1/3 或 1/4。
  2. max_lr 最大学习率
  3. step_size_up 表示经过多少 step,从初始学习率上升到最大学习率,一般设置为 样本数量 / batch_size 的 2-10 倍;
  4. step_size_down 表示经过多少 step,从最大学习率下降到初始学习率,一般设置为 样本数量 / batch_size 的 2-10 倍。
import torch
from torch.optim.lr_scheduler import CyclicLR
import torch.optim as optim
import torch.nn as nn
import torch
import matplotlib.pyplot as plt


if __name__ == '__main__':

    # 模型参数
    model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
    # 优化器
    optimizer = optim.SGD(model_parameters, lr=0.1)
    # 调度器
    scheduler = CyclicLR(optimizer,
                         base_lr=0.1,
                         max_lr=1,
                         step_size_up=10,
                         step_size_down=10,
                         mode='triangular')

    learning_rates = [0.1]
    learning_rates = [0.1]
    for _ in range(100):
        # 先优化器更新
        optimizer.step()
        # 再调度器更新
        scheduler.step()
        # 记录此刻学习率
        learning_rates.append(scheduler.get_last_lr()[0])

    # 横轴损失,纵轴学习率
    plt.plot(range(101), learning_rates)
    plt.title('CyclicLR')
    plt.grid()
    plt.show()

6. OneCycleLR

CyclicLR 使得学习率训返往复的在最大值与最小值之间变化,而 OneCycleLR 则可以理解为一个 cycle 的版本。它总体的变化,是从一个初始的学习率先变化到设置的最大学习率,然后再从最大学习率变化到最终的学习率,训练结束。由于整个训练只用一个 cycle,所以需要设置迭代的总次数 total_steps。

OneCycleLR(optimizer, max_lr, total_steps=None, epochs=None, steps_per_epoch=None, pct_start=0.3, anneal_strategy='cos', cycle_momentum=True, base_momentum=0.85, max_momentum=0.95, div_factor=25.0, final_div_factor=10000.0, three_phase=False, last_epoch=- 1, verbose=False)
  1. 初始学习率 = max_lr / div_factor;
  2. 最大学习率 = max_lr;
  3. 最终学习率= 初始学习率 / final_div_factor;
  4. pct_start 表示初始学习率到最大学习率这个训练期间占整个训练区间的比重,这部分基本都是学习率上升区间。
import torch
from torch.optim.lr_scheduler import OneCycleLR
import torch.optim as optim
import torch.nn as nn
import torch
import matplotlib.pyplot as plt


if __name__ == '__main__':

    # 模型参数
    model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
    # 优化器
    optimizer = optim.SGD(model_parameters, lr=0.1)
    # 调度器
    # pct_start 学习率上升部分占比
    # total_steps 整个训练过程共有多少 step
    # max_lr / div_factor = 初始学习率
    # 初始学习率 / final_div_factor = 最终落脚的学习率
    # 过程从初始学习率到 max_lr 再到最终落脚的学习率,一个 cycle
    scheduler = OneCycleLR(optimizer,
                           max_lr=0.1,
                           pct_start=0.8,
                           total_steps=100,
                           div_factor=4,
                           final_div_factor=1e-1)

    learning_rates = []
    for _ in range(100):
        # 先优化器更新
        optimizer.step()
        # 再调度器更新
        scheduler.step()
        # 记录此刻学习率
        learning_rates.append(scheduler.get_last_lr()[0])

    # 横轴损失,纵轴学习率
    plt.plot(range(100), learning_rates)
    plt.title('OneCycleLR')
    plt.grid()
    plt.show()

7. CosineAnnealingWarmRestarts

CosineAnnealingWarmRestarts 是设置学习率在什么 step 时回到初始学习率。T0 表示设置第一次回到初始学习率的 epoch 点。

如果 T_mult = 2 的话:

  1. 第二次回到初始学习率的 epoch 点为:上一个初始学习率的 epoch 点 + 2 * 10 = 10 + 20 = 30
  2. 第三次回到初始学习率的 epoch 点为: 上一个初始学习率的 epoch 点 + 4 * 10 = 30 + 40 = 70
  3. 第四次回到初始学习率的 epoch 点为: 上一个初始学习率的 epoch 点 + 8 * 10 = 70 + 80 = 150
  4. 一次类推
CosineAnnealingWarmRestarts(optimizer, T_0, T_mult=1, eta_min=0, last_epoch=- 1, verbose=False)

以下为示例代码:

import torch
from torch.optim.lr_scheduler import CosineAnnealingWarmRestarts
import torch.optim as optim
import torch.nn as nn
import torch
import matplotlib.pyplot as plt


if __name__ == '__main__':

    # 模型参数
    model_parameters = [nn.Parameter(torch.tensor([1, 2, 3], dtype=torch.float32))]
    # 优化器
    optimizer = optim.SGD(model_parameters, lr=0.1)
    # 调度器
    # T_0 表示第 10 epoch 时,将学习率回归到初始学习率
    # T_mult=2时,表示2倍变化。第二次是 2*10,第三次是 4*10,第四次是 8*10
    scheduler = CosineAnnealingWarmRestarts(optimizer,
                                            T_0=10,
                                            T_mult=2,
                                            eta_min=0.01)

    learning_rates = [0.1]
    for _ in range(100):
        # 先优化器更新
        optimizer.step()
        # 再调度器更新
        scheduler.step()
        # 记录此刻学习率
        learning_rates.append(scheduler.get_last_lr()[0])

    # 横轴损失,纵轴学习率
    plt.plot(range(101), learning_rates)
    plt.title('CosineAnnealingWarmRestarts')
    plt.xticks(range(0, 100, 5))
    plt.grid()
    plt.show()
未经允许不得转载:一亩三分地 » Learning Rate Scheduler(二)