当数据、模型和损失函数确定,任务的数学模型就已经确定,接着就要选择一个合适的优化器(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 循环当中执行。