ショートカット

ランタイム設定のカスタマイズ

最適化設定のカスタマイズ

最適化関連の設定は、現在すべて`optim_wrapper`によって管理されています。これは通常、`optimizer`、`paramwise_cfg`、`clip_grad`の3つのフィールドを持ちます。詳細はOptimWrapperを参照してください。以下の例では、`Adamw`が`optimizer`として使用され、バックボーンの学習率が10分の1に減少し、勾配クリッピングが追加されています。

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

モデルの学習率を変更するには、`optimizer`内の`lr`を変更するだけです。PyTorchのAPIドキュメントに従って、引数を直接設定できます。

独自実装のオプティマイザのカスタマイズ

1. 新しいオプティマイザの定義

カスタマイズされたオプティマイザは、次のように定義できます。

`MyOptimizer`という名前のオプティマイザを追加したいとします。これは、`a`、`b`、`c`という引数を持っています。`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. レジストリへのオプティマイザの追加

上記で定義されたモジュールを見つけるには、まずこのモジュールをメイン名前空間にインポートする必要があります。これを実現するには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を呼び出します。`CosineAnnealingLR`や`PolyLR`スケジュールなど、他にも多くの学習率スケジュールをここにサポートしています。いくつかの例を以下に示します。

  • ポリスケジュール

    param_scheduler = [
        dict(
            type='PolyLR',
            power=0.9,
            eta_min=1e-4,
            begin=0,
            end=8,
            by_epoch=True)]
    
  • CosineAnnealingスケジュール

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

トレーニングループのカスタマイズ

デフォルトでは、EpochBasedTrainLooptrain_cfgで使用され、トレーニングエポックごとに検証が行われます。

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にあると仮定すると、2つの方法があります。

  • 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で実装されたフックの使用

フックがMMDetectionに既に実装されている場合は、以下の通り構成を変更してフックを使用できます。

例:NumClassCheckHook

ヘッドのnum_classesdatasetのメタ情報内のclassesの長さと一致するかどうかをチェックするNumClassCheckHookという名前のカスタムフックを実装しました。

これをdefault_runtime.pyに設定します。

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

デフォルトのランタイムフックの変更

default_hooksを通じて登録される一般的なフックには、以下が含まれます。

  • IterTimerHook:データの読み込み時間を示す'data_time'とモデルのトレーニングステップ時間を示す'time'を記録するフック。

  • LoggerHookRunnerのさまざまなコンポーネントからログを収集し、ターミナル、JSONファイル、TensorBoard、wandbなどに書き込むフック。

  • ParamSchedulerHook:オプティマイザのハイパーパラメータ(例:学習率、モーメンタム)を更新するフック。

  • CheckpointHook:定期的にチェックポイントを保存するフック。

  • DistSamplerSeedHook:サンプラとbatch_samplerのシードを設定するフック。

  • DetVisualizationHook:検証とテストプロセスの予測結果を視覚化するのに使用されるフック。

IterTimerHookParamSchedulerHookDistSamplerSeedHookはシンプルで通常は変更する必要がないため、ここではLoggerHookCheckpointHookDetVisualizationHookで何ができるかについて説明します。

CheckpointHook

定期的なチェックポイントの保存に加えて、CheckpointHookmax_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

DetVisualizationHookDetLocalVisualizerを使用して予測結果を視覚化し、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')