PyTorch 模型训练实用教程(五):优化器基类/十种优化器/六个学习率调整⽅法

当数据、模型和损失函数确定,任务的数学模型就已经确定,接着就要选择一个合适的优化器(Optimizer)对该模型进行优化。

优化器基类 Optimizer

PyTorch 中所有的优化器(如:optim.Adadelta、optim.SGD、optim.RMSprop 等)均是 Optimizer 的子类,Optimizer 中定义了一些常用的方法,有 zero_grad()、 step(closure)、state_dict()、load_state_dict(state_dict)和 add_param_group(param_group),本节将会一一介绍。

参数组(param_groups)的概念

认识 Optimizer 的方法之前,需要了解一个概念,叫做参数组(param_groups)。在finetune,某层定制学习率,某层学习率置零操作中,都会涉及参数组的概念,因此首先 了解参数组的概念非常有必要。
optimizer 对参数的管理是基于组的概念,可以为每一组参数配置特定的lr,momentum,weight_decay 等等。
参数组在 optimizer 中表现为一个 list(self.param_groups),其中每个元素是dict,表示一个参数及其相应配置,在 dict 中包含'params'、'weight_decay'、'lr' 、 'momentum'等字段。
# coding: utf-8

import torch
import torch.optim as optim


w1 = torch.randn(2, 2)
w1.requires_grad = True

w2 = torch.randn(2, 2)
w2.requires_grad = True

w3 = torch.randn(2, 2)
w3.requires_grad = True

# 一个参数组
optimizer_1 = optim.SGD([w1, w3], lr=0.1)
print('len(optimizer.param_groups): ', len(optimizer_1.param_groups))
print(optimizer_1.param_groups, '\n')

# 两个参数组
optimizer_2 = optim.SGD([{'params': w1, 'lr': 0.1},
                         {'params': w2, 'lr': 0.001}])
print('len(optimizer.param_groups): ', len(optimizer_2.param_groups))
print(optimizer_2.param_groups)

zero_grad()

功能:将梯度清零。
由于 PyTorch 不会自动清零梯度,所以在每一次更新前会进行此操作。

state_dict()

功能:获取模型当前的参数,以一个有序字典形式返回。
这个有序字典中,key 是各层参数名,value 就是参数。

load_state_dict(state_dict)

功能:将 state_dict 中的参数加载到当前网络,常用于 finetune

add_param_group()

功能:给 optimizer 管理的参数组中增加一组参数,可为该组参数 定制 lr,momentum, weight_decay 等,在 finetune 中常用。
例如:optimizer_1.add_param_group({'params': w3, 'lr': 0.001, 'momentum':0.8})

step(closure)

功能:执行一步权值更新, 其中可传入参数 closure(一个闭包)。如,当采用 LBFGS优化方法时,需要多次计算,因此需要传入一个闭包去允许它们重新计算 loss
for input, target in dataset: 
    def closure(): 
    optimizer.zero_grad() 
    output = model(input) 
    loss = loss_fn(output, target) 
    loss.backward() 
    return loss 
optimizer.step(closure)

PyTorch 的⼗个优化器

上一节,介绍了所有优化器的基类——Optimizer 类,在 Optimizer 类中定义了 5 个实用的基本方法,虽然了解了 Optimizer 类,但是还无法构建一个能优化网络的优化器。
在本小节中,将会介绍 PyTorch 的十种优化器,有常见的 SGD、ASGD、Rprop、RMSprop、Adam 等等。这里都说的是***优化器,并没有提***优化方法,因为 PyTorch 中给出的优化器与原始论文中的优化方法,多多少少有改动,详细还需看优化器源码。

torch.optim.SGD

class torch.optim.SGD ( params , lr=<object> , momentum=0 , dampening=0 , weight_decay=0 , nesterov=False )
功能:
可实现 SGD 优化算法,带动量 SGD 优化算法,带 NAG(Nesterov accelerated gradient)动量 SGD 优化算法,并且均可拥有 weight_decay 项。
参数:
params(iterable)- 参数组,优化器要管理的那部分参数。
lr(float)- 初始学习率,可按需随着训练过程不断调整学习率。
momentum(float)- 动量,通常设置为 0.9,0.8
dampening(float)- dampening for momentum ,暂时不了其功能,在源码中是这样用 的:buf.mul_(momentum).add_(1 - dampening,d_p),值得注意的是,若采用 nesterov,dampening 必须为 0.
weight_decay(float)- 权值衰减系数,也就是 L2 正则项的系数
nesterov(bool)- bool 选项,是否使用 NAG(Nesterov accelerated gradient)
注意事项:
pytroch 中使用 SGD 十分需要注意的是,更新公式与其他框架略有不同!
PyTorch 中是这样的:
v=ρ v+g
p=p lr v = p - lr ρ v - lr g
其他框架:
v=ρ v+lr g
p=p v = p - ρ v - lr g
ρ 是动量,v 是速率,g 是梯度,p 是参数,其实差别就是在 ρ v 这一项,PyTorch 中将此项也乘了一个学习率。

torch.optim.ASGD

class torch.optim.ASGD ( params , lr=0.01 , lambd=0.0001 , alpha=0.75 , t0=1000000.0 , weight_decay=0 )
功能:
ASGD 也称为 SAG,均表示随机平均梯度下降( Averaged Stochastic Gradient Descent ),简单地说 ASGD 就是用空间换时间的一种 SGD,详细可参看论文:
http://riejohnson.com/rie/stograd_nips.pdf
参数:
params(iterable)- 参数组,优化器要优化的那些参数。
lr(float)- 初始学习率,可按需随着训练过程不断调整学习率。
lambd(float)- 衰减项,默认值 1e-4。
alpha(float)- power for eta update ,默认值 0.75。
t0(float)- point at which to start averaging ,默认值 1e6。
weight_decay(float)- 权值衰减系数,也就是 L2 正则项的系数。

torch.optim.Rprop

class torch.optim.Rprop ( params , lr=0.01 , etas=(0.5 , 1.2) , step_sizes=(1e-06 , 50) )
功能:
实现 Rprop 优化 方法 ( 弹性反向传播 ) ,优化方法原文《 Martin Riedmiller und HeinrichBraun: Rprop - A Fast Adaptive Learning Algorithm. Proceedings of the InternationalSymposium on Computer and Information Science VII, 1992
该优化方法适用于 full-batch 不适用于 mini-batch ,因而在 mini-batch 大行其道的时代
里,很少见到。

torch.optim.Adagrad

class torch.optim.Adagrad ( params , lr=0.01 , lr_decay=0 , weight_decay=0 , initial_accumulator_value=0 )
功能:
实现 Adagrad 优化方法( Adaptive Gradient ),Adagrad 是一种自适应优化方法,是自适应的为各个参数分配不同的学习率。这个学习率的变化,会受到梯度的大小和迭代次数的影响。梯度越大,学习率越小;梯度越小,学习率越大。缺点是训练后期,学习率过小,因为 Adagrad 累加之前所有的梯度平方作为分母。
详细公式请阅读:Adaptive Subgradient Methods for Online Learning and Stochastic Optimization John Duchi, Elad Hazan, Yoram Singer; 12(Jul):2121 2159,2011.( http://www.jmlr.org/papers/volume12/duchi11a/duchi11a.pdf )

torch.optim.Adadelta

class torch.optim.Adadelta ( params , lr=1.0 , rho=0.9 , eps=1e-06 , weight_decay=0 )
功能:
实现 Adadelta 优化方法。 Adadelta Adagrad 的改进。Adadelta 分母中采用距离当前时间点比较近的累计项,这可以避免在训练后期,学习率过小。
详细公式请阅读: https://arxiv.org/pdf/1212.5701.pdf

torch.optim.RMSprop

class torch.optim.RMSprop ( params , lr=0.01 , alpha=0.99 , eps=1e-08 , weight_decay=0 , momentum=0 , centered=False )
功能:
实现 RMSprop 优化方法(Hinton 提出),RMS 是均方根( root meam square )的意思。RMSprop 和 Adadelta 一样,也是对 Adagrad 的一种改进。RMSprop 采用均方根作为分母,可缓解 Adagrad 学习率下降较快的问题,并且引入均方根,可以减少摆动,详细了解 可读: http://www.cs.toronto.edu/~tijmen/csc321/slides/lecture_slides_lec6.pdf

torch.optim.Adam(AMSGrad)

class torch.optim.Adam ( params , lr=0.001 , betas=(0.9 , 0.999) , eps=1e-08 , weight_decay=0 , amsgrad=False )
功能:
实现 Adam(Adaptive Moment Estimation))优化方法。Adam 是一种自适应学习率的优化方法,Adam 利用梯度的一阶矩估计和二阶矩估计动态的调整学习率。吴老师课上说过,Adam 是结合了 Momentum 和 RMSprop,并进行了偏差修正。
参数:
amsgrad- 是否采用 AMSGrad 优化方法,asmgrad 优化方法是针对 Adam 的改进,通过添加额外的约束,使学习率始终为正值。(AMSGrad, ICLR-2018 Best-Pper 之一 ,《 On the convergence of Adam and Beyond 》)。
详细了解 Adam 可阅读,Adam: A Method for Stochastic Optimization ( https://arxiv.org/abs/1412.6980 )。

torch.optim.Adamax

class torch.optim.Adamax ( params , lr=0.002 , betas=(0.9 , 0.999) , eps=1e-08 , weight_decay=0 )
功能:
实现 Adamax 优化方法。Adamax 是对 Adam 增加了一个学习率上限的概念,所以也称之为 Adamax。
详细了解可阅读,Adam: A Method for Stochastic Optimization ( https://arxiv.org/abs/1412.6980 )(没错,就是 Adam 论文中提出Adamax)。

torch.optim.SparseAdam

class torch.optim.SparseAdam ( params , lr=0.001 , betas=(0.9 , 0.999) , eps=1e-08 )
功能:
针对稀疏张量的一种“阉割版”Adam 优化方法。
- only moments that show up in the gradient get updated, and only those portions of the gradient get applied to the parameters

torch.optim.LBFGS

class torch.optim.LBFGS ( params , lr=1 , max_iter=20 , max_eval=None , tolerance_grad=1e-05 , tolerance_change=1e-09 , history_size=100 , line_search_fn=None )
功能:
实现 L-BFGS(Limited-memory Broyden–Fletcher–Goldfarb–Shanno)优化方法。
L-BFGS 属于拟牛顿算法。L-BFGS 是对 BFGS 的改进,特点就是节省内存
使用注意事项:
1.This optimizer doesn’t support per-parameter options and parameter groups(there can be only one).
2. Right now all parameters have to be on a single device. This will be improved in the future.(2018-10-07)

PyTorch 的六个学习率调整⽅法

上一小节对十种优化器进行了介绍,可以发现优化器中最重要的一个参数就是学习率,合理的学习率可以使优化器快速收敛。一般在训练初期给予较大的学习率,随着训练的进行,学习率逐渐减小。学习率什么时候减小,减小多少,这就涉及到学习率调整方法。
PyTorch 中提供了六种方法供大家使用,下面将一一介绍,最后对学习率调整方法进行总结。

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 了。
参数:
step_size(int)- 学习率下降间隔数,若为 30,则会在 30、60、90......个 step 时,将学习率调整为 lr*gamma。
gamma(float)- 学习率调整倍数,默认为 0.1 倍,即下降 10 倍。
last_epoch(int)- 上一个 epoch 数,这个变量用来指示学习率是否需要调整。当 last_epoch 符合设定的间隔时,就会对学习率进行调整。当为-1 时,学习率设置为初始 值。

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 时,学习率设置为初始值。

 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 时,学习率设置为初始值。

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》(ICLR-2017): https://arxiv.org/abs/1608.03983
参数:
T_max(int)- 一次学习率周期的迭代次数,即 T_max 个 epoch 之后重新设置学习率。
eta_min(float)- 最小学习率,即在一个周期中,学习率最小会下降到 eta_min,默认值为 0。
学习率调整公式为:

可以看出是以初始学习率为最大学习率,以 2*Tmax 为周期,在一个周期内先下降,后上升。

 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==abs,并且 mode==min 时, dynamic_threshold = best - threshold
cooldown(int)- “ 冷却时间 ,当调整学习率之后,让学习率调整策略冷静一下,让模型再训练一段时间,再重启监测模式。
min_lr(float or list)- 学习率下限,可为 float ,或者 list ,当有多个参数组时,可用 list 进行设置。
eps(float)- 学习率衰减的最小值,当学习率变化小于 eps 时,则不调整学习率。

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。

学习率调整小结

PyTorch提供了六种学习率调整方法,可分为三大类,分别是
1. 有序调整;
2. 自适应调整;
3. 自定义调整。
第一类,依一定规律有序进行调整,这一类是最常用的,分别是等间隔下降(Step),按需设定下降间隔(MultiStep),指数下降(Exponential)和 CosineAnnealing。这四种方法的调整时机都是人为可控的,也是训练时常用到的。
第二类,依训练状况伺机调整,这就是 ReduceLROnPlateau 方法。该法通过监测某一指标的变化情况,当该指标不再怎么变化的时候,就是调整学习率的时机,因而属于自适应的调整。
第三类,自定义调整,Lambda。Lambda 方法提供的调整策略十分灵活,我们可以为不同的层设定不同的学习率调整方法,这在 fine-tune 中十分有用,我们不仅可为不同的层设定不同的学习率,还可以为其设定不同的学习率调整策略,简直不能更棒!

step 源码阅读

在 PyTorch 中,学习率的更新是通过 scheduler.step(),而我们知道影响学习率的一个重要参数就是 epoch,而 epoch 与 scheduler.step()是如何关联的呢?这就需要看源码了。
源码在 torch/optim/lr_scheduler.py,step()方法在_LRScheduler 类当中,该类作为所有学习率调整的基类,其中定义了一些基本方法,如现在要介绍的 step(),以及最常用的 get_lr(),不过 get_lr()是一个虚函数,均需要在派生类中重新定义函数。

看看 step() :

def step(self, epoch=None):

if epoch is None:
epoch = self.last_epoch + 1
self.last_epoch = epoch
for param_group, lr in zip(self.optimizer.param_groups, self.get_lr()):
param_group['lr'] = lr
函数接收变量 epoch,默认为 None,当为 None 时,epoch = self.last_epoch + 1。
从这里知道,last_epoch 是用以记录 epoch 的。上面有提到 last_epoch 的初始值是-1,因此,第一个 epoch 的值为 -1+1 =0。接着最重要的一步就是获取学习率,并更新。
由于 PyTorch 是基于参数组的管理方式,这里需要采用 for 循环对每一个参数组的学
习率进行获取及更新。这里需要注意的是 get_lr(),get_lr()的功能就是获取当前epoch,该参数组的学习率。 P
这里以 StepLR()为例,介绍 get_lr(),请看代码:
def get_lr(self):
return [base_lr * self.gamma ** (self.last_epoch // self.step_size) for base_lr in self.base_lrs]
由于 PyTorch 是基于参数组的管理方式,可能会有多个参数组,因此用 for 循环,返回的是一个 list。list 元素的计算方式为
base_lr * self.gamma ** (self.last_epoch // self.step_size)。
看完代码,可以知道,在执行一次 scheduler.step()之后,epoch 会加 1,因此scheduler.step()要放在 epoch 的 for 循环当中执行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值