- torch.optim是一个实现多种优化算法的包;
- 很多常用的方法已经被支持;
- 接口丰富;
- 容易整合更为复杂的算法;
如何使用一个优化器
- 为了使用torch.optim包功能;
- 用户必须构建一个优化器对象;
- 该优化器将保持当前的参数状态且基于计算的梯度更新参数;
构建优化器
- 要构建一个优化器;
- 必须给优化器一个可迭代的对象;
- 该对象包含可优化的参数(应当是变量s);
- 然后,用户可以指定具体的优化器参数,比如学习率,权重衰减等;
import torch.optim as optim
optimizer=optim.SGD(model.parameters(),lr=0.01,momentum=0.9)
optimizer=optim.Adam([var1,var2],lr=0.0001)
单一参数设置
- 优化器也支持每一个参数的设置;
- 为了这样做,不要给优化器传入一个可迭代的变量s;
- 而是给优化器传入一个可迭代的字典s;
- 每个字典将定义一个分离的参数组;
- 参数组内应当包含一个参数键,该参数键包含一个属于他的参数列表;
- 其他键应当匹配优化器可接受的关键字参数;
- 且该键将被用作这个组内的优化选项;
- 依然可以传递选项作为关键字参数,他们将被用于默认参数;
- 组内对他们并不覆写;
- 当用户想变化单一的选项时会很有用;
- 同时保持其他的参数组一致;
- 比如,当想指定每一层的学习率时:
optim.SGD([{'params':model.base.parameters()},
{'params':model.classifier.parameters(),'lr':1e-3}],lr=1e-2,momentum=0.9)
- 上述代码意味着model.base的参数将使用默认的学习率:;
- model.classifier的参数将使用的学习率;
- momentum=0.9将会被所有的参数使用;
优化步骤
- 所有的优化器都实现一个step()方法;
- 该方法对参数进行更新;
- 有两种使用方式:
- optimizier.step()
- optimizer.step(closure)
optimizer.step()
- 大多数优化器都支持的一个简单版本用法;
- 当使用backward()计算梯度后调用该方法;
for input,target in dataset:
optimizer.zero_grad()
output=model(input)
loss=loss_fn(output,target)
loss.backward()
optimizer.step()
optimizer.step(closure)
- 一些优化算法,比如共轭梯度和LBFGS;
- 需要多次评估该函数;
- 用户必须传递closure参数以允许算法重新计算模型;
- closure参数应当清理梯度,计算损失,并返回;
for input,target in dataset:
def closure():
optimizer.zero_grad()
output=model(input)
loss=loss_fn(output,target)
loss.backward()
optimizer.step(closure)
基础类
- 类 torch.optim.Optimizer(params,defaults)
- 是所有优化器的基础类;
- 必须以集合的方式指定参数;
- 集合内的参数具有确定的顺序且同实际运行中的一致;
- 不满足要求的是set和字典键值迭代器;
- params(iterable)---一个可迭代的torch.Tensor或者字典,指定需要优化的张量类型;
- defaults(Dict[str,Any])---具有默认优化选项值得字典(当参数组没有指定时使用);
算法
- 很多算法对于优化性能\可读性和通用性具有不同的实现;
- 如果用户没有特别的指定算法的实现方法,默认情况下针对用户设备尝试最快的实现方法;
- 有三大类主要的实现:for-loop,foreach(多张量),fused;
- 最直接的是对参数的for-loop循环实现,并进行大量的计算;
- for-loop实现通常较foreach实现更慢,foreach实现一次合并参数到多张量中且进行大量计算;
- foreach实现节省了很多序列化的内核调用;
- 一些优化器具有更快速的融合实现;
- 这些优化器融合大量的计算到一个内核中;
- 我们可以认为foreach实现是水平融合,融合实现是foreach实现水平融合的垂直融合;
- 一般来讲,三大类实现的性能排序为:fused>foreach>for-loop;
- 应用时,默认情况下优先采用foreach;
- 可应用意味着foreach实现是可用的,用户没有指定任何实现的细节参数(比如fused,foreach,differentiable),且所有张量是本地的在CUDA上;
- 注意,虽然融合的实现较foreach实现应当更快;
- 但这些实现是比较新的,在任何地方应用之前应该具有更多的实验时间;
- 欢迎大家尝试;
目前算法的状态
如何调整学习率
- torch.optim.lr_scheduler 提供一些方法基于训练的代数调整学习率;
- torch.optim.lr_scheduler.ReduceLROnPlateau 基于一些验证测量允许动态的减少学习率;
- 学习率调度应当在优化器更新后再应用;
optimizer=optim.SGD(model.parameters(),lr=0.01,momentum=0.9)
scheduler=ExponentialLR(optimizer,gamma=0.9)
for epoch in range(20):
for input,target in dataset:
optimizer.zero_grad()
output=model(input)
loss=loss_fn(output,target)
loss.backward()
optimizer.step()
scheduler.step()
- 很多学习率调度器被称为背靠背调度器(也成为链式调度器);
- 结果是调度器被一个个的应用到另一个之前的调度器获取到的学习率上;
optimizer=optim.SGD(model.parameters(),lr=0.01,momentum=0.9)
scheduler1=ExponentialLR(optimizer,gamma=0.9)
scheduler2=MultiStepLR(optimizer,milestones=[30,80],gamma=0.1)
for epoch in range(20):
for input,target in dataset:
optimizer.zero_grad()
output=model(input)
loss=loss_fn(output,target)
loss.backward()
optimizer.step()
scheduler1.step()
scheduler2.step()
注意
- 在1.1.0版本之前,学习率调度器被期望在优化器更新之前调用;
- 1.1.0版本改变了这一特性;
- 如果用户在优化器更新之前使用了学习率调度器;
- 将忽略学习率调度器中的第一个值;
- 如果在更新PyTorch1.1.0之后无法重新生成结果;
- 请检查是否在错误的位置调用了学习率调度器;
权值平均(SWA和EMA)
- torch.optim.swa.utils 实现随机权值平均(SWA)和指数移动平均(EMA);
- 特别的torch.optim.swa_utils.AveragedModel类实现SWA和EMA模型;
- torch.optim.swa_utils.SWALR实现SWA学习率调度器;
- torch.optim.swa_utils.update_bn()是一个工具函数用于在训练结束时更新SWA/EMA批次标准化统计;
- SWA在Averaging Weights Leads to Wider Optima and Better Generalization.中被提出;
- EMA是一个通过减少需要更新的权重的数量减少训练时间的广为人知的技术;
- EMA是一个Polyak averaging的变体,但是在迭代中使用等权重而不是指数权重;
构建平均模型
- AveragedModel类服务于计算SWA和EMA模型的权重;
- 创建SWA平均模型:
averaged_model=AveragedModel(model)
- 通过指定multi_avg_fn参数构建EMA模型:
decay=0.999
averaged_model=AveragedModel(model,multi_avg_fn=get_ema_multi_avg_fn(decay))
- decay是一个在0和1之间的参数,控制平均化的参数以多快的速度衰减;
- 如果decay参数没有提供给get_ema_multi_avg_fn,默认值为0.999;
- get_ema_multi_avg_fn返回一个应用以下EMA权重公式的函数:
- 这里,是EMA衰减因子,model可以是任何的torch.nn.Moudle对象;
- averaged_model将保持追踪运行中的模型的参数均值化;
- 为了更新这些均值,用户应当使用update_parameters()函数在optimizer.step()之后;
averaged_model.update_parameters(model)
- 对于SWA和EMA,该函数的调用通常紧随optimizer step()函数;
- 在SWA中,在训练起始的一些次数下被略过;
制定均值策略
- 默认情况下,torch.optim.swa_utils.AveragedModel计算一个用户提供的参数的运行等效均值;
- 但是用户可以使用定制的均值函数,使用avg_fn或者multi_avg_fn参数;
- avg_fn允许定义一个函数操作每一个参数元组(平均的参数,模型参数)且应当返回平均的参数;
- multi_avg_fn允许定义对元组参数列表(均值的参数列表,模型参数列表)的更有效的操作;
- 同时,比如使用torch._foreach* 函数,该函数必须在位更新均值化的参数;
- 下例中ema_model计算一个指数移动平均使用avg_fn参数:
import torch.optim.swa_utils
ema_avg=lambda averaged_model_parameter,model_parameter,
num_averaged:0.9*averaged_model_parameter+0.1*model_parameter
ema_model=torch.optim.swa_utils.AveragedModel(model,avg_fn=ema_avg)
- 以下的实例ema_model计算一个指数移动平均使用更为高效的multi_avg_fn参数:
ema_model=AveragedModel(model,multi_avg_fn=get_ema_multi_avg_fn(0.9))
swa学习率计划
- 通常,SWA中学习率被设置为一个大的常量数值;
- SWALR是一个学习率调度器,将学习率调整到一个固定的值,并保持为常量;
- 比如以下实例代码创建一个调度器线性调整学习率在5代训练的每个参数组中从初始值到0.05;
swa_scheduler=torch.optim.swa_utils.SWALR(optimizer,
anneal_strategy="linear",
anneal_epochs=5,swa_lr=0.05)
- 可以使用余弦退火到一个固定的学习率值而不是使用线性退火通过设置annel_strategy='cos';
关注批量规范化
- update_bn()是一个有用的函数允许计算SWA模型在一个给定加载器loader中的批规范统计,在训练的末尾;
torch.optim.swa_utils.update_fn(loader,swa_model)
- update_bn()应用swa_model模型到数据加载器中的每一个单元;
- 且在模型中每一个批标准化层计算激活数据;
- update_fn()假设数据加载器loader中的每一批不是张量就是张量列表;
- 且张量或张量列表中的第一个元素是网络swa_model应当应用到的张量;
- 如果用户的加载器具有不同的结构;
- 可以更新swa_model模型的批标准化数据;
- 通过对数据集的每个元素使用swa_model做前向传递;
SWA综述
- 以下实例,swa_model是一个累积权重均值的SWA模型;
- 对模型训练300代,调整学习率计划,在训练代160时手机SWA参数的均值:
import torch
loader,optimizer,model,loss_fn=...
swa_model=torch.opim.swa_utils.AveragedModel(model)
scheduler=torch.optim.lr_scheduler.CosineAnnealingLR(optimizer,T_max=300)
swa_start=160
swa_scheduler=SWALR(optimizer,swa_lr=0.05)
for epoch in range(300):
for input,target_in_loader:
optimizer.zero_grad()
loss_fn(model(input),target).backward()
optimizer.step()
if epoch>swa_start:
swa_model.update_parameters(model)
swa_scheduler.step()
else:
scheduler.step()
torch.optim.swa_utils.update_bn(loader,swa_model)
preds=swa_model(test_input)
EMA综述
- 以下实例中,ema_model是一个EMA模型的实例;
- 该实例累积权重均值的指数衰减;
- 衰减率为0.999;
- 训练模型300代,从训练一开始就收集EMA均值;
import torch
loader,optimizer,model,loss_fn=...
ema_model=torch.opim.swa_utils.AveragedModel(model,
multi_avg_fn=torch.optim.swa_utils.get_ema_multi_avg_fn(0.999))
for epoch in range(300):
for input,target_in_loader:
optimizer.zero_grad()
loss_fn(model(input),target).backward()
optimizer.step()
ema_model.update_parameters(model)
torch.optim.swa_utils.update_bn(loader,ema_model)
preds=swa_model(test_input)