目录
Pytorch中动态设置学习率
在深度神经网络中,学习率是最重要的超参数之一。深度学习的过程类似于八卦炉炼丹,加什么配方、配方加多少量、火候多大等等因素都会影响练出来的丹药的药效,感觉是很玄学的东西。学历率过大和过小也会导致模型出现一些问题。如何调整学习率是“炼丹玄学”中最重要的药方之一。
在很多网络中,学习率一般是固定的。Pyorch中的torch.optim.lr_scheduler为我们封装好了一些在训练过程中动态调整学习率的方法。我们不妨试着用动态学习率,对比一下二者的不同,模型是否因为学习率的动态调整而变得更优秀。
一、一般过程
torch.optim.lr_scheduler 设置的动态学习率是基于每个epoch的。根据每个epoch的学习状态,来设置当前epoch的学习率。需要注意的是学习率的调整需要应用在优化器参数更新之后。如以下代码所示的动态调整学习率的一般过程。
...
optimizer = torch.optim.Adam(...) # 选择一种优化器
scheduler = torch.optim.lr_scheduler.... # 选择一种动态调整学习率的方法,可以是下面几种之一
for epoch in range(epochs):
train(...)
validate(...)
optimizer.step()
scheduler.step() # 需要在优化器参数更新之后再动态调整学习率
...
...
二、不同调整方法介绍
2.1 torch.optim.lr_scheduler.LambdaLR方法
torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda, last_epoch = -1)
参数解释:
optimizer : 之前定义好的优化器的实例名;
lr_lambda : 可以是function或是function list,一般是一个关于epoch数目的函数,从而计算出一个乘数因子,并根据该乘数因子调整初始学习率;
last_epoch : 默认为-1,它一般不用设置,为-1时的作用是将人为设置的学习率设定为调整学习率的基础值lr。
这里需要注意的是,last_epoch默认为-1只能保证第一次调整学习率时,原始待调整的值为人工设定的初始学习率,而第二次调整学习率时,调整的基值就变成了第一次调整后的学习率。
理论:
LambdaLR 更新学习率方式是 lr = lr * lr_lambda(其中,lr 由 optim 系列优化器提供,lr_lambda 由 lr_scheduler 中的 lambdaLR 提供)。
实例:
假设,lr 初始值为0.4, 更新学习率函数lambda表达式为:lr_lambda = 0.1 * epoch。而 epoch 的初始值为 0,则有
epoch = 0 时, lr = 0.4 * 0.1 * 0 = 0
epoch = 1 时, lr = 0.4 * 0.1 * 1 = 0.04
epoch = 2 时, lr = 0.4 * 0.1 * 2 = 0.08
epoch = 3 时, lr = 0.4 * 0.1 * 3 = 0.12
...
感兴趣的同学可由以下完整代码来验证:
import torch
from torch import nn
torch.manual_seed(0)
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=1,out_channels=1,kernel_size=2,stride=1,padding=0)
self.conv2 = nn.Conv2d(in_channels=1, out_channels=1, kernel_size=2, stride=1, padding=0)
def forward(self,x):
out = self.conv1(x)
return out
net1 = model()
optimizer_1 = torch.optim.SGD(net1.parameters(),lr = 0.4)
scheduler_1 = torch.optim.lr_scheduler.LambdaLR(optimizer_1,lr_lambda = lambda epoch:0.1 * epoch)
print('\n当前学习率')
print(scheduler_1.get_lr())
for i in range(10):
scheduler_1.step() # 更新学习率
print(scheduler_1.get_lr())
2.2 torch.optim.lr_scheduler.StepLR 方法
torch.optim.lr_scheduler.StepLR(optimizer, step_size, gamma = 0.1, last_epoch = -1)
参数解释:
optimizer : 要更改学习率的优化器;
step_size : 每训练step_size个epoch,更新一次参数;
gamma : 更新lr的乘法因子;
last_epoch : 最后一个epoch的index,如果是训练了很多个epoch后中断了,继续训练,这个值就等于加载的模型的epoch。
默认为-1表示从头开始训练,即从epoch=0开始。
理论过程:
torch.optim.lr_scheduler.StepLR方法是每过step_size个epoch,做一次更新。更新公式如下:
其中new_lr是得到的新的学习率,initial_lr是初始的学习率,step_size是参数step_size,γ 是参数gamma。
实例:
感兴趣的同学可以用以下代码进行实例验证:
import torch
import torch.nn as nn
from torch.optim.lr_scheduler import StepLR
import itertools
initial_lr = 0.1
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3)
def forward(self, x):
pass
net_1 = model()
optimizer_1 = torch.optim.Adam(net_1.parameters(), lr = initial_lr)
scheduler_1 = StepLR(optimizer_1, step_size=3, gamma=0.1)
print("初始化的学习率:", optimizer_1.defaults['lr'])
for epoch in range(1, 11):
# train
optimizer_1.zero_grad()
optimizer_1.step()
print("第%d个epoch的学习率:%f" % (epoch, optimizer_1.param_groups[0]['lr']))
scheduler_1.step()
结果如下:
初始化的学习率: 0.1
第1个epoch的学习率:0.100000
第2个epoch的学习率:0.100000
第3个epoch的学习率:0.100000
第4个epoch的学习率:0.010000
第5个epoch的学习率:0.010000
第6个epoch的学习率:0.010000
第7个epoch的学习率:0.001000
第8个epoch的学习率:0.001000
第9个epoch的学习率:0.001000
第10个epoch的学习率:0.000100
2.3 torch.optim.lr_scheduler.ExponetialLR方法
torch.optim.lr_scheduler.ExponentialLR(optimizer, gamma, last_epoch = -1)
参数解释:
optimizer : 要更改学习率的优化器;
gamma : 更新lr的乘法因子;
last_epoch : 最后一个epoch的index,如果是训练了很多个epoch后中断了,继续训练,这个值就等于加载的模型的epoch。
默认为-1表示从头开始训练,即从epoch=1开始。
理论过程:
和StepLR方法不同,ExponetialLR每个epoch都需要重新计算学习率。
其中new_lr是得到的新的学习率,initial_lr是初始的学习率,γ是参数gamma。
实例:
感兴趣的同学可以用以下代码进行实例验证:
import torch
import torch.nn as nn
from torch.optim.lr_scheduler import ExponentialLR
import itertools
initial_lr = 0.1
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3)
def forward(self, x):
pass
net_1 = model()
optimizer_1 = torch.optim.Adam(net_1.parameters(), lr = initial_lr)
scheduler_1 = ExponentialLR(optimizer_1, gamma=0.1)
print("初始化的学习率:", optimizer_1.defaults['lr'])
for epoch in range(1, 11):
# train
optimizer_1.zero_grad()
optimizer_1.step()
print("第%d个epoch的学习率:%f" % (epoch, optimizer_1.param_groups[0]['lr']))
scheduler_1.step()
结果:
初始化的学习率: 0.1
第1个epoch的学习率:0.100000
第2个epoch的学习率:0.010000
第3个epoch的学习率:0.001000
第4个epoch的学习率:0.000100
第5个epoch的学习率:0.000010
第6个epoch的学习率:0.000001
第7个epoch的学习率:0.000000
第8个epoch的学习率:0.000000
第9个epoch的学习率:0.000000
第10个epoch的学习率:0.000000
2.4 torch.optim.lr_scheduler.MultiStepLR方法
torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones, gamma = 0.1, last_epoch = -1)
参数解释:
optimizer : 要更改学习率的优化器;
milestones : 递增的list,存放要更新lr的epoch;
gamma : 更新lr的乘法因子;
last_epoch : 最后一个epoch的index,如果是训练了很多个epoch后中断了,继续训练,这个值就等于加载的模型的epoch。
默认为-1表示从头开始训练,即从epoch=1开始。
理论过程:
每次遇到milestones序列中的epoch,做一次更新。
其中new_lr是得到的新的学习率,initial_lr是初始的学习率,γ是参数gamma, bisect_right(milestones, epoch)就是bisect模块中的bisect_right函数,返回值是把epoch插入排序好的列表milestones式的位置。
实例:
如果上述过程比较难懂,咱们直接上手用实际代码来理解此方法:
import torch
import torch.nn as nn
from torch.optim.lr_scheduler import MultiStepLR
import itertools
initial_lr = 0.1
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3)
def forward(self, x):
pass
net_1 = model()
optimizer_1 = torch.optim.Adam(net_1.parameters(), lr = initial_lr)
scheduler_1 = MultiStepLR(optimizer_1, milestones=[3, 7], gamma=0.1)
print("初始化的学习率:", optimizer_1.defaults['lr'])
for epoch in range(1, 11):
# train
optimizer_1.zero_grad()
optimizer_1.step()
print("第%d个epoch的学习率:%f" % (epoch, optimizer_1.param_groups[0]['lr']))
scheduler_1.step()
结果:可以看到,第4个和第8个epoch开始变化,其他的epoch时就保持不变。
初始化的学习率: 0.1
第1个epoch的学习率:0.100000
第2个epoch的学习率:0.100000
第3个epoch的学习率:0.100000
第4个epoch的学习率:0.010000
第5个epoch的学习率:0.010000
第6个epoch的学习率:0.010000
第7个epoch的学习率:0.010000
第8个epoch的学习率:0.001000
第9个epoch的学习率:0.001000
第10个epoch的学习率:0.001000
2.5 torch.optim.lr_scheduler.ReduceLROnPlateau方法
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)
参数解释:
optimizer : 要更改学习率的优化器;
mode : 只能是‘min’或'max',默认’min':
‘min'模式下,当metric不再下降时减小lr;
'max'模式下,当metric不再增长时减小lr;
factor : lr减小的乘法因子,默认为0.1;
patience : 在metric停止优化patience个epoch后减小lr,例如,如果patience=2,那metric不再优化的前两个epoch不做任何事,第三个epoch后metric仍然没有优化,那么更新lr,默认为10;
verbose : 如果为True,在更新lr后print一个更新信息,默认为False;
理论过程: 该方法是不依赖epoch来更新lr学习率的。给定一个metric,当metric停止优化时减小学习率。也就是在发现loss不再降低或者acc不再提高之后,降低学习率。
其中new_lr是得到的新的学习率,old_lr是上一次优化使用的学习率,λ是通过参数factor。
实例:
import torch
import torch.nn as nn
from torch.optim.lr_scheduler import ReduceLROnPlateau
import itertools
initial_lr = 0.1
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3)
def forward(self, x):
pass
net_1 = model()
optimizer_1 = torch.optim.Adam(net_1.parameters(), lr = initial_lr)
scheduler_1 = ReduceLROnPlateau(optimizer_1, mode='min', factor=0.1, patience=2)
print("初始化的学习率:", optimizer_1.defaults['lr'])
for epoch in range(1, 15):
# train
test = 2
optimizer_1.zero_grad()
optimizer_1.step()
print("第%d个epoch的学习率:%f" % (epoch, optimizer_1.param_groups[0]['lr']))
scheduler_1.step(test)
结果输出:
初始化的学习率: 0.1
第1个epoch的学习率:0.100000
第2个epoch的学习率:0.100000
第3个epoch的学习率:0.100000
第4个epoch的学习率:0.100000
第5个epoch的学习率:0.010000
第6个epoch的学习率:0.010000
第7个epoch的学习率:0.010000
第8个epoch的学习率:0.001000
第9个epoch的学习率:0.001000
第10个epoch的学习率:0.001000
第11个epoch的学习率:0.000100
第12个epoch的学习率:0.000100
第13个epoch的学习率:0.000100
第14个epoch的学习率:0.000010
可以看到后面的epoch每隔2个发现没有变化之后,就开始降低学习率来优化模型。
2.6 torch.optim.lr_scheduler.CosineAnnealingLR方法
torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max, eta_min = 0, last_epoch = -1)
参数解释:
optimizer : 要更改学习率的优化器;
T_max : lr的变化是周期性的,T_max是周期的1/4;
eta_min : lr的最小值,默认为0;
last_epoch : 最后一个epoch的index,如果是训练了很多个epoch后中断了,继续训练,这个值就等于加载的模型的epoch。默认为-1表示从头开始训练,即从epoch=1开始
理论过程:
让lr随着epoch的变化图类似于cos,其中new_lr是得到的新的学习率,initial_lr是初始的学习率,eta_min表示最小学习率,T_max表示cos的周期的1/4。
实例:
import torch
import torch.nn as nn
from torch.optim.lr_scheduler import CosineAnnealingLR
import itertools
import matplotlib.pyplot as plt
initial_lr = 0.1
class model(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(in_channels=3, out_channels=3, kernel_size=3)
def forward(self, x):
pass
net_1 = model()
optimizer_1 = torch.optim.Adam(net_1.parameters(), lr = initial_lr)
scheduler_1 = CosineAnnealingLR(optimizer_1, T_max=20)
print("初始化的学习率:", optimizer_1.defaults['lr'])
lr_list = [] # 把使用过的lr都保存下来,之后画出它的变化
for epoch in range(1, 101):
# train
optimizer_1.zero_grad()
optimizer_1.step()
print("第%d个epoch的学习率:%f" % (epoch, optimizer_1.param_groups[0]['lr']))
lr_list.append(optimizer_1.param_groups[0]['lr'])
scheduler_1.step()
# 画出lr的变化
plt.plot(list(range(1, 101)), lr_list)
plt.xlabel("epoch")
plt.ylabel("lr")
plt.title("learning rate's curve changes as epoch goes on!")
plt.show()
结果如下所示:
当然还有一些其他的动态学习率的函数,如:
torch.optim.lr_scheduler.CyclicLR
torch.optim.lr_scheduler.OneCycleLR
torch.optim.lr_scheduler.CosineAnnealingWarmRestarts
等等,可自行查阅相关资料。