快捷键

自定义运行时设置

自定义优化设置

与优化相关的配置现在全部由 optim_wrapper 管理,它通常有三个字段:optimizerparamwise_cfgclip_grad,有关更多详细信息,请参阅 OptimWrapper。以下是示例,其中 Adamw 用作 optimizer,主干的学习率降低了 10 倍,并添加了梯度裁剪。

optim_wrapper = dict(
    type='OptimWrapper',
    # optimizer
    optimizer=dict(
        type='AdamW',
        lr=0.0001,
        weight_decay=0.05,
        eps=1e-8,
        betas=(0.9, 0.999)),

    # Parameter-level learning rate and weight decay settings
    paramwise_cfg=dict(
        custom_keys={
            'backbone': dict(lr_mult=0.1, decay_mult=1.0),
        },
        norm_decay_mult=0.0),

    # gradient clipping
    clip_grad=dict(max_norm=0.01, norm_type=2))

自定义 Pytorch 支持的优化器

我们已经支持使用 PyTorch 实现的所有优化器,唯一需要修改的是配置文件 optim_wrapper 字段中的 optimizer 字段。例如,如果你想使用 ADAM(注意性能可能会大幅下降),修改可以如下进行。

optim_wrapper = dict(
    type='OptimWrapper',
    optimizer=dict(type='Adam', lr=0.0003, weight_decay=0.0001))

要修改模型的学习率,用户只需要修改 optim_wrapper 字段中 optimizer 字段下的 lr。用户可以直接设置参数,遵循 PyTorch 的 API 文档

自定义自行实现的优化器

1. 定义一个新的优化器

自定义优化器可以如下定义。

假设你想要添加一个名为 MyOptimizer 的优化器,它有参数 abc。你需要创建一个名为 mmdet/engine/optimizers 的新目录。然后在一个文件中实现这个新的优化器,例如,在 mmdet/engine/optimizers/my_optimizer.py 中。

from mmdet.registry import OPTIMIZERS
from torch.optim import Optimizer


@OPTIMIZERS.register_module()
class MyOptimizer(Optimizer):

    def __init__(self, a, b, c)

2. 将优化器添加到注册表

要找到上面定义的模块,首先应该将这个模块导入到主命名空间。有两种方法可以实现这一点。

  • 修改 mmdet/engine/optimizers/__init__.py 以导入它。

    新定义的模块应该在 mmdet/engine/optimizers/__init__.py 中被导入,这样注册表就可以找到这个新模块并添加它。

from .my_optimizer import MyOptimizer
  • 在配置文件中使用 custom_imports 手动导入它。

custom_imports = dict(imports=['mmdet.engine.optimizers.my_optimizer'], allow_failed_imports=False)

模块 mmdet.engine.optimizers.my_optimizer 将在程序开始时被导入,然后类 MyOptimizer 会自动注册。注意,只有包含类 MyOptimizer 的包应该被导入。 mmdet.engine.optimizers.my_optimizer.MyOptimizer **不能**直接被导入。

实际上,用户可以使用完全不同的文件目录结构来使用这种导入方法,只要模块根目录可以在 PYTHONPATH 中找到即可。

3. 在配置文件中指定优化器

然后你可以在配置文件 optim_wrapper 字段中的 optimizer 字段中使用 MyOptimizer。在配置文件中,优化器通过 optimizer 字段定义,如下所示。

optim_wrapper = dict(
    type='OptimWrapper',
    optimizer=dict(type='SGD', lr=0.02, momentum=0.9, weight_decay=0.0001))

要使用你自己的优化器,可以将这个字段更改为

optim_wrapper = dict(
    type='OptimWrapper',
    optimizer=dict(type='MyOptimizer', a=a_value, b=b_value, c=c_value))

自定义优化器包装器构造函数

一些模型可能对优化有一些参数特定设置,例如 BatchNorm 层的权重衰减。用户可以通过自定义优化器包装器构造函数进行这些细粒度的参数调整。

from mmengine.optim import DefaultOptiWrapperConstructor

from mmdet.registry import OPTIM_WRAPPER_CONSTRUCTORS
from .my_optimizer import MyOptimizer


@OPTIM_WRAPPER_CONSTRUCTORS.register_module()
class MyOptimizerWrapperConstructor(DefaultOptimWrapperConstructor):

    def __init__(self,
                 optim_wrapper_cfg: dict,
                 paramwise_cfg: Optional[dict] = None):

    def __call__(self, model: nn.Module) -> OptimWrapper:

        return optim_wrapper

默认的优化器包装器构造函数在 这里 实现,它也可以作为新优化器包装器构造函数的模板。

附加设置

优化器未实现的技巧应该通过优化器包装器构造函数(例如,设置参数级学习率)或钩子实现。我们列出了一些可以稳定训练或加速训练的常见设置。随时为更多设置创建 PR 或 issue。

  • 使用梯度裁剪稳定训练:一些模型需要梯度裁剪来裁剪梯度,以稳定训练过程。以下是一个示例。

    optim_wrapper = dict(
        _delete_=True, clip_grad=dict(max_norm=35, norm_type=2))
    

    如果你的配置文件继承了已经设置了 optim_wrapper 的基础配置文件,你可能需要使用 _delete_=True 来覆盖不必要的设置。有关更多详细信息,请参阅 配置文件文档

  • 使用动量调度加速模型收敛:我们支持动量调度器,根据学习率修改模型的动量,这可以使模型更快地收敛。动量调度器通常与 LR 调度器一起使用,例如,以下配置文件在 3D 检测 中被用来加速收敛。有关更多详细信息,请参阅 CosineAnnealingLRCosineAnnealingMomentum 的实现。

    param_scheduler = [
        # learning rate scheduler
        # During the first 8 epochs, learning rate increases from 0 to lr * 10
        # during the next 12 epochs, learning rate decreases from lr * 10 to lr * 1e-4
        dict(
            type='CosineAnnealingLR',
            T_max=8,
            eta_min=lr * 10,
            begin=0,
            end=8,
            by_epoch=True,
            convert_to_iter_based=True),
        dict(
            type='CosineAnnealingLR',
            T_max=12,
            eta_min=lr * 1e-4,
            begin=8,
            end=20,
            by_epoch=True,
            convert_to_iter_based=True),
        # momentum scheduler
        # During the first 8 epochs, momentum increases from 0 to 0.85 / 0.95
        # during the next 12 epochs, momentum increases from 0.85 / 0.95 to 1
        dict(
            type='CosineAnnealingMomentum',
            T_max=8,
            eta_min=0.85 / 0.95,
            begin=0,
            end=8,
            by_epoch=True,
            convert_to_iter_based=True),
        dict(
            type='CosineAnnealingMomentum',
            T_max=12,
            eta_min=1,
            begin=8,
            end=20,
            by_epoch=True,
            convert_to_iter_based=True)
    ]
    

自定义训练调度

默认情况下,我们使用带有 1x 调度的步进学习率,这将在 MMEngine 中调用 MultiStepLR。我们支持许多其他学习率调度,这里,例如 CosineAnnealingLRPolyLR 调度。以下是一些示例。

  • Poly 调度

    param_scheduler = [
        dict(
            type='PolyLR',
            power=0.9,
            eta_min=1e-4,
            begin=0,
            end=8,
            by_epoch=True)]
    
  • ConsineAnnealing 调度

    param_scheduler = [
        dict(
            type='CosineAnnealingLR',
            T_max=8,
            eta_min=lr * 1e-5,
            begin=0,
            end=8,
            by_epoch=True)]
    
    

自定义训练循环

默认情况下,EpochBasedTrainLoop 用于 train_cfg,并在每个训练 epoch 后进行验证,如下所示。

train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_begin=1, val_interval=1)

实际上,IterBasedTrainLoopEpochBasedTrainLoop 都支持动态间隔,请参见以下示例。

# Before 365001th iteration, we do evaluation every 5000 iterations.
# After 365000th iteration, we do evaluation every 368750 iterations,
# which means that we do evaluation at the end of training.

interval = 5000
max_iters = 368750
dynamic_intervals = [(max_iters // interval * interval + 1, max_iters)]
train_cfg = dict(
    type='IterBasedTrainLoop',
    max_iters=max_iters,
    val_interval=interval,
    dynamic_intervals=dynamic_intervals)

自定义钩子

自定义自实现钩子

1. 实现一个新的钩子

MMEngine 提供了许多有用的 钩子,但在某些情况下,用户可能需要实现一个新的钩子。MMDetection 在 v3.0 中支持训练中的自定义钩子。因此,用户可以直接在 mmdet 或其基于 mmdet 的代码库中实现钩子,并且只需修改训练中的配置即可使用该钩子。这里给出一个在 mmdet 中创建新钩子并在训练中使用它的例子。

from mmengine.hooks import Hook
from mmdet.registry import HOOKS


@HOOKS.register_module()
class MyHook(Hook):

    def __init__(self, a, b):

    def before_run(self, runner) -> None:

    def after_run(self, runner) -> None:

    def before_train(self, runner) -> None:

    def after_train(self, runner) -> None:

    def before_train_epoch(self, runner) -> None:

    def after_train_epoch(self, runner) -> None:

    def before_train_iter(self,
                          runner,
                          batch_idx: int,
                          data_batch: DATA_BATCH = None) -> None:

    def after_train_iter(self,
                         runner,
                         batch_idx: int,
                         data_batch: DATA_BATCH = None,
                         outputs: Optional[dict] = None) -> None:

根据钩子的功能,用户需要在 before_runafter_runbefore_trainafter_trainbefore_train_epochafter_train_epochbefore_train_iterafter_train_iter 训练的每个阶段指定钩子将做什么。还有更多可以插入钩子的位置,请参考 基本钩子类 获取更多详细信息。

2. 注册新钩子

然后我们需要导入 MyHook。假设该文件位于 mmdet/engine/hooks/my_hook.py,有两种方法可以做到这一点

  • 修改 mmdet/engine/hooks/__init__.py 以导入它。

    新定义的模块应该在 mmdet/engine/hooks/__init__.py 中导入,以便注册表可以找到新模块并添加它

from .my_hook import MyHook
  • 在配置文件中使用 custom_imports 手动导入它。

custom_imports = dict(imports=['mmdet.engine.hooks.my_hook'], allow_failed_imports=False)

3. 修改配置

custom_hooks = [
    dict(type='MyHook', a=a_value, b=b_value)
]

你也可以通过将键 priority 添加到 'NORMAL''HIGHEST' 来设置钩子的优先级,如下所示

custom_hooks = [
    dict(type='MyHook', a=a_value, b=b_value, priority='NORMAL')
]

默认情况下,钩子的优先级在注册期间设置为 NORMAL

使用 MMDetection 中实现的钩子

如果钩子已经在 MMDectection 中实现,你可以直接修改配置以使用该钩子,如下所示

示例:NumClassCheckHook

我们实现了一个名为 NumClassCheckHook 的自定义钩子来检查头部中的 num_classes 是否与 dataset 元信息中 classes 的长度匹配。

我们在 default_runtime.py 中设置它。

custom_hooks = [dict(type='NumClassCheckHook')]

修改默认运行时钩子

有一些常见的钩子是通过 default_hooks 注册的,它们是

  • IterTimerHook:一个记录加载数据的“data_time”和模型训练步骤的“time”的钩子。

  • LoggerHook:一个从 Runner 的不同组件收集日志并将它们写入终端、JSON 文件、tensorboard 和 wandb 等等的钩子。

  • ParamSchedulerHook:一个更新优化器中一些超参数(例如,学习率和动量)的钩子。

  • CheckpointHook:一个定期保存检查点的钩子。

  • DistSamplerSeedHook:一个为采样器和 batch_sampler 设置种子的钩子。

  • DetVisualizationHook:一个用于可视化验证和测试过程预测结果的钩子。

IterTimerHookParamSchedulerHookDistSamplerSeedHook 很简单,通常不需要修改,所以这里揭示了我们如何使用 LoggerHookCheckpointHookDetVisualizationHook

CheckpointHook

除了定期保存检查点之外,CheckpointHook 还提供了其他选项,例如 max_keep_ckptssave_optimizer 等等。用户可以设置 max_keep_ckpts 仅保存少量检查点,或通过 save_optimizer 决定是否存储优化器的状态字典。参数的更多详细信息请参考 这里

default_hooks = dict(
    checkpoint=dict(
        type='CheckpointHook',
        interval=1,
        max_keep_ckpts=3,
        save_optimizer=True))

LoggerHook

LoggerHook 可以设置间隔。详细用法可以在 docstring 中找到。

default_hooks = dict(logger=dict(type='LoggerHook', interval=50))

DetVisualizationHook

DetVisualizationHook 使用 DetLocalVisualizer 来可视化预测结果,DetLocalVisualizer 目前支持不同的后端,例如,TensorboardVisBackendWandbVisBackend(有关更多详细信息,请参见 docstring)。用户可以添加多个后端来进行可视化,如下所示。

default_hooks = dict(
    visualization=dict(type='DetVisualizationHook', draw=True))

vis_backends = [dict(type='LocalVisBackend'),
                dict(type='TensorboardVisBackend')]
visualizer = dict(
    type='DetLocalVisualizer', vis_backends=vis_backends, name='visualizer')