15 KiB
自定义训练优化策略
在我们的算法库中,已经提供了通用数据集(如ImageNet,CIFAR)的默认训练策略配置。如果想要在这些数据集上继续提升模型性能,或者在不同数据集和方法上进行新的尝试,我们通常需要修改这些默认的策略。
在本教程中,我们将介绍如何在运行自定义训练时,通过修改配置文件进行构造优化器、参数化精细配置、梯度裁剪、梯度累计以及定制动量调整策略等。同时也会通过模板简单介绍如何自定义开发优化器和构造器。
配置训练优化策略
我们通过 optim_wrapper
来配置主要的优化策略,包括优化器的选择,混合精度训练的选择,参数化精细配置,梯度裁剪以及梯度累计。接下来将分别介绍这些内容。
构造 PyTorch 内置优化器
MMClassification 支持 PyTorch 实现的所有优化器,仅需在配置文件中,指定优化器封装需要的 optimizer
字段。
如果要使用 SGD
,则修改如下。这里要注意所有优化相关的配置都需要封装在 optim_wrapper
配置里。
optim_wrapper = dict(
type='OptimWrapper',
optimizer=dict(type='SGD', lr=0.0003, weight_decay=0.0001)
)
配置文件中的 'type' 不是构造时的参数,而是 PyTorch 内置优化器的类名。
更多优化器选择可以参考{external+torch:ref}`PyTorch 支持的优化器列表<optim:algorithms>`。
要修改模型的学习率,只需要在优化器的配置中修改 lr
即可。
要配置其他参数,可直接根据 PyTorch API 文档 进行。
例如,如果想使用 Adam
并设置参数为 torch.optim.Adam(params, lr=0.001, betas=(0.9, 0.999), eps=1e-08, weight_decay=0, amsgrad=False)
。
则需要进行如下修改:
optim_wrapper = dict(
type='OptimWrapper',
optimizer = dict(
type='Adam',
lr=0.001,
betas=(0.9, 0.999),
eps=1e-08,
weight_decay=0,
amsgrad=False),
)
考虑到对于单精度训练来说,优化器封装的默认类型就是 `OptimWrapper`,我们在这里可以直接省略,因此配置文件可以进一步简化为:
```python
optim_wrapper = dict(
optimizer=dict(
type='Adam',
lr=0.001,
betas=(0.9, 0.999),
eps=1e-08,
weight_decay=0,
amsgrad=False))
```
混合精度训练
如果我们想要使用混合精度训练(Automactic Mixed Precision),我们只需简单地将 optim_wrapper
的类型改为 AmpOptimWrapper
。
optim_wrapper = dict(type='AmpOptimWrapper', optimizer=...)
另外,为了方便,我们同时在启动训练脚本 tools/train.py
中提供了 --amp
参数作为开启混合精度训练的开关,更多细节可以参考训练与测试教程。
参数化精细配置
在一些模型中,不同的优化策略需要适应特定的参数,例如不在 BatchNorm 层使用权重衰减,或者在不同层使用不同的学习率等等。
我们需要用到 optim_wrapper
中的 paramwise_cfg
参数来进行精细化配置。
-
为不同类型的参数设置超参乘子
例如,我们可以在
paramwise_cfg
配置中设置norm_decay_mult=0.
来改变归一化层权重和偏移的衰减为0。optim_wrapper = dict( optimizer=dict(type='SGD', lr=0.8, weight_decay=1e-4), paramwise_cfg=dict(norm_decay_mult=0.))
支持更多类型的参数配置,参考以下列表:
lr_mult
:所有参数的学习率系数decay_mult
:所有参数的衰减系数bias_lr_mult
:偏置的学习率系数(不包括正则化层的偏置以及可变形卷积的 offset),默认值为 1bias_decay_mult
:偏置的权值衰减系数(不包括正则化层的偏置以及可变形卷积的 offset),默认值为 1norm_decay_mult
:正则化层权重和偏置的权值衰减系数,默认值为 1dwconv_decay_mult
:Depth-wise 卷积的权值衰减系数,默认值为 1bypass_duplicate
:是否跳过重复的参数,默认为False
dcn_offset_lr_mult
:可变形卷积(Deformable Convolution)的学习率系数,默认值为 1
-
为特定参数设置超参乘子
MMClassification 通过
paramwise_cfg
的custom_keys
参数来配置特定参数的超参乘子。例如,我们可以通过以下配置来设置所有
backbone.layer0
层的学习率和权重衰减为0,backbone
的其余层和优化器保持一致,另外head
层的学习率为0.001.optim_wrapper = dict( optimizer=dict(type='SGD', lr=0.01, weight_decay=0.0001), paramwise_cfg=dict( custom_keys={ 'backbone.layer0': dict(lr_mult=0, decay_mult=0), 'backbone': dict(lr_mult=1), 'head': dict(lr_mult=0.1) }))
梯度裁剪
在训练过程中,损失函数可能接近于一些异常陡峭的区域,从而导致梯度爆炸。而梯度裁剪可以帮助稳定训练过程,更多介绍可以参见该页面。
目前我们支持在 optim_wrapper
字段中添加 clip_grad
参数来进行梯度裁剪,更详细的参数可参考 PyTorch 文档。
用例如下:
optim_wrapper = dict(
optimizer=dict(type='SGD', lr=0.01, weight_decay=0.0001),
# norm_type: 使用的范数类型,此处使用范数2。
clip_grad=dict(max_norm=35, norm_type=2))
梯度累计
计算资源缺乏缺乏时,每个训练批次的大小(batch size)只能设置为较小的值,这可能会影响模型的性能。
可以使用梯度累计来规避这一问题。我们支持在 optim_wrapper
字段中添加 accumulative_counts
参数来进行梯度累计。
用例如下:
train_dataloader = dict(batch_size=64)
optim_wrapper = dict(
optimizer=dict(type='SGD', lr=0.01, weight_decay=0.0001),
accumulative_counts=4)
表示训练时,每 4 个 iter 执行一次反向传播。由于此时单张 GPU 上的批次大小为 64,也就等价于单张 GPU 上一次迭代的批次大小为 256,也即:
train_dataloader = dict(batch_size=256)
optim_wrapper = dict(
optimizer=dict(type='SGD', lr=0.01, weight_decay=0.0001))
配置参数优化策略
在训练过程中,优化参数例如学习率、动量,通常不会是固定不变,而是随着训练进程的变化而调整。PyTorch 支持一些学习率调整的调度器,但是不足以完成复杂的策略。在MMClassification中,我们提供 param_scheduler
来更好地控制不同优化参数的策略。
配置学习率调整策略
深度学习研究中,广泛应用学习率衰减来提高网络的性能。我们支持大多数 PyTorch 学习率调度器, 其中包括 ExponentialLR
, LinearLR
, StepLR
, MultiStepLR
等等。
-
单个学习率策略
多数情况下,我们使用单一学习率策略,这里
param_scheduler
会是一个字典。比如在默认的 ResNet 网络训练中,我们使用阶梯式的学习率衰减策略MultiStepLR
,配置文件为:param_scheduler = dict( type='MultiStepLR', by_epoch=True, milestones=[100, 150], gamma=0.1)
或者我们想使用
CosineAnnealingLR
来进行学习率衰减:param_scheduler = dict( type='CosineAnnealingLR', by_epoch=True, T_max=num_epochs)
-
多个学习率策略
然而在一些其他情况下,为了提高模型的精度,通常会使用多种学习率策略。例如,在训练的早期阶段,网络容易不稳定,而学习率的预热就是为了减少这种不稳定性。
整个学习过程中,学习率将会通过预热从一个很小的值逐步提高到预定值,再会通过其他的策略进一步调整。
在 MMClassification 中,我们同样使用
param_scheduler
,将多种学习策略写成列表就可以完成上述预热策略的组合。例如:
- 在前50次迭代中逐迭代次数地线性预热
param_scheduler = [ # 逐迭代次数,线性预热 dict(type='LinearLR', start_factor=0.001, by_epoch=False, # 逐迭代次数 end=50), # 只预热50次迭代次数 # 主要的学习率策略 dict(type='MultiStepLR', by_epoch=True, milestones=[8, 11], gamma=0.1) ]
- 在前10轮迭代中逐迭代次数地线性预热
param_scheduler = [ # 在前10轮迭代中,逐迭代次数,线性预热 dict(type='LinearLR', start_factor=0.001, by_epoch=True, end=10, convert_to_iter_based=True, # 逐迭代次数更新学习率. ), # 在 10 轮次后,通过余弦退火衰减 dict(type='CosineAnnealingLR', by_epoch=True, begin=10) ]
注意这里增加了
begin
和end
参数,这两个参数指定了调度器的生效区间。生效区间通常只在多个调度器组合时才需要去设置,使用单个调度器时可以忽略。当指定了begin
和end
参数时,表示该调度器只在 [begin, end) 区间内生效,其单位是由by_epoch
参数决定。在组合不同调度器时,各调度器的by_epoch
参数不必相同。如果没有指定的情况下,begin
为 0,end
为最大迭代轮次或者最大迭代次数。如果相邻两个调度器的生效区间没有紧邻,而是有一段区间没有被覆盖,那么这段区间的学习率维持不变。而如果两个调度器的生效区间发生了重叠,则对多组调度器叠加使用,学习率的调整会按照调度器配置文件中的顺序触发(行为与 PyTorch 中
ChainedScheduler
一致)。为了避免学习率曲线与预期不符, 配置完成后,可以使用 MMClassification 提供的 [学习率可视化工具](../user_guides/visualization.md#learning-rate-schedule-visualization) 画出对应学习率调整曲线。
配置动量调整策略
MMClassification 支持动量调度器根据学习率修改优化器的动量,从而使损失函数收敛更快。用法和学习率调度器一致。
我们支持的动量策略和详细的使用细节可以参考这里。我们只将调度器中的 LR
替换为了 Momentum
,动量策略可以直接追加 param_scheduler
列表中。
这里是一个用例:
param_scheduler = [
# 学习率策略
dict(type='LinearLR', ...),
# 动量策略
dict(type='LinearMomentum',
start_factor=0.001,
by_epoch=False,
begin=0,
end=1000)
]
新增优化器或者优化器构造器
本部分将修改 MMClassification 源码或者向 MMClassification 框架添加代码,初学者可跳过。
新增优化器
在学术研究和工业实践中,可能需要使用 MMClassification 未实现的优化方法,可以通过以下方法添加。
1. 定义一个新的优化器
一个自定义的优化器可根据如下规则进行定制:
假设我们想添加一个名为 MyOptimzer
的优化器,其拥有参数 a
, b
和 c
。
可以创建一个名为 mmcls/engine/optimizer
的文件夹,并在目录下的一个文件,如 mmcls/engine/optimizer/my_optimizer.py
中实现该自定义优化器:
from mmengine.registry import OPTIMIZERS
from torch.optim import Optimizer
@OPTIMIZERS.register_module()
class MyOptimizer(Optimizer):
def __init__(self, a, b, c):
...
def step(self, closure=None):
...
2. 注册优化器
要注册上面定义的上述模块,首先需要将此模块导入到主命名空间中。有两种方法可以实现它。
-
修改
mmcls/engine/optimizers/__init__.py
,将其导入至mmcls.engine
包。# 在 mmcls/engine/optimizers/__init__.py 中 ... from .my_optimizer import MyOptimizer # MyOptimizer 是我们自定义的优化器的名字 __all__ = [..., 'MyOptimizer']
在运行过程中,我们会自动导入
mmcls.engine
包并同时注册MyOptimizer
。 -
在配置中使用
custom_imports
手动导入custom_imports = dict( imports=['mmcls.engine.optimizers.my_optimizer'], allow_failed_imports=False, )
mmcls.engine.optimizers.my_optimizer
模块将会在程序开始阶段被导入,MyOptimizer
类会随之自动被注册。 注意,这里只需要导入包含MyOptmizer
类的包。如果填写mmcls.engine.optimizers.my_optimizer.MyOptimizer
则 不会 被直接导入。
3. 在配置文件中指定优化器
之后,用户便可在配置文件的 optim_wrapper.optimizer
域中使用 MyOptimizer
:
optim_wrapper = dict(
optimizer=dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value))
新增优化器构造器
某些模型可能具有一些特定于参数的设置以进行优化,例如 为所有 BatchNorm 层设置不同的权重衰减。
Although we already can use the optim_wrapper.paramwise_cfg
field to
configure various parameter-specific optimizer settings. It may still not cover your need.
尽管我们已经可以使用 optim_wrapper.paramwise_cfg
字段来配置特定参数的优化设置,但可能仍然无法覆盖你的需求。
当然你可以在此基础上进行修改。我们默认使用 DefaultOptimWrapperConstructor
来构造优化器。在构造过程中,通过 paramwise_cfg
来精细化配置不同设置。这个默认构造器可以作为新优化器构造器实现的模板。
我们可以新增一个优化器构造器来覆盖这些行为。
# 在 mmcls/engine/optimizers/my_optim_constructor.py 中
from mmengine.optim import DefaultOptimWrapperConstructor
from mmcls.registry import OPTIM_WRAPPER_CONSTRUCTORS
@OPTIM_WRAPPER_CONSTRUCTORS.register_module()
class MyOptimWrapperConstructor:
def __init__(self, optim_wrapper_cfg, paramwise_cfg=None):
...
def __call__(self, model):
...
接下来类似 新增优化器教程 来导入并使用新的优化器构造器。
-
修改
mmcls/engine/optimizers/__init__.py
,将其导入至mmcls.engine
包。# 在 mmcls/engine/optimizers/__init__.py 中 ... from .my_optim_constructor import MyOptimWrapperConstructor __all__ = [..., 'MyOptimWrapperConstructor']
-
在配置文件的
optim_wrapper.constructor
字段中使用MyOptimWrapperConstructor
。optim_wrapper = dict( constructor=dict(type='MyOptimWrapperConstructor'), optimizer=..., paramwise_cfg=..., )