Zum Inhalt springen

Anpassungstrainer

Die Ultralytics basiert auf BaseTrainer und aufgabenspezifische Trainer wie DetectionTrainerDiese Klassen übernehmen die Trainingsschleife, Validierung, Checkpointing und Protokollierung sofort nach der Installation. Wenn Sie mehr Kontrolle benötigen – beispielsweise um benutzerdefinierte Metriken zu verfolgen, die Verlustgewichtung anzupassen oder Lernratenpläne zu implementieren –, können Sie eine Unterklasse des Trainers erstellen und bestimmte Methoden überschreiben.

Dieser Leitfaden führt Sie durch fünf gängige Anpassungen:

  1. Protokollierung benutzerdefinierter Metriken (F1-Score) am Ende jeder Epoche
  2. Hinzufügen von Klassengewichten zur Behandlung von Klassenungleichgewichten
  3. Das beste Modell basierend auf einer anderen Metrik speichern
  4. Das Backbone für die ersten N Epochen einfrieren, dann auftauen
  5. Festlegen der Lernraten pro Schicht

Voraussetzungen

Bevor Sie diesen Leitfaden lesen, sollten Sie sich mit den Grundlagen von Training von YOLO und das Erweiterte Anpassung Seite, die sich mit dem Thema befasst BaseTrainer Architektur.

Wie maßgeschneiderte Trainer funktionieren

Die YOLO Die Modellklasse akzeptiert eine trainer Parameter in der train() Methode. Auf diese Weise können Sie Ihre 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."""

    pass  # Add your customizations here


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

Ihr benutzerdefinierter Trainer übernimmt alle Funktionen von DetectionTrainerSie müssen also nur die spezifischen Methoden überschreiben, die Sie anpassen möchten.

Benutzerdefinierte Metriken protokollieren

Die Validierung Schritt berechnet Präzision, Recallund mAPWenn Sie zusätzliche Metriken wie pro Klasse benötigen F1-Score, überschreiben 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)

Hiermit wird der durchschnittliche F1-Score für alle Klassen und eine Aufschlüsselung nach Klassen nach jedem Validierungslauf protokolliert.

Verfügbare Metriken

Der Validator bietet Zugriff auf viele Metriken über self.validator.metrics.box:

AttributBeschreibung
f1F1-Score pro Klasse
pGenauigkeit pro Klasse
rRückruf pro Klasse
ap50AP IoU ,5 pro Klasse
apAP IoU ,5:0,95 pro Klasse
mp, mrMittlere Präzision und Rückrufquote
map50, mapMittlere AP

Klassengewichte hinzufügen

Wenn Ihr Datensatz unausgewogene Klassen enthält (z. B. einen seltenen Fehler bei der Fertigungsprüfung), können Sie unterrepräsentierte Klassen in der Verlustfunktion stärker gewichten. Dadurch bestraft das Modell Fehlklassifikationen bei seltenen Klassen stärker.

Um den Verlust anzupassen, erstellen Sie Unterklassen für die Verlustklassen, das Modell und den Trainer:

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)

Berechnung von Gewichten aus Datensatz

Sie können die Klassengewichte automatisch aus der Label-Verteilung Ihres Datensatzes berechnen. Ein gängiger Ansatz ist die inverse Frequenzgewichtung:

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]

Das beste Modell anhand einer benutzerdefinierten Metrik speichern

Der Trainer speichert best.pt basierend auf der Fitness, die standardmäßig auf 0.9 × mAP@0.5:0.95 + 0.1 × mAP@0.5Um eine andere Metrik zu verwenden (wie mAP@0.5 oder Rückruf), Überschreiben validate() und gibt die von Ihnen gewählte Metrik als Fitnesswert zurück. Die integrierte save_model() wird es dann automatisch verwenden:

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)

Verfügbare Metriken

Allgemeine Metriken verfügbar in self.metrics nach der Validierung umfassen:

SchlüsselBeschreibung
metrics/precision(B)Präzision
metrics/recall(B)Recall
metrics/mAP50(B)mAP IoU ,5
metrics/mAP50-95(B)mAP IoU ,5: 0,95

Einfrieren und Auftauen des Backbones

Transferlernen Workflows profitieren oft davon, wenn das vortrainierte Backbone für die ersten N Epochen eingefroren wird, damit sich der Erkennungskopf anpassen kann, bevor Feinabstimmung das gesamte Netzwerk. Ultralytics eine freeze Parameter, um Ebenen zu Beginn des Trainings einzufrieren, und Sie können einen Callback um sie nach N Epochen wieder freizugeben:

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)

Die freeze=10 Der Parameter friert die ersten 10 Schichten (das Backbone) zu Beginn des Trainings ein. Der on_train_epoch_start Der Callback wird zu Beginn jeder Epoche ausgelöst und hebt alle Parameter auf, sobald die Sperrfrist abgelaufen ist.

Auswahl der einzufrierenden Produkte

  • freeze=10 friert die ersten 10 Schichten ein (in der Regel das Rückgrat in YOLO )
  • freeze=[0, 1, 2, 3] friert bestimmte Ebenen nach Index ein
  • Höher FREEZE_EPOCHS Werte geben dem Kopf mehr Zeit, sich anzupassen, bevor sich die Wirbelsäule verändert.

Lernraten pro Schicht

Verschiedene Teile des Netzwerks können von unterschiedlichen Lernraten profitieren. Eine gängige Strategie besteht darin, 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 Rate schneller anpassen kann:

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)

Lernratenplaner

Der integrierte Lernratenschalter (cosine oder linear) gilt zusätzlich zu den gruppenbezogenen Basis-Lernraten. Sowohl die Backbone- als auch die Head-Lernraten folgen dem gleichen Abklingplan, wobei das Verhältnis zwischen ihnen während des gesamten Trainings beibehalten wird.

Kombination von Techniken

Diese Anpassungen können zu einer einzigen Trainerklasse kombiniert werden, indem mehrere Methoden überschrieben und bei Bedarf Callbacks hinzugefügt werden.

FAQ

Wie übergebe ich einen benutzerdefinierten Trainer an YOLO?

Übergeben Sie Ihre benutzerdefinierte Trainerklasse (keine Instanz) an die trainer Parameter in model.train():

from ultralytics import YOLO

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

Die YOLO Die Klasse übernimmt intern die Instanziierung des Trainers. Siehe die Erweiterte Anpassung Seite für weitere Details zur Trainer-Architektur.

Welche BaseTrainer-Methoden kann ich überschreiben?

Wichtige Methoden zur Anpassung:

MethodeZweck
validate()Validierung durchführen und Metriken zurückgeben
build_optimizer()Konstruieren Sie den Optimierer
save_model()Trainings-Checkpoints speichern
get_model()Die Modellinstanz zurückgeben
get_validator()Die Validator-Instanz zurückgeben
get_dataloader()Erstellen Sie den Datenlader.
preprocess_batch()Eingabebatch vorverarbeiten
label_loss_items()Formatverlustelemente für die Protokollierung formatieren

Die vollständige API-Referenz finden Sie unter BaseTrainer Dokumentation.

Kann ich Callbacks anstelle von Unterklassen des Trainers verwenden?

Ja, für einfachere Anpassungen, Rückrufe sind oft ausreichend. Zu den verfügbaren Callback-Ereignissen gehören on_train_start, on_train_epoch_start, on_train_epoch_end, on_fit_epoch_endund on_model_save. Diese ermöglichen es Ihnen, sich in die Trainingsschleife einzuklinken, ohne eine Unterklasse zu erstellen. Das obige Beispiel zum Einfrieren des Backbones veranschaulicht diesen Ansatz.

Wie kann ich die Verlustfunktion anpassen, ohne das Modell zu unterklassen?

Wenn Ihre Änderung einfacher ist (z. B. die Anpassung von Verlustgewinnen), können Sie die Hyperparameter direkt ändern:

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

Für strukturelle Änderungen am Verlust (z. B. Hinzufügen von Klassengewichten) müssen Sie den Verlust und das Modell wie im Abschnitt „Klassengewichte” gezeigt unterteilen.



📅 Erstellt vor 6 Tagen ✏️ Aktualisiert vor 0 Tagen
raimbekovmonuralpszr

Kommentare