Pytorch学习笔记(六):学习率
上一篇笔记对优化器进行了介绍,可以发现优化器中最重要的一个参数就是学习率,合理的学习率可以使优化器快速收敛。一般在训练初期给予较大的学习率,随着训练的进行,学习率逐渐减小。学习率什么时候减小,减小多少,这就涉及到学习率调整方法,本篇笔记将进行说明。
部分学习资源链接:
- Pytorch官方在优达学城公布的教学视频:
https://classroom.udacity.com/courses/ud188 - 与其配套的代码和练习答案:
https://github.com/udacity/deep-learning-v2-pytorch - 陈云《深度学习框架Pytorch入门与实践》:
https://github.com/chenyuntc/pytorch-book - 《Pytorch模型训练实用教程》:
https://github.com/tensor-yu/PyTorch_Tutorial
之前的相关文章:
- 《PYTORCH学习笔记(一):代码结构布局》
- 《PYTORCH学习笔记(二):载入数据》
- 《PYTORCH学习笔记(三):模型定义》
- 《PYTORCH学习笔记(四):损失函数》
- 《PYTORCH学习笔记(五):优化器》
PyTorch中提供了六种方法供大家使用,下面将一一介绍,最后对学习率调整方法进行总结。
1. lr_scheduler.StepLR
CLASS torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma=0.1, last_epoch=-1)
功能:
等间隔调整学习率,调整倍数为 gamma 倍,调整间隔为 step_size 。间隔单位是 step 。需要注意的是,step 通常是指 epoch ,不要弄成 iteration 了。
epoch——使用整个训练样本集传播一次。
一次传播 = 一次前向传播 + 一次后向传播。(所有的训练样本完成一次Forword运算以及一次BP运算)
但是考虑到内存不够用的问题,训练样本们往往并不是全都一起拿到内存中去训练,而是一次拿一个batch去训练,一个batch包含的样本数称为batch size。
iteration——使用batch size个样本传播一次。同样,一次传播 = 一次前向传播 + 一次后向传播。
eg. 我们有1000个训练样本,batch size为100,那么完成一次epoch就需要10个iteration。
参数:
step_size(int):学习率下降间隔数,若为30,则会在30、60、90……个step时,将学习率调整为lr*gamma。
gamma(float):学习率调整倍数,默认为0.1倍,即下降10倍。
last_epoch(int):上一个epoch数,这个变量用来指示学习率是否需要调整。当last_epoch符合设定的间隔时,就会对学习率进行调整。当为-1时,学习率设置为初始值。
代码示例:
# 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. lr_scheduler.MultiStepLR
CLASS torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones, gamma=0.1, last_epoch=-1)
功能:
按设定的间隔调整学习率。这个方法适合后期调试使用,观察loss曲线,为每个实验定制学习率调整时机。
参数:
milestones(list):一个list,每一个元素代表何时调整学习率,list元素必须是递增的。如 milestones=[30,80,120]
gamma(float):学习率调整倍数,默认为0.1倍,即下降10倍。
last_epoch(int):上一个epoch数,这个变量用来指示学习率是否需要调整。当last_epoch符合设定的间隔时,就会对学习率进行调整。当为-1时,学习率设置为初始值。
代码示例:
# Assuming optimizer uses lr = 0.05 for all groups
# lr = 0.05 if epoch < 30
# lr = 0.005 if 30 <= epoch < 80
# lr = 0.0005 if epoch >= 80
scheduler = MultiStepLR(optimizer, milestones=[30,80], gamma=0.1)
for epoch in range(100):
train(...)
validate(...)
scheduler.step()
3. lr_scheduler.ExponentialLR
CLASS torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma, last_epoch=-1)
功能:
按指数衰减调整学习率,调整公式: \(lr = lr * gamma^{epoch}\)
参数:
gamma:学习率调整倍数的底,指数为epoch,即 gamma**epoch
last_epoch(int):上一个epoch数,这个变量用来指示学习率是否需要调整。当last_epoch符合设定的间隔时,就会对学习率进行调整。当为-1时,学习率设置为初始值。
4. lr_scheduler.CosineAnnealingLR
CLASS torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max, eta_min=0, last_epoch=-1)
功能:
以余弦函数为周期,并在每个周期最大值时重新设置学习率。具体如下图所示:
详情可以查阅论文:《 SGDR: Stochastic Gradient Descent with Warm Restarts》
参数:
T_max(int):一次学习率周期的迭代次数,即T_max个epoch之后重新设置学习率。
eta_min(float):最小学习率,即在一个周期中,学习率最小会下降到eta_min,默认值为0。
学习率调整公式:
\[\eta_{t}=\eta_{min}+\frac{1}{2}(\eta_{max}-\eta_{min})(1+cos(\frac{T_{cur}}{T_{max}}\pi))\]
可以看出是以初始学习率为最大学习率,以\(2*T_{max}\)为周期,在一个周期内先下降,后上升。
示例:
\(T_{max}\) = 200, 初始学习率 = 0.001, \(\eta_{min}\) = 0
5. lr_scheduler.ReduceLROnPlateau
CLASS torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10, verbose=False, threshold=0.0001, threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-08)
功能:
当某指标不再变化(下降或升高),调整学习率,这是非常实用的学习率调整策略。例如,当验证集的 loss 不再下降时,进行学习率调整;或者监测验证集的 accuracy ,当 accuracy 不再上升时,则调整学习率。
参数:
mode(str):模式选择,有 min和max两种模式,min表示当指标不再降低(如监测loss),max表示当指标不再升高(如监测accuracy)。
factor(float):学习率调整倍数(等同于其它方法的gamma),即学习率更新为 lr = lr * factor
patience(int):直译——"耐心",即忍受该指标多少个step不变化,当忍无可忍时,调整学习率。
verbose(bool):是否打印学习率信息, print('Epoch {:5d}: reducing learning rate' ' of group {} to {:.4e}.'.format(epoch, i, new_lr))
threshold(float):Threshold for measuring the new optimum,配合threshold_mode使用。
threshold_mode(str):选择判断指标是否达最优的模式,有两种模式,rel和abs。 当threshold_mode==rel,并且mode==max时,dynamic_threshold = best * ( 1 + threshold );当threshold_mode==rel,并且mode==min时,dynamic_threshold = best * ( 1 - threshold ); 当threshold_mode==abs,并且mode==max时,dynamic_threshold = best + threshold ; 当threshold_mode==rel,并且mode==max时,dynamic_threshold = best - threshold
cooldown(int):“冷却时间“,当调整学习率之后,让学习率调整策略冷静一下,让模型再训练一段时间,再重启监测模式。
min_lr(float or list):学习率下限,可为float,或者list,当有多个参数组时,可用list进行设置。
eps(float):学习率衰减的最小值,当学习率变化小于eps时,则不调整学习率。
代码示例:
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
scheduler = ReduceLROnPlateau(optimizer, 'min')
for epoch in range(10):
train(...)
val_loss = validate(...)
# Note that step should be called after validate()
scheduler.step(val_loss)
6. lr_scheduler.LambdaLR
CLASS torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch=-1)
功能:
为不同参数组设定不同学习率调整策略。调整规则为,lr = base_lr * lmbda(self.last_epoch) 。
参数:
lr_lambda(function or list):一个计算学习率调整倍数的函数,输入通常为step,当有多个参数组时,设为list。
last_epoch(int):上一个epoch数,这个变量用来指示学习率是否需要调整。当last_epoch符合设定的间隔时,就会对学习率进行调整。当为-1时,学习率设置为初始值。
代码举例:
ignored_params = list(map(id, net.fc3.parameters()))
base_params = filter(lambda p: id(p) not in ignored_params, net.parameters())
optimizer = optim.SGD([
{'params': base_params},
{'params': net.fc3.parameters(), 'lr': 0.001*100}], 0.001, momentum=0.9, weight_decay=1e-4)
lambda1 = lambda epoch: epoch // 3
lambda2 = lambda epoch: 0.95 ** epoch
scheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=[lambda1, lambda2])
for epoch in range(100):
scheduler.step()
print('epoch: ', i, 'lr: ', scheduler.get_lr())
train(...)
validate(...)
输出结果:
epoch: 0 lr: [0.0, 0.1]
epoch: 1 lr: [0.0, 0.095]
epoch: 2 lr: [0.0, 0.09025]
epoch: 3 lr: [0.001, 0.0857375]
epoch: 4 lr: [0.001, 0.081450625]
epoch: 5 lr: [0.001, 0.07737809374999999]
epoch: 6 lr: [0.002, 0.07350918906249998]
epoch: 7 lr: [0.002, 0.06983372960937498]
epoch: 8 lr: [0.002, 0.06634204312890622]
epoch: 9 lr: [0.003, 0.0630249409724609]
为什么第一个参数组的学习率会是0呢? 来看看学习率是如何计算的。
第一个参数组的初始学习率设置为 0.001 , lambda1 = lambda epoch: epoch // 3
第1个epoch时,由lr = base_lr * lmbda(self.last_epoch),可知道 lr = 0.001 * (0//3) ,又因为1//3等于0,所以导致学习率为0。
第二个参数组的学习率变化,就很容易看啦,初始为0.1,lr = 0.1 * 0.95^epoch ,当epoch为0时,lr=0.1 ,epoch为1时,lr=0.1*0.95。
几种动态调整学习率的方法
1. 自定义根据 epoch 改变学习率
这种方法在开源代码中常见,此处引用 pytorch 官方实例中的代码 adjust_lr
def adjust_learning_rate(optimizer, epoch):
"""Sets the learning rate to the initial LR decayed by 10 every 30 epochs"""
lr = args.lr * (0.1 ** (epoch // 30))
for param_group in optimizer.param_groups:
param_group['lr'] = lr
注:在调用此函数时需要输入所用的 optimizer 以及对应的 epoch ,并且 args.lr 作为初始化的学习率也需要给出。
使用代码示例:
optimizer = torch.optim.SGD(model.parameters(),lr = args.lr,momentum = 0.9)
for epoch in range(10):
adjust_learning_rate(optimizer,epoch)
train(...)
validate(...)
2. 针对模型的不同层设置不同的学习率
当我们在使用预训练的模型时,需要对分类层进行单独修改并进行初始化,其他层的参数采用预训练的模型参数进行初始化,这个时候我们希望在进行训练过程中,除分类层以外的层只进行微调,不需要过多改变参数,因此需要设置较小的学习率。而改正后的分类层则需要以较大的步子去收敛,学习率往往要设置大一点以 resnet101 为例,分层设置学习率。
model = torchvision.models.resnet101(pretrained=True)
large_lr_layers = list(map(id,model.fc.parameters()))
small_lr_layers = filter(lambda p:id(p) not in large_lr_layers,model.parameters())
optimizer = torch.optim.SGD([
{"params":large_lr_layers},
{"params":small_lr_layers,"lr":1e-4}
],lr = 1e-2,momenum=0.9)
注:large_lr_layers 学习率为 1e-2,small_lr_layers 学习率为 1e-4,两部分参数共用一个 mometum
3. 根据具体需要改变 lr
这里使用 ReduceLROnPlateau,因为它可以根据损失或者准确度的变化来改变 lr 。
class torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.1, patience=10, verbose=False, threshold=0.0001, threshold_mode='rel', cooldown=0, min_lr=0, eps=1e-08)
以 acc 为例,当 mode 设置为 “max” 时,如果 acc 在给定 patience 内没有提升,则以 factor 的倍率降低 lr。
示例:
optimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9)
scheduler = ReduceLROnPlateau(optimizer, 'max',verbose=1,patience=3)
for epoch in range(10):
train(...)
val_acc = validate(...)
# 降低学习率需要在给出 val_acc 之后
scheduler.step(val_acc)
4. 手动设置 lr 衰减区间
def adjust_learning_rate(optimizer, lr):
for param_group in optimizer.param_groups:
param_group['lr'] = lr
for epoch in range(60):
lr = 30e-5
if epoch > 25:
lr = 15e-5
if epoch > 30:
lr = 7.5e-5
if epoch > 35:
lr = 3e-5
if epoch > 40:
lr = 1e-5
adjust_learning_rate(optimizer, lr)
5. 余弦退火
论文: SGDR: Stochastic Gradient Descent with Warm Restarts
使用示例:
epochs = 60
optimizer = optim.SGD(model.parameters(),lr = config.lr,momentum=0.9,weight_decay=1e-4)
scheduler = lr_scheduler.CosineAnnealingLR(optimizer,T_max = (epochs // 9) + 1)
for epoch in range(epochs):
scheduler.step(epoch)
学习率调整小结
PyTorch提供了六种学习率调整方法,可分为三大类,分别是
- 有序调整;
- 自适应调整;
- 自定义调整。
第一类,依一定规律有序进行调整,这一类是最常用的,分别是等间隔下降(Step),按需设定下降间隔(MultiStep),指数下降(Exponential)和 CosineAnnealing 。这四种方法的调整时机都是人为可控的,也是训练时常用到的。
第二类,依训练状况伺机调整,这就是 ReduceLROnPlateau 方法。该法通过监测某一指标的变化情况,当该指标不再怎么变化的时候,就是调整学习率的时机,因而属于自适应的调整。
第三类,自定义调整,Lambda 。Lambda 方法提供的调整策略十分灵活,我们可以为不同的层设定不同的学习率调整方法,这在 finetune 中十分有用,我们不仅可为不同的层设定不同的学习率,还可以为其设定不同的学习率调整策略。
在PyTorch中,学习率的更新是通过 scheduler.step() ,而我们知道影响学习率的一个重要参数就是epoch,这里需要注意的是在执行一次 scheduler.step() 之后, epoch 会加1,因此 scheduler.step() 要放在 epoch 的 for 循环当中执行。
小案例
import torch
import torch.nn.functional as F
from torch.autograd import Variable
import matplotlib.pyplot as plt
# 超参数
torch.manual_seed(1)
LR = 0.01
Batch_size = 24
Epoch = 24
# 定义数据集
x = torch.unsqueeze(torch.linspace(-1,1,1000),dim=1)
y = x**2 + 0.1*torch.normal(torch.zeros(x.size()))
# 先转换成torch能识别的Dataset >> 再使用数据加载器加载数据
data_set = torch.utils.data.TensorDataset(x,y)
dataset_loader = torch.utils.data.DataLoader(dataset = data_set,
batch_size = Batch_size,
shuffle = True,
num_workers = 2,)
# 定义神经网络
class Net(torch.nn.Module):
def __init__(self):
super(Net,self).__init__()
self.hidden = torch.nn.Linear(1,20)
self.predict = torch.nn.Linear(20,1)
def forward(self, input):
x = F.relu(self.hidden(input))
x = self.predict(x)
return x
net_SGD = Net()
net_Momentum = Net()
net_RMSprop = Net()
net_Adam = Net()
nets = [net_SGD,net_Momentum,net_RMSprop,net_Adam]
# 定义优化器(学习率相同)
opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.8)
opt_RMSprop = torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.9)
opt_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.9, 0.99))
optimizers = [opt_SGD, opt_Momentum, opt_RMSprop, opt_Adam] # 放在list里面,可以用个for循环遍历
#回归误差
loss_func = torch.nn.MSELoss()
losses_his = [[], [], [], []] # 记录 training 时不同神经网络的 loss
if __name__ == '__main__':
for epoch in range(Epoch):
print('Epoch: ', epoch)
for step, (batch_x, batch_y) in enumerate(dataset_loader):
b_x = Variable(batch_x) # 包装成Variable
b_y = Variable(batch_y)
# 对每个优化器, 优化属于他的神经网络
for net, opt, l_his in zip(nets, optimizers, losses_his): # 三个都是list形式zip打包处理
output = net(b_x) # get output for every net
loss = loss_func(output, b_y) # compute loss for every net
opt.zero_grad() # clear gradients for next train
loss.backward() # backpropagation, compute gradients
opt.step() # apply gradients
l_his.append(loss.data.item()) # loss recoder
labels = ['SGD', 'Momentum', 'RMSprop', 'Adam']
for i, l_his in enumerate(losses_his):
plt.plot(l_his, label=labels[i])
plt.legend(loc='best')
plt.xlabel('Steps')
plt.ylabel('Loss')
plt.ylim(0, 0.2)
plt.show()

更多关于调整学习率的方法可以参考官方文档:
https://pytorch.org/docs/stable/optim.html#how-to-adjust-learning-rate