书接上回,上篇文章介绍了 7 种学习率的调整策略,PyTorch 1.11 版本中共有 14 种,本篇文章接着介绍剩下的 7 种学习率调整策略。
lr_scheduler.CosineAnnealingLR
lr_scheduler.ChainedScheduler
lr_scheduler.SequentialLR
lr_scheduler.ReduceLROnPlateau
lr_scheduler.CyclicLR
lr_scheduler.OneCycleLR
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)
- mode 参数一般我们使用默认的 min 模式;
- factor 表示当需要对学习率进行衰减时的计算因子:new_lr = old_lr * factor;
- patience 表示多少个 epoch 损失没有下降时,对学习率进行衰减;
- threshold 表示阈值,其和 threshold_mode 一起决定损失阈值。threshold_mode 默认是 rel 模式,即:当 loss 不小于 best + threshold 时,就开始统计 patience;
- cooldown 表示学习率降低之后,多少个 epoch 内不再监控损失变化;
- eps 表示学习率下降低于该值,则学习率不再衰减;
- 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)
- base_lr 初始学习率,base_lr 一般为 max_lr 的 1/3 或 1/4。
- max_lr 最大学习率
- step_size_up 表示经过多少 step,从初始学习率上升到最大学习率,一般设置为 样本数量 / batch_size 的 2-10 倍;
- 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)
- 初始学习率 = max_lr / div_factor;
- 最大学习率 = max_lr;
- 最终学习率= 初始学习率 / final_div_factor;
- 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 的话:
- 第二次回到初始学习率的 epoch 点为:上一个初始学习率的 epoch 点 + 2 * 10 = 10 + 20 = 30
- 第三次回到初始学习率的 epoch 点为: 上一个初始学习率的 epoch 点 + 4 * 10 = 30 + 40 = 70
- 第四次回到初始学习率的 epoch 点为: 上一个初始学习率的 epoch 点 + 8 * 10 = 70 + 80 = 150
- 一次类推
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()