文章目录
在深度学习中,学习率调度器(Learning Rate Scheduler) 是用来动态调整学习率的工具。它的主要目的是在训练过程中自动调整学习率,以提高训练的效率和效果。之所以称其为“调度器”,是因为它控制着学习率的调整和更新,类似于调度一个过程或者任务,它按照某种策略和规则来“调度”学习率,本文将详细介绍pytorch中动态调整学习率方法之一 torch.optim.lr_scheduler.StepLR
1. torch.optim.lr_scheduler.StepLR 官方文档详解
官方文档定义:
class torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=-1, verbose='deprecated')
每隔 step_size 个 epochs 将每个参数组的学习率衰减 gamma 倍。
请注意,这种衰减可能与来自此调度程序外部的学习率的其他更改同时发生。当 last_epoch=-1 时,将初始 lr 设置为 lr。
参数:
- o p t i m i z e r optimizer optimizer (优化器) : 包装的优化器
- s t e p _ s i z e ( i n t ) step\_size(int) step_size(int) : 学习率衰减周期
- g a m m a ( f l o a t ) gamma (float) gamma(float) : 学习率衰减的乘法因子, 默认值:0.1
- l a s t _ e p o c h ( i n t ) last\_epoch (int) last_epoch(int) : 最后一个 epoch 的索引, 默认值:-1 表示从头开始
- v e r b o s e ( b o o l ∣ s t r ) verbose (bool | str) verbose(bool∣str): 如果为 True,则为每次更新打印一条消息到标准输出, 默认值:False。
注意:
- 自版本 2.2 起不推荐使用: v e r b o s e verbose verbose 已弃用。请使用 g e t _ l a s t _ l r ( ) get\_last\_lr() get_last_lr()访问学习率
其它:
- g e t _ l a s t _ l r ( ) get\_last\_lr() get_last_lr() : 返回当前调度程序计算的最后一个学习率, 返回类型 L i s t [ f l o a t ] List[float] List[float]
- g e t _ l r ( ) get\_lr() get_lr() : 计算每个组的学习率
- l o a d _ s t a t e _ d i c t ( s t a t e _ d i c t ) load\_state\_dict(state\_dict) load_state_dict(state_dict) : 加载调度程序的状态,参数: s t a t e _ d i c t ( d i c t ) state\_dict (dict) state_dict(dict) 调度程序状态,应为 s t a t e _ d i c t ( ) state\_dict() state_dict() 调用返回的对象。
- p r i n t _ l r ( i s _ v e r b o s e , g r o u p , l r , e p o c h = N o n e ) print\_lr(is\_verbose, group, lr, epoch=None) print_lr(is_verbose,group,lr,epoch=None) : 显示当前学习率。
- s t a t e _ d i c t ( ) state\_dict() state_dict() : 将调度程序的状态作为 d i c t dict dict 返回,它包含 s e l f . _ _ d i c t _ _ self.\_\_dict\_\_ self.__dict__ 中每个变量的条目,这些变量不是优化器。
- s t e p ( e p o c h = N o n e ) step(epoch=None) step(epoch=None) : 执行一步操作,即更新一次学习率
注意:
- 自版本 2.4 起不推荐使用: p r i n t _ l r ( ) print\_lr() print_lr() 已弃用。请使用 g e t _ l a s t _ l r ( ) get\_last\_lr() get_last_lr() 访问学习率。
2. 使用示例
2.1 官方提供使用示例
- 假设初始 l r = 0.05 lr = 0.05 lr=0.05, s t e p _ s i z e = 30 step\_size=30 step_size=30, g a m m a = 0.1 gamma=0.1 gamma=0.1,即每 30 个 epoch后,将学习率乘以 0.1
- 当 e p o c h < 30 epoch< 30 epoch<30 时, l r = 0.05 lr = 0.05 lr=0.05 ,即 l r = l r lr=lr lr=lr
- 当 30 < = e p o c h < 60 30 <= epoch < 60 30<=epoch<60 , l r = 0.005 lr = 0.005 lr=0.005,即 l r n e w = l r o l d ∗ g a m m a = 0.05 ∗ 0.1 = 0.005 lr_{new}=lr_{old}*gamma=0.05*0.1=0.005 lrnew=lrold∗gamma=0.05∗0.1=0.005
- 当 60 < = e p o c h < 90 60 <= epoch < 90 60<=epoch<90 , l r = 0.0005 lr = 0.0005 lr=0.0005,即 l r n e w = l r o l d ∗ g a m m a = 0.005 ∗ 0.1 = 0.0005 lr_{new}=lr_{old}*gamma=0.005*0.1=0.0005 lrnew=lrold∗gamma=0.005∗0.1=0.0005
# Assuming optimizer uses lr = 0.05 for all groups
# lr = 0.05 if epoch < 30
# lr = 0.005 if 30 <= epoch < 60
# lr = 0.0005 if 60 <= epoch < 90
# ...
scheduler = StepLR(optimizer, step_size=30, gamma=0.1)
for epoch in range(100):
train(...)
validate(...)
scheduler.step()
2.2 自己写代码测试方法
2.2.1 get_last_lr() 方法
- 用于返回调度器计算的 最后一个学习率。这个学习率是在调度器(scheduler)调整之后的当前学习率
- 如果优化器中有多个参数组, g e t _ l a s t _ l r ( ) get\_last\_lr() get_last_lr() 返回的是一个列表,每个元素对应一个参数组的学习率
- 如果优化器中只有一个参数组, g e t _ l a s t _ l r ( ) get\_last\_lr() get_last_lr() 返回一个只有一个元素的列表
import torch
from torch import nn
from torch import optim
net = nn.Linear(3,4)
def train():
optimizer = optim.Adam(net.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size = 1, gamma=0.1)
for epoch in range(0, 10):
print(epoch, scheduler.get_last_lr())
optimizer.step()
scheduler.step()
if __name__ == "__main__":
train()
代码输出:
0 [0.1]
1 [0.010000000000000002]
2 [0.0010000000000000002]
3 [0.00010000000000000003]
4 [1.0000000000000004e-05]
5 [1.0000000000000004e-06]
6 [1.0000000000000005e-07]
7 [1.0000000000000005e-08]
8 [1.0000000000000005e-09]
9 [1.0000000000000006e-10]
2.2.2 state_dict() 方法
- s t a t e _ d i c t ( ) state\_dict() state_dict() 是一个非常重要的函数,它能够返回一个包含模型或优化器状态的字典(dict)。对于学习率调度器来说, s t a t e _ d i c t ( ) state\_dict() state_dict() 返回调度器的状态,包括它的参数和变量,用于保存和恢复调度器的状态等
代码示例:
import torch
from torch import nn
from torch import optim
net = nn.Linear(3,4)
def train():
optimizer = optim.Adam(net.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size = 1, gamma=0.1)
for epoch in range(10):
print(epoch, scheduler.state_dict())
optimizer.step()
scheduler.step()
if __name__ == "__main__":
train()
代码输出:
0 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 0, 'verbose': False, '_step_count': 1, '_get_lr_called_within_step': False, '_last_lr': [0.1]}
1 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 1, 'verbose': False, '_step_count': 2, '_get_lr_called_within_step': False, '_last_lr': [0.010000000000000002]}
2 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 2, 'verbose': False, '_step_count': 3, '_get_lr_called_within_step': False, '_last_lr': [0.0010000000000000002]}
3 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 3, 'verbose': False, '_step_count': 4, '_get_lr_called_within_step': False, '_last_lr': [0.00010000000000000003]}
4 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 4, 'verbose': False, '_step_count': 5, '_get_lr_called_within_step': False, '_last_lr': [1.0000000000000004e-05]}
5 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 5, 'verbose': False, '_step_count': 6, '_get_lr_called_within_step': False, '_last_lr': [1.0000000000000004e-06]}
6 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 6, 'verbose': False, '_step_count': 7, '_get_lr_called_within_step': False, '_last_lr': [1.0000000000000005e-07]}
7 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 7, 'verbose': False, '_step_count': 8, '_get_lr_called_within_step': False, '_last_lr': [1.0000000000000005e-08]}
8 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 8, 'verbose': False, '_step_count': 9, '_get_lr_called_within_step': False, '_last_lr': [1.0000000000000005e-09]}
9 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 9, 'verbose': False, '_step_count': 10, '_get_lr_called_within_step': False, '_last_lr': [1.0000000000000006e-10]}
输出详解:
step_size:
该值表示每隔多少个 epoch 学习率就会发生变化。在例子中, s t e p _ s i z e = 1 step\_size = 1 step_size=1,意味着每经过 1 个 epoch,学习率都会更新一次gamma:
这是学习率更新的衰减因子。每次调用 s c h e d u l e r . s t e p ( ) scheduler.step() scheduler.step() 时,当前的学习率将会乘以 g a m m a gamma gamma。例子中, g a m m a = 0.1 gamma = 0.1 gamma=0.1,意味着每次更新学习率时,学习率将减少为原来的 10%base_lrs:
这是每个参数组的初始学习率(在调度器调整之前的学习率)。这里的 [ 0.1 ] [0.1] [0.1]表示模型的初始学习率是 0.1。如果有多个参数组,这里会是一个列表,列出每个参数组的初始学习率last_epoch:
这是上一个 epoch 的编号,用来确定学习率更新时的参考点。 l a s t _ e p o c h = 0 last\_epoch = 0 last_epoch=0 表示调度器刚刚初始化,学习率还没有更新过。通常,last_epoch 用来恢复训练时从哪个 epoch 开始更新学习率verbose:
该参数控制调度器是否在学习率更新时打印详细信息。 v e r b o s e = F a l s e verbose = False verbose=False 表示调度器在更新学习率时不会打印信息。如果设置为 T r u e True True,则每次更新学习率时都会打印一条日志。_step_count:
这个内部变量跟踪调度器已经调用了多少次 step()。在例子中, _ s t e p _ c o u n t = 1 \_step\_count = 1 _step_count=1,表示调度器已经调用过一次 s t e p ( ) step() step(),即更新过一次学习率_get_lr_called_within_step:
是一个内部标志,表示是否在 step() 方法内部调用了get_lr()
。通常不需要关注这个值,它帮助调度器管理内部逻辑_last_lr:
这是调度器最近一次计算的学习率。这个列表保存了每个参数组的学习率。在你的例子中, _ l a s t _ l r = [ 0.1 ] \_last\_lr = [0.1] _last_lr=[0.1],表示当前学习率是 0.1。这会在 scheduler.step() 后更新为新的学习率。
2.2.3 load_state_dict() 保存和加载调度器
可以将 state_dict() 保存到文件中,然后在以后恢复。如下是一个保存和加载学习率调度器状态的例子:
保存调度器状态:
# 保存调度器的 state_dict
torch.save(scheduler.state_dict(), 'scheduler_state.pth')
加载调度器状态:
# 加载调度器的 state_dict
scheduler.load_state_dict(torch.load('scheduler_state.pth'))
通过这种方式,可以在训练中断后恢复学习率调度器的状态,并继续进行训练。
保存调度器状态示例:
当 epoch = 5 时,保存调度器状态:
import torch
from torch import nn
from torch import optim
net = nn.Linear(3,4)
def train():
optimizer = optim.Adam(net.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size = 1, gamma=0.1)
for epoch in range(10):
if epoch == 5:
torch.save(scheduler.state_dict(), 'scheduler_state.pth')
break
optimizer.step()
scheduler.step()
if __name__ == "__main__":
train()
加载调度器状态示例:
import torch
from torch import nn
from torch import optim
net = nn.Linear(3,4)
def train():
optimizer = optim.Adam(net.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size = 1, gamma=0.1)
scheduler.load_state_dict(torch.load("scheduler_state.pth"))
for epoch in range(3):
print(epoch, scheduler.state_dict())
optimizer.step()
scheduler.step()
if __name__ == "__main__":
train()
代码输出:
0 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 5, 'verbose': False, '_step_count': 6, '_get_lr_called_within_step': False, '_last_lr': [1.0000000000000004e-06]}
1 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 6, 'verbose': False, '_step_count': 7, '_get_lr_called_within_step': False, '_last_lr': [0.010000000000000002]}
2 {'step_size': 1, 'gamma': 0.1, 'base_lrs': [0.1], 'last_epoch': 7, 'verbose': False, '_step_count': 8, '_get_lr_called_within_step': False, '_last_lr': [0.0010000000000000002]}
可以看到:
last_epoch
从 5 开始_step_count
从6 开始_last_lr
从 1e-6 开始
说明是从上次终断的状态继续运行
3. 思考
3.1 为什么需要state_dict()
保存和恢复训练:
当你希望在训练中断后恢复训练时,你可以保存模型和调度器的 state_dict(),然后在恢复时加载它们,确保学习率调度器从上次停止的地方继续工作,而不是从头开始。调试和分析:
通过 state_dict() 可以查看学习率的变化,帮助你调试和分析训练过程中调度器的行为。
3.2 get_lr() 与 get_last_lr() 的输出不一致问题
测试代码:
import torch
from torch import nn
from torch import optim
net = nn.Linear(3,4)
def train():
optimizer = optim.Adam(net.parameters(), lr=0.1)
scheduler = optim.lr_scheduler.StepLR(optimizer, step_size = 1, gamma=0.1)
for epoch in range(3):
print(epoch, scheduler.get_lr())
print(epoch, scheduler.get_last_lr())
print([group["lr"] for group in optimizer.param_groups])
print("==========================================")
optimizer.step()
scheduler.step()
if __name__ == "__main__":
train()
代码输出:
0 [0.1]
0 [0.1]
[0.1]
==========================================
1 [0.0010000000000000002]
1 [0.010000000000000002]
[0.010000000000000002]
==========================================
2 [0.00010000000000000003]
2 [0.0010000000000000002]
[0.0010000000000000002]
==========================================
不知道为什么从 epoch > 0 之后,get_lr()
每次都比 get_last_lr()
提前一步更新,但是通过查看 optimizer.param_groups
的学习率与 get_last_lr()
一致。
get_lr()
函数源码:
def get_lr(self):
if not self._get_lr_called_within_step:
warnings.warn("To get the last learning rate computed by the scheduler, "
"please use `get_last_lr()`.", UserWarning)
if (self.last_epoch == 0) or (self.last_epoch % self.step_size != 0):
return [group['lr'] for group in self.optimizer.param_groups]
return [group['lr'] * self.gamma
for group in self.optimizer.param_groups]
我们可以看到在源码中当:if (self.last_epoch == 0) or (self.last_epoch % self.step_size != 0)
,返回值为[group['lr'] for group in self.optimizer.param_groups]
, 否则的话返回值为[group['lr'] * self.gamma for group in self.optimizer.param_groups]
,因此可以很好的解释上面的现象,但是Pytorch为什么要这样做呢??目前没有找到相关资料,可以评论区留言讨论!
get_last_lr()
函数源码:
def get_last_lr(self):
""" Return last computed learning rate by current scheduler.
"""
return self._last_lr