Ultralytics YOLO27: Trag dich auf die Warteliste ein.
Get Started

Trainer anpassen#

Die Ultralytics-Trainingspipeline basiert auf BaseTrainer und aufgabenspezifischen Trainern wie DetectionTrainer. Diese Klassen übernehmen standardmäßig die Trainingsschleife, Validierung, Checkpoint-Erstellung und Protokollierung. Wenn du mehr Kontrolle benötigst – etwa zum Erfassen benutzerdefinierter Metriken, Anpassen der Verlustgewichtung oder Implementieren von Lernratenplänen –, kannst du den Trainer ableiten und bestimmte Methoden überschreiben.

Diese Anleitung behandelt sieben häufige Anpassungen:

  1. Benutzerdefinierte Metriken (F1-Score) protokollieren am Ende jeder Epoche
  2. Klassengewichte hinzufügen, um einem Ungleichgewicht zwischen Klassen entgegenzuwirken
  3. Das beste Modell speichern anhand einer anderen Metrik
  4. Das Backbone einfrieren während der ersten N Epochen und anschließend wieder freigeben
  5. Lernraten für einzelne Schichten festlegen
  6. BatchNorm über mehrere GPUs hinweg synchronisieren für das Training mit mehreren GPUs
  7. Gradientenbeschneidung konfigurieren zur Stabilitätsoptimierung
Voraussetzungen

Bevor du diesen Leitfaden liest, solltest du mit den Grundlagen des Trainierens von YOLO-Modellen und der Seite Erweiterte Anpassung vertraut sein, auf der die Architektur von BaseTrainer behandelt wird.

Funktionsweise benutzerdefinierter Trainer#

Die Modellklasse YOLO akzeptiert einen Parameter trainer in der Methode train(). Dadurch kannst du deine eigene Trainerklasse übergeben, die das Standardverhalten erweitert:

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

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

    # Füge hier deine Anpassungen hinzu

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

Dein benutzerdefinierter Trainer erbt alle Funktionen von DetectionTrainer. Du musst also nur die Methoden überschreiben, die du anpassen möchtest.

Benutzerdefinierte Metriken protokollieren#

Der Schritt Validierung berechnet Precision, Recall und mAP. Wenn du zusätzliche Metriken wie den F1-Score für jede Klasse benötigst, überschreibe 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)

Damit werden nach jedem Validierungslauf der mittlere F1-Score aller in der Validierung vertretenen Klassen sowie eine Aufschlüsselung nach Klassen protokolliert.

Verfügbare Metriken

Über self.validator.metrics.box stellt der Validator zahlreiche Metriken bereit:

AttributBeschreibung
f1F1-Score für jede Klasse
image_metricsMetrikwörterbuch pro Bild mit Precision, Recall, F1, TP, FP und FN
pPrecision für jede Klasse
rRecall für jede Klasse
ap50AP bei IoU 0.5 für jede Klasse
apAP bei IoU 0.5:0.95 für jede Klasse
mp, mrMittlere Precision und Recall
map50, mapMittlere AP-Metriken

Klassengewichte hinzufügen#

Lege cls_pw auf einen Wert zwischen 0.0 und 1.0 fest, um normalisierte inverse Häufigkeitsgewichte auf den Klassifikationsverlust anzuwenden. Überschreibe die vorhandene Gewichtsberechnung nur, wenn du manuell festgelegte Verhältnisse benötigst:

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() normalisiert diese Werte auf einen Mittelwert von 1.0 und speichert sie im Modell, wo der vorhandene Erkennungsverlust sie anwendet. Für die obigen Indizes muss der Datensatz mindestens zwei Klassen enthalten.

Das beste Modell anhand einer benutzerdefinierten Metrik speichern#

Der Trainer speichert best.pt anhand des Fitnesswerts, der bei der Objekterkennung standardmäßig mAP@0.5:0.95 ist (Gewichte [0.0, 0.0, 0.0, 1.0] für [P, R, mAP@0.5, mAP@0.5:0.95]). Wenn du eine andere Metrik verwenden möchtest (etwa mAP@0.5 oder Recall), überschreibe validate() und gib die gewünschte Metrik als Fitnesswert zurück. Die integrierte Funktion save_model() verwendet sie dann automatisch:

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() aktualisiert best_fitness anhand der Standardmetrik. Speichere daher den vorherigen Wert, bevor du die Funktion aufrufst.

Verfügbare Metriken

Zu den gängigen Metriken, die nach der Validierung in self.metrics verfügbar sind, gehören:

SchlüsselBeschreibung
metrics/precision(B)Precision
metrics/recall(B)Recall
metrics/mAP50(B)mAP bei IoU 0.5
metrics/mAP50-95(B)mAP bei IoU 0.5:0.95

Backbone einfrieren und wieder freigeben#

Bei Workflows für Transfer Learning ist es oft sinnvoll, das vortrainierte Backbone während der ersten N Epochen einzufrieren. So kann sich der Erkennungskopf anpassen, bevor das gesamte Netzwerk feinabgestimmt wird. Ultralytics bietet den Parameter freeze, mit dem du zu Beginn des Trainings Schichten einfrieren kannst. Mit einem Callback kannst du sie nach N Epochen wieder freigeben:

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)

Der Parameter freeze=10 friert zu Beginn des Trainings die ersten 10 Schichten (Indizes 0–9) ein. Damit wird der Großteil des YOLO26-Backbones abgedeckt. Das Backbone umfasst die Schichten 0–10. Mit freeze=10 bleibt daher der letzte C2PSA-Block (Schicht 10) trainierbar; verwende freeze=11, um das gesamte Backbone einzufrieren. Der Callback on_train_epoch_start wird zu Beginn jeder Epoche ausgeführt und gibt die angeforderten Schichten nach Ablauf der Einfrierphase wieder frei. Dabei bleiben dauerhaft eingefrorene DFL- und Distillation-Teacher-Parameter unverändert.

Auswahl der einzufrierenden Schichten
  • freeze=10 friert die ersten 10 Schichten mit den Indizes 0–9 ein (den Großteil des YOLO26-Backbones; verwende freeze=11, um den letzten C2PSA-Block in Schicht 10 einzuschließen)
  • freeze=[0, 1, 2, 3] friert bestimmte Schichten anhand ihres Index ein
  • Bei höheren Werten für FREEZE_EPOCHS hat der Kopf mehr Zeit, sich anzupassen, bevor sich das Backbone verändert

Lernraten pro Schicht#

Verschiedene Teile des Netzwerks können von unterschiedlichen Lernraten profitieren. Eine gängige Strategie ist, für das vortrainierte Backbone eine niedrigere Lernrate zu verwenden, um die gelernten Merkmale zu erhalten, während sich der Erkennungskopf mit einer höheren Lernrate schneller anpassen kann:

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-Variante#

Verwende für RT-DETR dieselbe Überschreibung mit RTDETRTrainer als übergeordneter Klasse und lade den Prüfpunkt mit RTDETR("rtdetr-l.pt").

Synchronisiertes BatchNorm für das Training mit mehreren GPUs#

Beim Training auf mehreren GPUs mit DistributedDataParallel berechnen die standardmäßigen BatchNorm2d-Schichten die Statistiken unabhängig auf jeder GPU. Beim Feinabstimmen von RT-DETR und bei anderen Konfigurationen mit kleinen Batch-Größen pro GPU können die Statistiken der einzelnen GPUs verrauscht sein. PyTorchs SyncBatchNorm synchronisiert Mittelwert und Varianz über alle Ranks hinweg, um eine globale Batch-Statistik zu erhalten. Das verbessert häufig die Konvergenz, verursacht aber einen geringen Mehraufwand für die Kommunikation zwischen GPUs.

Die Umwandlung muss erfolgen, nachdem sich das Modell auf der GPU befindet, aber bevor es von DDP umschlossen wird. Der dafür am besten geeignete Hook ist set_model_attributes(), den BaseTrainer genau in diesem Zeitfenster aufruft:

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)

Die Prüfung mit world_size > 1 stellt sicher, dass der Trainer auch bei Läufen mit nur einer GPU sicher verwendet werden kann. Bei einer einzelnen GPU wird die Umwandlung übersprungen und das Training mit dem regulären BatchNorm2d fortgesetzt. Dasselbe Muster funktioniert für YOLO, wenn du die übergeordnete Klasse zu DetectionTrainer änderst.

Wann sollte SyncBatchNorm verwendet werden?
SzenarioEmpfehlung
Training mit mehreren GPUs, kleine Batch-Größe pro GPU (≤ 16)Aktivieren
Training mit mehreren GPUs, große Batch-Größe pro GPU (≥ 32)Optional; geringer Vorteil
Training mit einer GPUNicht zutreffend (wird übersprungen)

Konfigurierbare Gradientenbeschneidung#

Der Standardtrainer beschneidet Gradienten in optimizer_step() auf max_norm=10.0. Dieser großzügig bemessene Wert ist auf YOLO-Modelle abgestimmt, deren Gradienten ihn selten überschreiten. Detektoren der DETR-Familie (RT-DETR, DEIM, DINO) verwenden zur Stabilisierung der Cross-Attention-Schichten des Decoders normalerweise deutlich niedrigere Werte wie 0.1, da dort die Gradientenbeträge stark ansteigen können. Um den Beschneidungswert zu ändern, leite eine eigene Trainerklasse ab und überschreibe 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  # Maximale Gradientennorm; auf 0 setzen, um die Beschneidung zu deaktivieren

    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)

Derselbe Trainer funktioniert auch für YOLO, wenn du die übergeordnete Klasse zu DetectionTrainer (from ultralytics.models.yolo.detect import DetectionTrainer) änderst und einen YOLO-Prüfpunkt mit YOLO("yolo26n.pt") lädst. Der Inhalt von optimizer_step bleibt unverändert.

Typische Werte für `clip_grad_norm`
ArchitekturfamilieTypischer Wert für max_norm
RT-DETR- / DEIM- / DETR-Familie0.1
YOLO (Ultralytics-Standard)10.0
Beschneidung deaktivieren0

Häufig gestellte Fragen#

  • Übergib deine benutzerdefinierte Trainerklasse (keine Instanz) als Parameter trainer an model.train():

    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)

    Die Klasse YOLO übernimmt intern die Instanziierung des Trainers. Weitere Informationen zur Trainerarchitektur findest du auf der Seite Erweiterte Anpassung.

  • Wichtige Methoden zur Anpassung:

    MethodeZweck
    validate()Validierung ausführen und Metriken zurückgeben
    build_optimizer()Den Optimierer erstellen
    save_model()Prüfpunkte des Trainings speichern
    get_model()Die Modellinstanz zurückgeben
    get_validator()Die Validatorinstanz zurückgeben
    get_dataloader()Den Datenlader erstellen
    preprocess_batch()Den Eingabebatch vorverarbeiten
    label_loss_items()Verlustwerte für die Protokollierung formatieren

    Die vollständige API-Referenz findest du in der Dokumentation zu BaseTrainer.

  • Ja, für einfachere Anpassungen reichen Callbacks oft aus. Zu den verfügbaren Callback-Ereignissen gehören on_train_start, on_train_epoch_start, on_train_epoch_end, on_fit_epoch_end und on_model_save. Damit kannst du dich in die Trainingsschleife einklinken, ohne eine Unterklasse zu erstellen. Das obige Beispiel zum Einfrieren des Backbones zeigt diese Vorgehensweise.

  • Wenn deine Änderung einfacher ist (etwa die Anpassung von Verlustgewichten), kannst du die Hyperparameter direkt ändern:

    from ultralytics import YOLO
    
    model = YOLO("yolo26n.pt")
    model.train(data="coco8.yaml", box=10.0, cls=1.5, dfl=2.0)

    Bei YOLO26 skaliert dfl den protokollierten Wert l1_loss, da der Erkennungskopf reg_max: 1 verwendet. Bei Modellen mit reg_max > 1 wird stattdessen dfl_loss skaliert.

Kommentare