Ultralytics YOLO27:
Get Started

Trainerのカスタマイズ#

Ultralyticsの学習パイプラインは、BaseTrainerと、DetectionTrainerなどのタスク固有のtrainerを中心に構成されています。これらのクラスは、学習ループ、検証、チェックポイントの保存、ログ記録を標準で処理します。カスタム指標の追跡、損失の重み付けの調整、学習率スケジュールの実装など、より細かく制御する必要がある場合は、trainerをサブクラス化し、特定のメソッドをオーバーライドできます。

このガイドでは、よく使われる次の7つのカスタマイズ方法を説明します。

  1. 各エポックの終了時にカスタム指標(F1スコア)をログに記録する
  2. クラスの不均衡に対処するためのクラス重みの追加
  3. 異なる指標に基づく最良モデルの保存
  4. 最初の N エポックはバックボーンを凍結し、その後凍結を解除する
  5. レイヤーごとの学習率の指定
  6. マルチGPUトレーニング向けにGPU間でBatchNormを同期する
  7. 安定性を調整するための勾配クリッピングの設定
前提条件

このガイドを読む前に、YOLOモデルのトレーニングの基本と、BaseTrainerアーキテクチャを説明している高度なカスタマイズのページに目を通してください。

カスタムトレーナーの仕組み#

YOLOモデルクラスは、train()メソッドでtrainerパラメーターを受け取ります。これにより、デフォルトの動作を拡張する独自のトレーナークラスを渡せます。

from ultralytics import YOLO
from ultralytics.models.yolo.detect import DetectionTrainer

class CustomTrainer(DetectionTrainer):
    """A custom trainer that extends DetectionTrainer with additional functionality."""

    # カスタマイズをここに追加します

model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", epochs=10, trainer=CustomTrainer)

カスタムトレーナーはDetectionTrainerのすべての機能を継承するため、カスタマイズする特定のメソッドだけをオーバーライドすれば十分です。

カスタム指標のログ記録#

検証ステップでは、適合率、再現率、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スコアと、検証の実行ごとのクラス別内訳がログに記録されます。

利用可能な指標

バリデーターでは、self.validator.metrics.boxを通じて多くの指標にアクセスできます。

属性説明
f1クラスごとのF1スコア
image_metrics適合率、再現率、F1、TP、FP、FNを含む画像ごとの指標辞書
pクラスごとの適合率
rクラスごとの再現率
ap50クラスごとのIoU 0.5におけるAP
apクラスごとのIoU 0.5:0.95におけるAP
mp, mr平均適合率と平均再現率
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 that the detection loss applies."""
        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つのクラスを含むデータセットが必要です。

カスタム指標に基づく最良モデルの保存#

トレーナーは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や再現率など)を使うには、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)適合率
metrics/recall(B)再現率
metrics/mAP50(B)IoU 0.5におけるmAP
metrics/mAP50-95(B)IoU 0.5:0.95におけるmAP

バックボーンの凍結と解除#

転移学習のワークフローでは、最初の N エポックにわたって事前学習済みバックボーンを凍結すると、検出ヘッドが適応する時間を確保してからネットワーク全体をファインチューニングできるため、多くの場合効果的です。Ultralyticsでは、トレーニング開始時にレイヤーを凍結するfreezeパラメーターを用意しています。また、コールバックを使って N エポック後に凍結を解除できます。

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コールバックは各エポックの開始時に実行され、凍結期間の終了後に指定したレイヤーの凍結を解除します。その際、恒久的に凍結されたDFLと蒸留教師のパラメーターはそのまま維持されます。

凍結する対象の選択
  • freeze=10は最初の10レイヤー(インデックス0~9、YOLO26バックボーンの大部分)を凍結します。最後のC2PSAブロック(レイヤー10)も含めるには、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はすべてのランク間で平均と分散を同期し、単一のグローバルバッチ統計量を算出します。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の実行でも安全に使用できます。単一GPUでは変換がスキップされ、通常のBatchNorm2dでトレーニングが続行されます。YOLOでも、親クラスをDetectionTrainerに変更すれば同じパターンを使用できます。

SyncBatchNormを使用するタイミング
シナリオ推奨事項
マルチGPUトレーニング、GPUごとのバッチサイズが小さい場合(≤ 16)有効にする
マルチGPUトレーニング、GPUごとのバッチサイズが大きい場合(≥ 32)任意。効果はわずかです
シングルGPUトレーニング該当しません(スキップされます)

設定可能な勾配クリッピング#

デフォルトのトレーナーは、optimizer_step()で勾配をmax_norm=10.0にクリップします。この値は、勾配が通常この値を超えないYOLOモデル向けに調整された緩やかな設定です。DETR系の検出器(RT-DETR、DEIM、DINO)では、デコーダーのクロスアテンション層を安定させるため、通常は0.1のような、より厳しい値を使用します。これらの層では勾配の大きさが急増することがあります。クリップ値をオーバーライドするには、トレーナーをサブクラス化して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  # 最大勾配ノルム。クリッピングを無効にするには0に設定します

    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)

同じトレーナーは、親クラスをDetectionTrainer(from ultralytics.models.yolo.detect import DetectionTrainer)に切り替え、YOLOチェックポイントをYOLO("yolo26n.pt")で読み込むことで、YOLOにも使用できます。optimizer_stepの本体に変更はありません。

一般的な`clip_grad_norm`の値
アーキテクチャの系統一般的なmax_norm
RT-DETR / DEIM / DETR系0.1
YOLO(Ultralyticsのデフォルト)10.0
クリッピングを無効にする0

よくある質問#

  • カスタムトレーナークラス(インスタンスではありません)を、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クラスは、内部でトレーナーのインスタンス化を処理します。トレーナーアーキテクチャの詳細については、高度なカスタマイズのページをご覧ください。

  • カスタマイズできる主なメソッドは次のとおりです。

    メソッド用途
    validate()検証を実行して指標を返します
    build_optimizer()オプティマイザーを構築します
    save_model()トレーニングチェックポイントを保存します
    get_model()モデルインスタンスを返します
    get_validator()バリデーターインスタンスを返します
    get_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をスケーリングします。

コメント