Trainerのカスタマイズ#
Ultralyticsのトレーニングパイプラインは、BaseTrainer と DetectionTrainer のようなタスク固有のtrainerを中心に構築されています。これらのクラスは、トレーニングループ、検証、チェックポイント保存、ロギングを標準で処理します。カスタムメトリクスの追跡、損失の重み付けの調整、学習率スケジュールの実装など、より細かい制御が必要な場合は、trainerをサブクラス化して特定のメソッドをオーバーライドできます。
このガイドでは、次の7つの一般的なカスタマイズについて説明します。
- 各 epoch の終了時に カスタムメトリクス(F1スコア)をロギングする
- クラス不均衡に対応するための クラス重みの追加
- 別のメトリクスに基づく 最良モデルの保存
- 最初のN epochで バックボーンを凍結し、その後凍結を解除する
- レイヤーごとの学習率の指定
- マルチGPUトレーニング向けの GPU間BatchNormの同期
- 安定性調整のための 勾配クリッピングの設定
このガイドを読む前に、YOLOモデルのトレーニングの基礎と、BaseTrainer アーキテクチャを扱う 高度なカスタマイズのページを理解しておいてください。
カスタムTrainerの仕組み#
YOLO モデルクラスは、train() メソッドで trainer パラメーターを受け取ります。これにより、デフォルトの動作を拡張する独自のtrainerクラスを渡せます。
from ultralytics import YOLO
from ultralytics.models.yolo.detect import DetectionTrainer
class CustomTrainer(DetectionTrainer):
"""A custom trainer that extends DetectionTrainer with additional functionality."""
# Add your customizations here
model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", epochs=10, trainer=CustomTrainer)カスタムtrainerは DetectionTrainer からすべての機能を継承するため、カスタマイズしたい特定のメソッドだけをオーバーライドすれば済みます。
カスタムメトリクスのロギング#
検証ステップでは、precision、recall、mAPが計算されます。クラスごとの F1スコアなど、追加のメトリクスが必要な場合は、validate() をオーバーライドします。
import numpy as np
from ultralytics import YOLO
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.utils import LOGGER
class MetricsTrainer(DetectionTrainer):
"""Custom trainer that computes and logs F1 score at the end of each epoch."""
def validate(self):
"""Run validation and compute per-class F1 scores."""
metrics, fitness = super().validate()
if metrics is None:
return metrics, fitness
if hasattr(self.validator, "metrics") and hasattr(self.validator.metrics, "box"):
box = self.validator.metrics.box
f1_per_class = box.f1
class_indices = box.ap_class_index
names = self.validator.names
mean_f1 = float(np.mean(f1_per_class)) if len(f1_per_class) else 0.0
LOGGER.info(f"Mean F1 Score: {mean_f1:.4f}")
per_class_str = [f"{names[i]}: {f1_per_class[j]:.3f}" for j, i in enumerate(class_indices)]
LOGGER.info(f"Per-class F1: {per_class_str}")
return metrics, fitness
model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", epochs=5, trainer=MetricsTrainer)これにより、各検証実行後に、検証データに含まれるすべてのクラスにわたる平均F1スコアと、クラスごとの内訳が記録されます。
validatorでは、self.validator.metrics.box を通じて多数のメトリクスにアクセスできます。
| 属性 | 説明 |
|---|---|
f1 | クラスごとのF1スコア |
image_metrics | precision、recall、F1、TP、FP、FNを含む画像ごとのメトリクス辞書 |
p | クラスごとのprecision |
r | クラスごとのrecall |
ap50 | クラスごとのIoU 0.5におけるAP |
ap | クラスごとのIoU 0.5:0.95におけるAP |
mp、mr | 平均precisionとrecall |
map50、map | 平均APメトリクス |
クラス重みの追加#
分類損失に正規化した逆頻度重みを適用するには、0.0 と 1.0 の間に cls_pw を設定します。手動で選択した比率が必要な場合にのみ、既存の重み計算をオーバーライドしてください。
import numpy as np
from ultralytics import YOLO
from ultralytics.models.yolo.detect import DetectionTrainer
class WeightedTrainer(DetectionTrainer):
"""Detection trainer with hand-picked class-weight ratios."""
def compute_class_weights(self, class_counts):
"""Return custom per-class weights for the production loss owner."""
weights = np.ones_like(class_counts)
weights[0] = 2.0
weights[1] = 3.0
return weights
model = YOLO("yolo26n.pt")
model.train(data="custom.yaml", epochs=10, cls_pw=1.0, trainer=WeightedTrainer)set_class_weights() はこれらの値を平均1.0に正規化してモデルに保存し、既存の検出損失がそれらを適用します。上記のインデックスを使用するには、少なくとも2つのクラスを含むデータセットが必要です。
カスタムメトリクスによる最良モデルの保存#
trainerはfitnessに基づいて best.pt を保存します。検出タスクでは、fitnessはデフォルトで mAP@0.5:0.95 です([P, R, mAP@0.5, mAP@0.5:0.95] に対して [0.0, 0.0, 0.0, 1.0] の重みを使用します)。別のメトリクス(mAP@0.5 やrecallなど)を使用するには、validate() をオーバーライドし、選択したメトリクスをfitness値として返します。組み込みの save_model() が自動的にそれを使用します。
from ultralytics import YOLO
from ultralytics.models.yolo.detect import DetectionTrainer
class CustomSaveTrainer(DetectionTrainer):
"""Trainer that saves the best model based on mAP@0.5 instead of default fitness."""
def validate(self):
"""Override fitness to use mAP@0.5 for best model selection."""
previous_best = self.best_fitness
metrics, fitness = super().validate()
if metrics is None:
return metrics, fitness
fitness = metrics["metrics/mAP50(B)"]
self.best_fitness = fitness if previous_best is None else max(previous_best, fitness)
return metrics, fitness
model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", epochs=20, trainer=CustomSaveTrainer)BaseTrainer.validate() はデフォルトのメトリクスを使用して best_fitness を更新するため、呼び出す前にその以前の値を取得してください。
検証後に self.metrics で利用できる一般的なメトリクスは次のとおりです。
| キー | 説明 |
|---|---|
metrics/precision(B) | Precision |
metrics/recall(B) | Recall |
metrics/mAP50(B) | IoU 0.5におけるmAP |
metrics/mAP50-95(B) | IoU 0.5:0.95におけるmAP |
バックボーンの凍結と凍結解除#
転移学習のワークフローでは、最初のN epochの間、事前学習済みバックボーンを凍結すると効果的なことがあります。これにより、ネットワーク全体を ファインチューニングする前に、検出ヘッドを適応させられます。Ultralyticsにはトレーニング開始時にレイヤーを凍結する freeze パラメーターがあり、コールバックを使用してN epoch後に凍結を解除できます。
from ultralytics import YOLO
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.utils import LOGGER
FREEZE_EPOCHS = 5
def unfreeze_backbone(trainer):
"""Callback to unfreeze the user-requested layers after FREEZE_EPOCHS."""
if trainer.epoch == FREEZE_EPOCHS:
user_freeze = [x for x in trainer.freeze_layer_names if x not in {".dfl", "teacher_model."}]
LOGGER.info(f"Epoch {trainer.epoch}: Unfreezing requested layers for fine-tuning")
for name, param in trainer.model.named_parameters():
if (
not param.requires_grad
and ".dfl" not in name
and "teacher_model." not in name
and any(x in name for x in user_freeze)
):
param.requires_grad = True
LOGGER.info(f" Unfroze: {name}")
trainer.freeze_layer_names = [x for x in trainer.freeze_layer_names if x not in user_freeze]
class FreezingTrainer(DetectionTrainer):
"""Trainer with backbone freezing for first N epochs."""
def __init__(self, *args, **kwargs):
"""Initialize and register the unfreeze callback."""
super().__init__(*args, **kwargs)
self.add_callback("on_train_epoch_start", unfreeze_backbone)
model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", epochs=20, freeze=10, trainer=FreezingTrainer)freeze=10 パラメーターは、トレーニング開始時に最初の10レイヤー(インデックス0~9)を凍結します。これはYOLO26バックボーンの大部分をカバーします。バックボーンはレイヤー0~10にまたがるため、freeze=10 では最後のC2PSAブロック(レイヤー10)がトレーニング可能なままになります。バックボーン全体を凍結するには freeze=11 を使用してください。on_train_epoch_start コールバックは各epochの開始時に実行され、凍結期間の完了後に指定されたレイヤーの凍結を解除します。その際、恒久的に凍結されるDFLおよび蒸留teacherのパラメーターは保持されます。
freeze=10は最初の10レイヤー(インデックス0~9、YOLO26バックボーンの大部分)を凍結します。レイヤー10の最後のC2PSAブロックを含めるにはfreeze=11を使用してください。freeze=[0, 1, 2, 3]はインデックスで特定のレイヤーを凍結しますFREEZE_EPOCHSの値を大きくすると、バックボーンが変更される前にヘッドが適応する時間が長くなります
レイヤーごとの学習率#
ネットワークの異なる部分では、異なる 学習率が有効な場合があります。一般的な戦略は、学習済みの特徴を維持するために事前学習済みバックボーンには低い学習率を使用し、検出ヘッドにはより高い学習率を使用して、より速く適応できるようにすることです。
from ultralytics import YOLO
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.utils import LOGGER
from ultralytics.utils.torch_utils import unwrap_model
class PerLayerLRTrainer(DetectionTrainer):
"""Trainer with different learning rates for backbone and head."""
backbone_lr_ratio = 0.1
def build_optimizer(self, model, name="auto", lr=0.001, momentum=0.9, decay=1e-5, iterations=1e5):
"""Reuse the trainer optimizer and lower its backbone parameter-group rates."""
optimizer = super().build_optimizer(model, name, lr, momentum, decay, iterations)
unwrapped = unwrap_model(model)
backbone_len = len(unwrapped.yaml["backbone"])
backbone = {
id(p)
for name, p in unwrapped.named_parameters()
if any(name.startswith(f"model.{i}.") for i in range(backbone_len))
}
groups = []
for group in optimizer.param_groups:
head_params = [p for p in group["params"] if id(p) not in backbone]
backbone_params = [p for p in group["params"] if id(p) in backbone]
if head_params:
groups.append({**group, "params": head_params})
if backbone_params:
groups.append({**group, "params": backbone_params, "lr": group["lr"] * self.backbone_lr_ratio})
optimizer.param_groups = groups
LOGGER.info(f"PerLayerLR: {len(backbone)} backbone params at {self.backbone_lr_ratio}x the head rate")
return optimizer
model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", epochs=20, trainer=PerLayerLRTrainer)RT-DETRのバリアント#
RT-DETRでは、RTDETRTrainer を親クラスとする同じオーバーライドを使用し、RTDETR("rtdetr-l.pt") でチェックポイントを読み込みます。
マルチGPUトレーニング向けの同期BatchNorm#
DistributedDataParallelを使用して複数のGPUでトレーニングする場合、デフォルトの BatchNorm2d レイヤーは各GPUで独立して統計量を計算します。RT-DETRのファインチューニングや、GPUごとのバッチサイズが小さいその他のレシピでは、GPUごとのバッチ統計量が不安定になることがあります。PyTorchの SyncBatchNorm は、すべてのrank間で平均と分散を同期し、単一のグローバルバッチ統計量を生成します。これにより、GPU間通信のわずかなオーバーヘッドと引き換えに、収束が改善することがあります。
変換はモデルをGPUに配置した後、DDPでラップする前に実行する必要があります。この処理に最適なフックは set_model_attributes() です。BaseTrainer はまさにこのタイミングでこれを呼び出します。
from torch import nn
from ultralytics import RTDETR
from ultralytics.models.rtdetr.train import RTDETRTrainer
class SyncBNTrainer(RTDETRTrainer):
"""RT-DETR trainer that converts BatchNorm to SyncBatchNorm for multi-GPU training."""
def set_model_attributes(self):
"""Run the parent setup, then convert BN to SyncBatchNorm when training on multiple GPUs."""
super().set_model_attributes()
if self.world_size > 1:
self.model = nn.SyncBatchNorm.convert_sync_batchnorm(self.model)
model = RTDETR("rtdetr-l.pt")
model.train(data="coco8.yaml", epochs=20, device=[0, 1], trainer=SyncBNTrainer)world_size > 1 のガードにより、単一GPUでの実行でもtrainerを安全に使用できます。単一GPUでは変換がスキップされ、通常の BatchNorm2d でトレーニングが続行されます。親クラスを DetectionTrainer に切り替えることで、同じパターンをYOLOにも適用できます。
| シナリオ | 推奨事項 |
|---|---|
| マルチGPUトレーニング、GPUごとのバッチが小さい(≤ 16) | 有効化 |
| マルチGPUトレーニング、GPUごとのバッチが大きい(≥ 32) | 任意。効果は小さい |
| 単一GPUトレーニング | 該当なし(スキップ) |
設定可能な勾配クリッピング#
デフォルトのtrainerは、勾配を optimizer_step() 内で max_norm=10.0 にクリップします。これは、勾配が通常この値を超えないYOLOモデル向けに調整された緩やかな値です。DETR系の検出器(RT-DETR、DEIM、DINO)では通常、デコーダーのcross-attentionレイヤーを安定させるために 0.1 のような、より厳しい値を使用します。ここでは勾配の大きさが急増することがあります。クリップ値をオーバーライドするには、trainerをサブクラス化して optimizer_step() をオーバーライドします。
import torch
from ultralytics import RTDETR
from ultralytics.models.rtdetr.train import RTDETRTrainer
from ultralytics.utils.torch_utils import TORCH_2_0
class CustomClipTrainer(RTDETRTrainer):
"""RT-DETR trainer with configurable gradient clipping."""
clip_grad_norm = 0.1 # max gradient norm; set to 0 to disable clipping
def optimizer_step(self):
"""Run an optimizer step with a configurable gradient-norm clip."""
self.scaler.unscale_(self.optimizer)
if self.clip_grad_norm > 0:
kwargs = {"foreach": False} if self.device.type == "npu" and TORCH_2_0 else {}
torch.nn.utils.clip_grad_norm_(self.model.parameters(), max_norm=self.clip_grad_norm, **kwargs)
self.scaler.step(self.optimizer)
self.scaler.update()
self.optimizer.zero_grad()
if self.ema:
self.ema.update(self.model)
model = RTDETR("rtdetr-l.pt")
model.train(data="coco8.yaml", epochs=20, trainer=CustomClipTrainer)同じtrainerをYOLOにも使用できます。親クラスを DetectionTrainer(from ultralytics.models.yolo.detect import DetectionTrainer)に切り替え、YOLO("yolo26n.pt") でYOLOチェックポイントを読み込みます。optimizer_step の本体は変更されません。
| アーキテクチャファミリー | 一般的な max_norm |
|---|---|
| RT-DETR / DEIM / DETRファミリー | 0.1 |
| YOLO(Ultralyticsのデフォルト) | 10.0 |
| クリッピングを無効化 | 0 |
FAQ#
カスタムtrainerクラス(インスタンスではありません)を、
model.train()のtrainerパラメーターに渡します。from ultralytics import YOLO from ultralytics.models.yolo.detect import DetectionTrainer class MyCustomTrainer(DetectionTrainer): """A custom trainer that extends DetectionTrainer.""" model = YOLO("yolo26n.pt") model.train(data="coco8.yaml", trainer=MyCustomTrainer)YOLOクラスは、trainerのインスタンス化を内部で処理します。trainerアーキテクチャの詳細については、高度なカスタマイズのページを参照してください。カスタマイズに使用できる主なメソッドは次のとおりです。
メソッド 目的 validate()検証を実行してメトリクスを返す build_optimizer()optimizerを構築する save_model()トレーニングチェックポイントを保存する get_model()モデルインスタンスを返す get_validator()validatorインスタンスを返す get_dataloader()dataloaderを構築する preprocess_batch()入力バッチを前処理する label_loss_items()ロギング用に損失項目をフォーマットする 完全なAPIリファレンスについては、
BaseTrainerのドキュメントを参照してください。はい。より簡単なカスタマイズでは、コールバックで十分なことがよくあります。利用可能なコールバックイベントには、
on_train_start、on_train_epoch_start、on_train_epoch_end、on_fit_epoch_end、on_model_saveがあります。これらを使用すると、サブクラス化せずにトレーニングループへフックできます。上記のバックボーン凍結の例では、この方法を示しています。変更が単純な場合(損失ゲインの調整など)は、ハイパーパラメーターを直接変更できます。
from ultralytics import YOLO model = YOLO("yolo26n.pt") model.train(data="coco8.yaml", box=10.0, cls=1.5, dfl=2.0)YOLO26では、検出ヘッドが
reg_max: 1を使用するため、dflは記録されるl1_lossをスケーリングします。reg_max > 1を使用するモデルでは、dfl_lossをスケーリングします。