コンテンツにスキップ

トレーナーのカスタマイズ

Ultralytics パイプラインは、以下の要素を中心に構築されています。 BaseTrainer タスク固有のトレーナー(例: DetectionTrainer。これらのクラスは、トレーニングループ、バリデーション、チェックポイント、およびロギングをすぐに処理します。カスタムメトリクスの追跡、損失重みの調整、学習率スケジュールの実装など、より詳細な制御が必要な場合は、トレーナーをサブクラス化し、特定のメソッドをオーバーライドできます。

このガイドでは、5つの一般的なカスタマイズについて順を追って説明します:

  1. カスタムメトリクス(F1スコア)のログ記録を各エポックの終わりに
  2. クラス重みの追加によりクラスの不均衡を処理する
  3. 最適なモデルの保存を異なるメトリクスに基づいて
  4. バックボーンの凍結を最初のNエポックの間行い、その後凍結解除する
  5. レイヤーごとの学習率の指定

前提条件

このガイドを読む前に、の基本を理解していることを確認してください。 YOLO トレーニング の両方のエンドポイントを、以前と同じcURLコマンドでテストできます。 高度なカスタマイズ ページ、これは以下の内容をカバーしています BaseTrainer アーキテクチャ。

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

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

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


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

    pass  # Add your customizations here


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

カスタムトレーナーはすべての機能を継承します DetectionTrainer、そのため、カスタマイズしたい特定のメソッドのみをオーバーライドすればよいです。

カスタムメトリクスのロギング

The 検証 ステップ計算 適合率, 再現率、および 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

            valid_f1 = f1_per_class[f1_per_class > 0]
            mean_f1 = np.mean(valid_f1) if len(valid_f1) > 0 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) if f1_per_class[j] > 0
            ]
            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スコア
pクラスごとの精度
rクラスごとのリコール
ap50クラスごとのIoU 0.5でのAP
apクラスごとのIoU 0.5:0.95でのAP
mp, mr平均精度と平均再現率
map50, map平均AP

クラスウェイトの追加

データセットに不均衡なクラス(例:製造検査における稀な欠陥)が含まれる場合、損失関数において過小評価されているクラスを重み付けできます。これにより、モデルは稀なクラスへの誤分類をより厳しく罰するようになります。

損失をカスタマイズするには、損失クラス、モデル、およびトレーナーをサブクラス化します:

import torch
from torch import nn

from ultralytics import YOLO
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.nn.tasks import DetectionModel
from ultralytics.utils import RANK
from ultralytics.utils.loss import E2ELoss, v8DetectionLoss


class WeightedDetectionLoss(v8DetectionLoss):
    """Detection loss with class weights applied to BCE classification loss."""

    def __init__(self, model, class_weights=None, tal_topk=10, tal_topk2=None):
        """Initialize loss with optional per-class weights for BCE."""
        super().__init__(model, tal_topk=tal_topk, tal_topk2=tal_topk2)
        if class_weights is not None:
            self.bce = nn.BCEWithLogitsLoss(
                pos_weight=class_weights.to(self.device),
                reduction="none",
            )


class WeightedE2ELoss(E2ELoss):
    """E2E Loss with class weights for YOLO26."""

    def __init__(self, model, class_weights=None):
        """Initialize E2E loss with weighted detection loss."""

        def weighted_loss_fn(model, tal_topk=10, tal_topk2=None):
            return WeightedDetectionLoss(model, class_weights=class_weights, tal_topk=tal_topk, tal_topk2=tal_topk2)

        super().__init__(model, loss_fn=weighted_loss_fn)


class WeightedDetectionModel(DetectionModel):
    """Detection model that uses class-weighted loss."""

    def init_criterion(self):
        """Initialize weighted loss criterion with per-class weights."""
        class_weights = torch.ones(self.nc)
        class_weights[0] = 2.0  # upweight class 0
        class_weights[1] = 3.0  # upweight rare class 1
        return WeightedE2ELoss(self, class_weights=class_weights)


class WeightedTrainer(DetectionTrainer):
    """Trainer that returns a WeightedDetectionModel."""

    def get_model(self, cfg=None, weights=None, verbose=True):
        """Return a WeightedDetectionModel."""
        model = WeightedDetectionModel(cfg, nc=self.data["nc"], verbose=verbose and RANK == -1)
        if weights:
            model.load(weights)
        return model


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

データセットからの重み計算

データセットのラベル分布からクラス重みを自動的に計算できます。一般的な手法は逆頻度加重です:

import numpy as np

# class_counts: number of instances per class
class_counts = np.array([5000, 200, 3000])
# Inverse frequency: rarer classes get higher weight
class_weights = max(class_counts) / class_counts
# Result: [1.0, 25.0, 1.67]

カスタム指標による最良モデルの保存

トレーナーが保存する best.pt フィットネスに基づいており、デフォルトは 0.9 × mAP@0.5:0.95 + 0.1 × mAP@0.5。異なるメトリクス(例えば mAP@0.5 またはリコール)、オーバーライド validate() 選択したメトリックを適合度として返します。組み込みの 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."""
        metrics, fitness = super().validate()
        if metrics:
            fitness = metrics.get("metrics/mAP50(B)", fitness)
            if self.best_fitness is None or fitness > self.best_fitness:
                self.best_fitness = fitness
        return metrics, fitness


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

利用可能なメトリクス

で利用可能な一般的なメトリクス self.metrics 検証後には以下が含まれます。

キー説明
metrics/precision(B)適合率
metrics/recall(B)再現率
metrics/mAP50(B)mAP IoU .IoU )
metrics/mAP50-95(B)IoU .mAP : 0.95

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

転移学習 ワークフローでは、最初の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 all layers after FREEZE_EPOCHS."""
    if trainer.epoch == FREEZE_EPOCHS:
        LOGGER.info(f"Epoch {trainer.epoch}: Unfreezing all layers for fine-tuning")
        for name, param in trainer.model.named_parameters():
            if not param.requires_grad:
                param.requires_grad = True
                LOGGER.info(f"  Unfroze: {name}")
        trainer.freeze_layer_names = [".dfl"]


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)

The freeze=10 パラメータは、学習開始時に最初の10層(バックボーン)を固定します。 on_train_epoch_start コールバックは各エポックの開始時に起動し、凍結期間が完了するとすべてのパラメータの凍結を解除します。

フリーズするものの選択

  • freeze=10 最初の10層(通常はYOLO バックボーン)を凍結する
  • freeze=[0, 1, 2, 3] 特定のレイヤーをインデックスで凍結する
  • より高い FREEZE_EPOCHS 値は、背骨が変化する前に頭部が適応するための時間をより多く与える

レイヤーごとの学習率

ネットワークの異なる部分は、異なる学習率から恩恵を受けることができます。一般的な戦略は、学習済みの特徴を保持するために事前学習済みバックボーンには低い学習率を使用し、検出ヘッドには高い学習率でより迅速に適応させることです。

import torch

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."""

    def build_optimizer(self, model, name="auto", lr=0.001, momentum=0.9, decay=1e-5, iterations=1e5):
        """Build optimizer with separate learning rates for backbone and head."""
        backbone_params = []
        head_params = []

        for k, v in unwrap_model(model).named_parameters():
            if not v.requires_grad:
                continue
            is_backbone = any(k.startswith(f"model.{i}.") for i in range(10))
            if is_backbone:
                backbone_params.append(v)
            else:
                head_params.append(v)

        backbone_lr = lr * 0.1

        optimizer = torch.optim.AdamW(
            [
                {"params": backbone_params, "lr": backbone_lr, "weight_decay": decay},
                {"params": head_params, "lr": lr, "weight_decay": decay},
            ],
        )

        LOGGER.info(
            f"PerLayerLR optimizer: backbone ({len(backbone_params)} params, lr={backbone_lr}) "
            f"| head ({len(head_params)} params, lr={lr})"
        )
        return optimizer


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

学習率スケジューラ

組み込みの学習率スケジューラ (cosine または linear)は、グループごとの基本学習率に加えて適用されます。バックボーンとヘッドの両方の学習率は同じ減衰スケジュールに従い、トレーニング全体を通してそれらの比率を維持します。

手法の組み合わせ

これらのカスタマイズは、複数のメソッドを上書きし、必要に応じてコールバックを追加することで、単一のトレーナークラスに統合できます。

よくある質問

YOLOにカスタムトレーナーを渡すにはどうすればよいですか?

カスタムトレーナークラス(インスタンスではなく)を trainer のパラメータ model.train():

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", trainer=MyCustomTrainer)

The YOLO クラスはトレーナーのインスタンス化を内部的に処理します。参照: 高度なカスタマイズ トレーナーのアーキテクチャに関する詳細については、こちらのページをご覧ください。

どのBaseTrainerメソッドをオーバーライドできますか?

カスタマイズに利用可能な主な方法:

メソッド目的
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。これらにより、サブクラス化せずにトレーニングループにフックできます。上記のバックボーンフリーズの例がこのアプローチを示しています。

モデルをサブクラス化せずに損失関数をカスタマイズするにはどうすればよいですか?

変更がより単純な場合(損失項の調整など)、ハイパーパラメータを直接変更できます:

model.train(data="coco8.yaml", box=10.0, cls=1.5, dfl=2.0)

損失の構造的な変更(クラス重みの追加など)を行う場合は、クラス重みセクションに示すように、損失とモデルをサブクラス化する必要があります。



📅 1か月前に作成 ✏️ 1か月前に更新
raimbekovmonuralpszr

コメント