トレーナーのカスタマイズ
Ultralytics パイプラインは、以下の要素を中心に構築されています。 BaseTrainer およびタスク特化型トレーナーのような DetectionTrainerこれらのクラスは、トレーニングループ、検証、チェックポイント保存、ログ記録を標準で処理します。カスタムメトリクスの追跡、損失重みの調整、学習率スケジュールの実装など、より詳細な制御が必要な場合は、トレーナーをサブクラス化し、特定のメソッドをオーバーライドできます。
このガイドでは、5つの一般的なカスタマイズについて順を追って説明します:
- 各エポック終了時にカスタムメトリクス(F1スコア)を記録する
- クラス不均衡に対処するためのクラス重み付けの追加
- 異なる指標に基づいて最適なモデルを保存する
- 最初のNエポックの間、バックボーンを凍結し、その後凍結を解除する
- レイヤーごとの学習率の指定
前提条件
このガイドを読む前に、基本事項について理解していることを確認してください。 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 | AP クラスAP IoU .5単位 |
ap | AP IoU .5:0.95 per class |
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)
損失関数の構造的変更(クラス重みの追加など)を行うには、クラス重みのセクションで示したように、損失関数とモデルをサブクラス化する必要があります。