torch.optim

torch.optim 是一个包含多种优化算法的包。

大多数常用方法已经得到了支持,接口设计得相当通用,因此未来可以轻松地集成更复杂的方法。

如何使用优化器

要使用torch.optim,你需要创建一个优化器对象来保存当前状态,并根据计算出的梯度更新参数。

构建

要构建一个Optimizer,你需要提供一个包含参数(所有参数都应该是Variables)的可迭代对象。然后,你可以指定特定优化器的选项,如学习率、权重衰减等。

示例:

optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
optimizer = optim.Adam([var1, var2], lr=0.0001)

参数选项

Optimizer 还支持为每个参数指定不同的选项。为此,不要传递 Variable 的可迭代对象,而是传入一个字典的可迭代对象。每个字典定义一个单独的参数组,并应包含一个 params 键,其中列出该组中的参数。其他键应该与优化器接受的关键字参数匹配,并将用作此组的优化选项。

例如,当你想为每一层设置不同的学习率时,这非常有用。

optim.SGD([
                {'params': model.base.parameters(), 'lr': 1e-2},
                {'params': model.classifier.parameters()}
            ], lr=1e-3, momentum=0.9)

这意味着 model.base 的参数将使用学习率为 1e-2,而 model.classifier 的参数将保持默认的学习率 1e-3。最后,所有参数都将采用动量值为 0.9

注意

你仍然可以将选项作为关键字参数传递。它们会在没有被覆盖的组中作为默认值使用。当你只想更改一个选项,同时保持其他所有选项在各个参数组中的设置不变时,这一点非常有用。

请考虑与参数区别惩罚相关的以下示例。记得parameters() 返回一个包含所有可学习参数(包括偏置和其他可能需要不同惩罚的参数)的可迭代对象。为了解决这个问题,可以为每个参数组指定单独的惩罚权重:

bias_params = [p for name, p in self.named_parameters() if 'bias' in name]
others = [p for name, p in self.named_parameters() if 'bias' not in name]

optim.SGD([
                {'params': others},
                {'params': bias_params, 'weight_decay': 0}
            ], weight_decay=1e-2, lr=1e-2)

通过这种方式,偏差项和非偏差项被分开处理,并且对偏差项特别设置 weight_decay0,以确保不对它们施加任何惩罚。

进行优化步骤

所有优化器都实现了 step() 方法,用于更新参数。此方法可以有两种用法:

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)需要多次重新评估函数,因此你需要传递一个闭包,让这些算法可以重新计算你的模型。这个闭包应包括清空梯度、计算损失并返回损失值的步骤。

示例:

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)

基类

torch.optim.Optimizer(params, defaults)[源代码]

所有优化器的基类。

警告

参数需要被指定为具有固定顺序的集合,并且这个顺序在不同的运行过程中要保持一致。例如,集合和字典值的迭代器就不符合这样的要求。

参数
  • params (iterable) – 一个包含 torch.Tensordict 的可迭代对象。用于指定需要优化的张量。

  • defaults (Dict[str, Any]) – (字典): 包含优化选项默认值的字典,当参数组未指定这些值时使用。

Optimizer.add_param_group

Optimizerparam_groups中添加一个参数组。

Optimizer.load_state_dict

加载优化器的状态。

Optimizer.register_load_state_dict_pre_hook

注册一个在调用 load_state_dict() 之前被调用的预处理钩子。该钩子应具有以下签名::。

Optimizer.register_load_state_dict_post_hook

注册一个在调用load_state_dict()之后触发的load_state_dict后处理函数。它应该具有以下签名::。

Optimizer.state_dict

dict形式返回优化器的状态。

Optimizer.register_state_dict_pre_hook

注册一个在调用 state_dict() 之前的预处理钩子。

Optimizer.register_state_dict_post_hook

注册一个状态字典的后置钩子,在调用state_dict()之后会被调用。

Optimizer.step

执行一个优化步骤来更新参数。

Optimizer.register_step_pre_hook

注册一个在优化器步骤执行前被调用的预处理钩子。

Optimizer.register_step_post_hook

注册一个在优化器步骤之后调用的后处理钩子。

Optimizer.zero_grad

将所有优化的torch.Tensor的梯度重置。

算法

Adadelta

实现 Adadelta 算法。

Adafactor

实现Adafactor算法。

Adagrad

实现Adagrad算法。

Adam

实现Adam算法。

AdamW

实现AdamW算法。

SparseAdam

SparseAdam 实现了适合稀疏梯度的带掩码的 Adam 算法。

Adamax

实现基于无穷范数的Adam变体——Adamax算法。

ASGD

实现平均 stochastic gradient descent。

LBFGS

实现L-BFGS算法。

NAdam

实现NAdam算法。

RAdam

实现RAdam算法。

RMSprop

实现RMSprop算法。

Rprop

实现弹性反向传播算法。

SGD

实现带选项动量的随机梯度下降。

我们的许多算法有多重实现方式,分别针对性能、可读性和通用性进行了优化。如果没有特别指定,我们将默认选择在当前设备上运行速度最快的那种实现方式。

我们有三大类实现:for循环、foreach(多张量)和融合。最直接的是针对参数的大块计算进行for循环。然而,for循环通常比我们的foreach实现慢,后者将参数组合成一个多张量,并一次性运行大块计算,从而节省了许多顺序内核调用的开销。我们的一些优化器甚至有更快的融合实现,这些实现将大块计算融合到一个内核中。我们可以认为foreach实现是横向融合,而融合实现则是在此基础上进行纵向融合。

一般来说,三种实现的性能排序为:融合 > foreach > 循环。因此,在适用的情况下,默认选择foreach而不是循环。适用的情况是指foreach实现可用,用户没有指定任何特定于实现的关键字参数(例如,融合、foreach、可微分),并且所有张量都是原生的。需要注意的是,虽然融合应该比foreach更快,但这些实现较新,我们希望在全面验证其稳定性之前不要急于切换到它们。我们在下面的第二个表格中总结了每种实现的稳定性状态,欢迎您尝试使用它们!

下表列出了每种算法的可用实现及其默认值:

算法

默认值

有foreach吗?

已经融合了吗?

Adadelta

forEach

Adafactor

for 循环

Adagrad

forEach

是的(仅使用CPU)

Adam

forEach

AdamW

forEach

SparseAdam

for 循环

Adamax

forEach

ASGD

forEach

LBFGS

for 循环

NAdam

forEach

RAdam

forEach

RMSprop

forEach

Rprop

forEach

SGD

forEach

下表展示了融合实现的稳定性状态:

算法

中央处理器

CUDA

MPS

Adadelta

不受支持

不受支持

不受支持

Adafactor

不受支持

不受支持

不受支持

Adagrad

beta

不受支持

不受支持

Adam

beta

稳定

beta

AdamW

beta

稳定

beta

SparseAdam

不受支持

不受支持

不受支持

Adamax

不受支持

不受支持

不受支持

ASGD

不受支持

不受支持

不受支持

LBFGS

不受支持

不受支持

不受支持

NAdam

不受支持

不受支持

不受支持

RAdam

不受支持

不受支持

不受支持

RMSprop

不受支持

不受支持

不受支持

Rprop

不受支持

不受支持

不受支持

SGD

beta

beta

beta

调整学习率的方法

torch.optim.lr_scheduler.LRScheduler 提供了几种方法,可以根据训练轮数来调整学习率。而 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()

在多处文档中,我们将使用以下模板来引用调度算法。

>>> scheduler = ...
>>> for epoch in range(100):
>>>     train(...)
>>>     validate(...)
>>>     scheduler.step()

警告

在 PyTorch 1.1.0 之前,学习率调度器需要在优化器更新之前被调用;而从 1.1.0 版本开始,这一行为以破坏向后兼容性的方式进行了更改。如果你在调用 optimizer.step() 之前调用了学习率调度器(即调用 scheduler.step()),这将导致跳过学习率调度的第一个值。因此,如果你在升级到 PyTorch 1.1.0 后无法重现结果,请检查是否在错误的时间调用了 scheduler.step()

lr_scheduler.LRScheduler

在优化过程中调节学习率。

lr_scheduler.LambdaLR

设定初始学习率。

lr_scheduler.MultiplicativeLR

将每个参数组的学习率乘以指定函数提供的因子。

lr_scheduler.StepLR

每隔step_size个纪元,将每个参数组的学习率乘以gamma进行衰减。

lr_scheduler.MultiStepLR

当达到某个里程碑时,每个参数组的学习率将以 gamma 的比例进行衰减。

lr_scheduler.ConstantLR

将每个参数组的学习率乘以一个较小的常数因子。

lr_scheduler.LinearLR

通过一个小的乘法因子的线性变化来衰减每个参数组的学习率。

lr_scheduler.ExponentialLR

每轮(epoch)将每个参数组的学习率乘以gamma进行衰减。

lr_scheduler.PolynomialLR

根据给定的总迭代次数,使用多项式函数来衰减每个参数组的学习率。

lr_scheduler.CosineAnnealingLR

采用余弦退火调度来设置每个参数组的学习率。

lr_scheduler.ChainedScheduler

将一组学习率调度器进行链式组合。

lr_scheduler.SequentialLR

包含了在优化过程中预期依次调用的一系列调度器。

lr_scheduler.ReduceLROnPlateau

当某个指标不再改善时,降低学习率。

lr_scheduler.CyclicLR

按照循环学习率策略 (CLR) 设置每个参数组的学习率。

lr_scheduler.OneCycleLR

按照1cycle学习率策略设置每个参数组的学习率。

lr_scheduler.CosineAnnealingWarmRestarts

采用余弦退火调度来设置每个参数组的学习率。

权重平均(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(指数移动平均)是一种通过减少所需的权重更新次数来缩短训练时间的广泛技术。它是Polyak 平均的一种变体,但在迭代过程中使用的是指数加权而非等权重。

构建平均模型

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))

衰减是一个介于0和1之间的参数,用于控制平均参数的衰减速率。如果不提供给torch.optim.swa_utils.get_ema_multi_avg_fn(),默认值为0.999。

torch.optim.swa_utils.get_ema_multi_avg_fn() 返回一个函数,用于将以下指数移动平均(EMA)公式应用到权重上。

$W^\textrm{EMA}_{t+1} = \alpha W^\textrm{EMA}_{t} + (1 - \alpha) W^\textrm{model}_t$

其中 alpha 代表 EMA 的衰减率。

这里的模型 model 可以是任意一个torch.nn.Module 对象。averaged_model 会跟踪 model 参数的运行平均值。要更新这些平均值,你应该在调用optimizer.step()之后使用 update_parameters() 函数:

>>> averaged_model.update_parameters(model)

对于SWA和EMA,这个调用通常紧随优化器的step()操作之后执行。而在SWA中,训练初期会跳过若干步不进行此操作。

自定义平均策略

默认情况下,torch.optim.swa_utils.AveragedModel 会计算你提供的参数的运行平均值。不过,你也可以通过 avg_fnmulti_avg_fn 参数使用自定义的平均函数。

  • avg_fn 允许定义一个函数,该函数对每个参数元组(平均参数和模型参数)进行操作,并返回新的平均参数。

  • multi_avg_fn 允许定义作用于参数列表元组(包括平均参数列表和模型参数列表)的更高效操作,例如使用 torch._foreach* 函数。此函数必须就地更新平均参数。

在以下示例中,ema_model 使用 avg_fn 参数来计算指数移动平均值。

>>> 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 是一种学习率调度器,它会将学习率调整到固定值并保持不变。例如,以下代码创建了一个在每个参数组内从初始值线性降低学习率至0.05的调度器,并且这个过程会在5个epochs内完成:

>>> swa_scheduler = torch.optim.swa_utils.SWALR(optimizer, \
>>>         anneal_strategy="linear", anneal_epochs=5, swa_lr=0.05)

你也可以通过设置 anneal_strategy="cos" 来使用余弦退火而非线性退火,并将其调整为固定值。

处理批量归一化

update_bn() 是一个实用函数,在训练结束时,它使用给定的数据加载器 loader 为 SWA 模型计算批处理规范化统计信息。

>>> torch.optim.swa_utils.update_bn(loader, swa_model)

update_bn()swa_model 应用到数据加载器中的每一个元素,并计算模型中每个批处理规范化层的激活统计信息。

警告

update_bn() 假设数据加载器 loader 中的每个批次要么是一个张量,要么是包含多个张量的列表,并且第一个张量是要应用到网络 swa_model 上的。如果你的数据加载器具有不同的结构,可以通过在数据集的每个元素上使用 swa_model 进行前向传递来更新其批量归一化统计信息。

综合介绍:SWA

在下面的例子中,swa_model 是一个累积权重平均值的SWA模型。我们总共训练300个 epochs,并从第160个 epoch 开始切换到 SWA 学习率计划并开始收集参数的 SWA 平均值。

>>> loader, optimizer, model, loss_fn = ...
>>> swa_model = torch.optim.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()
>>>
>>> # Update bn statistics for the swa_model at the end
>>> torch.optim.swa_utils.update_bn(loader, swa_model)
>>> # Use swa_model to make predictions on test data
>>> preds = swa_model(test_input)

综合:EMA

在下面的例子中,ema_model 是一个 EMA 模型,它以 0.999 的衰减率累积权重的指数衰减平均值。我们总共训练该模型 300 个周期,并立即开始收集 EMA 平均值。

>>> loader, optimizer, model, loss_fn = ...
>>> ema_model = torch.optim.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)
>>>
>>> # Update bn statistics for the ema_model at the end
>>> torch.optim.swa_utils.update_bn(loader, ema_model)
>>> # Use ema_model to make predictions on test data
>>> preds = ema_model(test_input)

swa_utils.AveragedModel

实现随机权重平均(SWA)和指数移动平均(EMA)的模型。

swa_utils.SWALR

将每个参数组的学习率调整(退火)到一个固定值。

torch.optim.swa_utils.get_ema_multi_avg_fn(decay=0.999)[源代码]

获取用于多个参数的指数移动平均(EMA)函数。

torch.optim.swa_utils.update_bn(loader, model, device=None)[源代码]

更新模型中 BatchNorm 的运行均值(running_mean)和运行方差(running_var)缓存。

它会遍历loader中的数据一次,以估算模型中BatchNorm层的激活统计信息。

参数
  • loader (torch.utils.data.DataLoader) – 用于计算激活统计信息的数据集加载器。每个数据批次应该是一个张量,或者一个其第一个元素为包含数据的张量的列表或元组。

  • model (torch.nn.Module) – 用于更新批量归一化统计信息的模型。

  • device (torch.device, 可选) – 如果设置了设备,数据将在传递给 model 之前被转移到指定的 device

示例

>>> loader, model = ...
>>> torch.optim.swa_utils.update_bn(loader, model)

注意

update_bn 工具假设 loader 中的每个数据批次要么是一个张量,要么是张量的列表或元组。如果是后者,则应调用 model.forward() 来处理对应于数据批次的第一个元素。

本页目录