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提供了六种学习率调整方法,可分为三大类,分别是

  1. 有序调整;
  2. 自适应调整;
  3. 自定义调整。

第一类,依一定规律有序进行调整,这一类是最常用的,分别是等间隔下降(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

展开阅读更多
来源:孟繁阳的博客
本篇文章来源于孟繁阳的博客,如需转载,请注明出处,谢谢配合。
孟繁阳的博客 » Pytorch学习笔记(六):学习率
Loading...

发表评论

表情
图片 链接 代码

孟繁阳的博客

博客首页 文章归档