Customize Runtime Settings
我们已经支持使用 PyTorch 实现的所有优化器,唯一的修改是改变配置文件中的优化器字段。例如,如果您想使用Adam(请注意,性能可能会下降很多),可以做如下修改。
optimizer = dict(type='Adam', lr=0.0003, weight_decay=0.0001)
要修改模型的学习率,用户只需要在优化器的配置中修改 lr。用户可以按照 PyTorch 的 API 文档直接设置参数。
自定义自己实现的优化器
首先定义一个新的优化器,一个自定义的优化器可以定义如下。
假设你想添加一个名为 MyOptimizer 的优化器,它有参数 a、b 和 c。你需要创建一个名为 mmdet/core/optimizer
的新目录。然后在一个文件中实现新的优化器,例如,在 mmdet/core/optimizer/my_optimizer.py
中。
from .registry import OPTIMIZERS
from torch.optim import Optimizer
@OPTIMIZERS.register_module()
class MyOptimizer(Optimizer):
def __init__(self, a, b, c)
将优化器添加到 Registry:
要找到上面定义的模块,首先应该把这个模块导入到主命名空间。有两个选项可以实现。
修改
mmdet/core/optimizer/__init__.py
来导入(需要重新编译源代码)新定义的模块应该在
mmdet/core/optimizer/__init__.py
中导入,这样 Registry 就会找到新的模块并将其加入。from .my_optimizer import MyOptimizer
使用
custom_imports
来手动导入custom_imports = dict(imports=['mmdet.core.optimizer.my_optimizer'], allow_failed_imports=False)
mmdet.core.optimizer.my_optimizer
模块将在程序开始时被导入,然后 MyOptimizer 类会被自动注册。注意,只有包含 MyOptimizer 类的包应该被导入。mmdet.core.optimizer.my_optimizer.MyOptimizer
不能被直接导入。
实际上用户可以用这种导入方式使用完全不同的文件目录结构,只要模块根部能位于 PYTHONPATH
中。
最后,指定使用的优化器:
optimizer = dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value)
构造自定义优化器的构造器
一些模型可能有一些特定的优化参数设置,例如 BatchNorm 层的权重衰减。用户可以通过定制优化器构造器来进行这些细粒度的参数调整。
from mmcv.utils import build_from_cfg
from mmcv.runner.optimizer import OPTIMIZER_BUILDERS, OPTIMIZERS
from mmdet.utils import get_root_logger
from .my_optimizer import MyOptimizer
@OPTIMIZER_BUILDERS.register_module()
class MyOptimizerConstructor(object):
def __init__(self, optimizer_cfg, paramwise_cfg=None):
def __call__(self, model):
return my_optimizer
这里实现了默认的优化器构造函数,它也可以作为新的优化器构造函数的模板。
可选的设置
没有被优化器实现的技巧应该通过优化器构造器(例如,设置参数化的学习率)或钩子来实现。我们列出了一些可以稳定训练或加速训练的常用设置。欢迎创建PR,为更多的设置提出问题。
使用梯度裁剪来稳定训练。有些模型需要用梯度夹来夹住梯度以稳定训练过程。下面是一个例子。
optimizer_config = dict(
_delete_=True, grad_clip=dict(max_norm=35, norm_type=2))如果你的配置继承了已经设置了
optimizer_config
的基础配置,你可能需要_delete_=True
来覆盖不必要的设置。更多细节请参见配置文档。使用 momentum schedule 来加速模型的收敛。我们支持 momentum schedule,根据学习速率修改模型的动量,这可以使模型以更快的方式收敛。动量调度器通常与 LR 调度器一起使用,例如,在 3D 检测中使用以下配置来加速收敛。更多细节,请参考 CyclicLrUpdater 和 CyclicMomentumUpdater 的实现。
lr_config = dict(
policy='cyclic',
target_ratio=(10, 1e-4),
cyclic_times=1,
step_ratio_up=0.4,
)
momentum_config = dict(
policy='cyclic',
target_ratio=(0.85 / 0.95, 1),
cyclic_times=1,
step_ratio_up=0.4,
)
自定义训练 schedules
默认情况下,我们使用 1x 计划的阶梯学习率,这在 MMCV 中调用 StepLRHook。我们在这里支持许多其他的学习率计划,如 CosineAnnealing 和 Poly 计划。下面是一些例子
poly schedule
lr_config = dict(policy='poly', power=0.9, min_lr=1e-4, by_epoch=False)
ConsineAnnealing schedule
lr_config = dict(
policy='CosineAnnealing',
warmup='linear',
warmup_iters=1000,
warmup_ratio=1.0 / 10,
min_lr_ratio=1e-5)
自定义 workflow
workflow 是一个(阶段,历时)的列表,用于指定运行顺序和历时。默认情况下,它被设置为
workflow = [('train', 1)]
这意味着运行1个历时进行训练。有时,用户可能想在验证集上检查一些关于模型的指标(如损失、准确度)。在这种情况下,我们可以将工作流程设置为
[('train', 1), ('val', 1)]
这样,1个历时的训练和1个历时的验证将被反复运行。
- 模型的参数在估值时代不会被更新。
- 配置中的关键词total_epochs只控制训练周期的数量,不会影响验证工作流程。
[('train', 1), ('val', 1)]
和[('train', 1)]
不会改变 EvalHook 的行为,因为 EvalHook 是由 after_train_epoch 调用的,验证工作流程只影响通过 after_val_epoch 调用的 hook。因此,[('train', 1), ('val', 1)]
和[('train', 1)]
之间的唯一区别是,运行者将在每个训练epoch
之后计算验证集的损失。
自定义 hook
自定义自己实现的 hook
创建新的 hook 文件
在某些情况下,用户可能需要实现一个新的钩子。从 v2.3.0 开始,MMDetection 支持训练中的自定义钩子(#3395)。因此,用户可以直接在 mmdet 或其基于 mmdet 的代码库中实现一个 hook,只需修改训练中的配置即可使用该钩子。在 v2.3.0 版之前,用户需要修改代码,以便在训练开始前注册钩子。这里我们给出一个在 mmdet 中创建一个新钩子并在训练中使用它的例子。
在
mmdet/core/utils/my_hook.py
创建MyHook
的类from mmcv.runner import HOOKS, Hook
@HOOKS.register_module()
class MyHook(Hook):
def __init__(self, a, b):
pass
def before_run(self, runner):
pass
def after_run(self, runner):
pass
def before_epoch(self, runner):
pass
def after_epoch(self, runner):
pass
def before_iter(self, runner):
pass
def after_iter(self, runner):
pass根据钩子的功能,用户需要在 before_run、after_run、before_epoch、after_epoch、before_iter 和 after_iter 中指定钩子在训练的每个阶段将做什么。
注册新的 hook
修改
mmdet/core/optimizer/__init__.py
来导入(需要重新编译源代码)新定义的模块应该在
mmdet/core/optimizer/__init__.py
中导入,这样 Registry 就会找到新的模块并将其加入。from .my_hook import MyHook
或者使用
custom_imports
来手动导入custom_imports = dict(imports=['mmdet.core.utils.my_hook'], allow_failed_imports=False)
修改你的 config
custom_hooks = [
dict(type='MyHook', a=a_value, b=b_value)
]你也可以添加
prioirity
字段修改 hook 的优先级,有NORMAL
和HIGHEST
:custom_hooks = [
dict(type='MyHook', a=a_value, b=b_value, priority='NORMAL')
]默认优先级为
NORMAL
使用 MMCV 实现的 Hook
如果钩子已经在 MMCV 中实现,你可以直接修改配置来使用这个钩子,如下所示
我们实现了一个名为 NumClassCheckHook 的自定义钩子,以检查 head 中的 num_classes 是否与数据集中的 CLASSES 长度一致。在 default_runtime.py
中:
custom_hooks = [dict(type='NumClassCheckHook')]
修改默认使用的 hook
有一些常见的钩子没有通过 custom_hooks 注册,它们是
- log_config
- checkpoint_config
- evaluation
- lr_config
- optimizer_config
- momentum_config
在这些钩子中,只有 logger 钩子的优先级是 VERY_LOW,其他的优先级是 NORMAL。上述教程已经涵盖了如何修改optimizer_config、momentum_config 和 lr_config。在这里,我们揭示了我们可以如何使用 log_config、checkpoint_config 和 evaluation。
Checkpoint config
MMCV运行器将使用 checkpoint_config 来初始化 CheckpointHook。
checkpoint_config = dict(interval=1)
用户可以设置 max_keep_ckpts 来只保存少量的检查点,或者决定是否通过 save_optimizer 存储优化器的状态 dict。参数的更多细节在这里
Log config
log_config 封装了多个记录器钩子,并能够设置间隔时间。现在 MMCV 支持 WandbLoggerHook, MlflowLoggerHook, 和 TensorboardLoggerHook。详细的使用方法可以在文档中找到。
log_config = dict(
interval=50,
hooks=[
dict(type='TextLoggerHook'),
dict(type='TensorboardLoggerHook')
])
Evaluation config
评价的配置将被用来初始化 EvalHook。除了 interval
不会被传递,其他参数如 metric
将被传递给 dataset.evaluation()
。
evaluation = dict(interval=1, metric='bbox')