ランタイム設定のカスタマイズ¶
最適化設定のカスタマイズ¶
最適化関連の設定は、現在すべて`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検出で使用され、収束を高速化します。詳細は、CosineAnnealingLRとCosineAnnealingMomentumの実装を参照してください。
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)]
トレーニングループのカスタマイズ¶
デフォルトでは、EpochBasedTrainLoop
がtrain_cfg
で使用され、トレーニングエポックごとに検証が行われます。
train_cfg = dict(type='EpochBasedTrainLoop', max_epochs=12, val_begin=1, val_interval=1)
実際には、IterBasedTrainLoop
とEpochBasedTrainLoop
の両方で動的な間隔がサポートされています。以下の例を参照してください。
# 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_run
、after_run
、before_train
、after_train
、before_train_epoch
、after_train_epoch
、before_train_iter
、after_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_classes
がdataset
のメタ情報内のclasses
の長さと一致するかどうかをチェックするNumClassCheckHookという名前のカスタムフックを実装しました。
これを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
:検証とテストプロセスの予測結果を視覚化するのに使用されるフック。
IterTimerHook
、ParamSchedulerHook
、DistSamplerSeedHook
はシンプルで通常は変更する必要がないため、ここではLoggerHook
、CheckpointHook
、DetVisualizationHook
で何ができるかについて説明します。
CheckpointHook¶
定期的なチェックポイントの保存に加えて、CheckpointHook
はmax_keep_ckpts
、save_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
は現在、TensorboardVisBackend
やWandbVisBackend
などのさまざまなバックエンドをサポートしています(詳細については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')