Vai al contenuto

Personalizzazione Trainer

Il percorso Ultralytics è incentrato su BaseTrainer e formatori specifici per determinati compiti come DetectionTrainerQueste classi gestiscono il ciclo di addestramento, la convalida, il checkpointing e la registrazione immediatamente. Quando è necessario un maggiore controllo, ad esempio per monitorare metriche personalizzate, regolare la ponderazione delle perdite o implementare programmi di apprendimento, è possibile creare una sottoclasse del trainer e sovrascrivere metodi specifici.

Questa guida illustra cinque personalizzazioni comuni:

  1. Registrazione delle metriche personalizzate (punteggio F1) alla fine di ogni epoca
  2. Aggiunta di pesi alle classi per gestire lo squilibrio tra le classi
  3. Salvataggio del modello migliore in base a una metrica diversa
  4. Congelamento della struttura portante per i primi N periodi, quindi scongelamento
  5. Specificare i tassi di apprendimento per livello

Prerequisiti

Prima di leggere questa guida, assicurati di avere familiarità con le nozioni di base relative a addestramento YOLO che l' Personalizzazione avanzata pagina, che copre il BaseTrainer architettura.

Come funzionano gli allenatori personalizzati

Il YOLO La classe modello accetta un trainer parametro in the train() metodo. Ciò consente di passare la propria classe trainer che estende il comportamento predefinito:

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)

Il tuo trainer personalizzato eredita tutte le funzionalità da DetectionTrainer, quindi è sufficiente sovrascrivere solo i metodi specifici che si desidera personalizzare.

Registrazione di metriche personalizzate

Il convalida calcolo dei passi precisione, richiamo, e mAP. Se hai bisogno di metriche aggiuntive come quelle relative alle singole classi Punteggio F1, sovrascrivere 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)

Questo registra il punteggio F1 medio per tutte le classi e una ripartizione per classe dopo ogni esecuzione di convalida.

Metriche Disponibili

Il validatore fornisce l'accesso a numerose metriche tramite self.validator.metrics.box:

AttributoDescrizione
f1Punteggio F1 per classe
pPrecisione per classe
rRichiamo per classe
ap50AP IoU ,5 per classe
apAP IoU ,5:0,95 per classe
mp, mrPrecisione e richiamo medi
map50, mapAP medie

Aggiunta dei pesi delle classi

Se il set di dati presenta classi sbilanciate (ad esempio, un difetto raro nell'ispezione di produzione), è possibile aumentare il peso delle classi sottorappresentate nella funzione di perdita. In questo modo il modello penalizza maggiormente le classificazioni errate sulle classi rare.

Per personalizzare la perdita, creare sottoclassi delle classi di perdita, del modello e del 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)

Calcolo dei pesi dal set di dati

È possibile calcolare automaticamente i pesi delle classi dalla distribuzione delle etichette del set di dati. Un approccio comune è la ponderazione inversa della frequenza:

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]

Salvataggio del modello migliore in base a metriche personalizzate

L'allenatore salva best.pt basato sul fitness, il cui valore predefinito è 0.9 × mAP@0.5:0.95 + 0.1 × mAP@0.5. Per utilizzare una metrica diversa (come mAP@0.5 o richiamo), sovrascrivere validate() e restituisce la metrica scelta come valore di fitness. Il save_model() lo utilizzerà automaticamente:

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)

Metriche Disponibili

Metriche comuni disponibili in self.metrics dopo la convalida includono:

ChiaveDescrizione
metrics/precision(B)Precisione
metrics/recall(B)Recall
metrics/mAP50(B)mAP IoU ,5
metrics/mAP50-95(B)mAP IoU ,5:0,95

Congelamento e scongelamento della dorsale

Apprendimento trasferito I flussi di lavoro spesso traggono vantaggio dal congelamento della struttura pre-addestrata per i primi N cicli, consentendo alla testa di rilevamento di adattarsi prima di messa a punto l'intera rete. Ultralytics un freeze parametro per congelare i livelli all'inizio dell'addestramento, ed è possibile utilizzare un callback per sbloccarli dopo N epoche:

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)

Il freeze=10 Il parametro congela i primi 10 livelli (la struttura portante) all'inizio dell'addestramento. Il on_train_epoch_start Il callback viene attivato all'inizio di ogni epoca e sblocca tutti i parametri una volta completato il periodo di congelamento.

Scegliere cosa congelare

  • freeze=10 congela i primi 10 livelli (tipicamente la struttura portante nelle YOLO )
  • freeze=[0, 1, 2, 3] blocca livelli specifici in base all'indice
  • Maggiore FREEZE_EPOCHS i valori danno alla testa più tempo per adattarsi prima che la colonna vertebrale cambi

Tassi di apprendimento per livello

Diverse parti della rete possono trarre vantaggio da diversi tassi di apprendimento. Una strategia comune consiste nell'utilizzare un tasso di apprendimento più basso per la struttura pre-addestrata, al fine di preservare le caratteristiche apprese, consentendo al contempo alla testa di rilevamento di adattarsi più rapidamente con un tasso più elevato:

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)

Programmatore della velocità di apprendimento

Il programmatore della velocità di apprendimento integrato (cosine oppure linear) si applica ancora oltre ai tassi di apprendimento di base per gruppo. Sia il tasso di apprendimento backbone che quello head seguiranno lo stesso programma di decadimento, mantenendo il rapporto tra loro durante tutto l'addestramento.

Combinazione di tecniche

Queste personalizzazioni possono essere combinate in un'unica classe trainer sovrascrivendo più metodi e aggiungendo callback secondo necessità.

FAQ

Come posso passare un trainer personalizzato a YOLO?

Passa la tua classe trainer personalizzata (non un'istanza) al trainer parametro in model.train():

from ultralytics import YOLO

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

Il YOLO La classe gestisce internamente l'istanziazione del trainer. Vedere il Personalizzazione avanzata pagina per maggiori dettagli sull'architettura del trainer.

Quali metodi BaseTrainer posso sovrascrivere?

Principali metodi disponibili per la personalizzazione:

MetodoScopo
validate()Esegui la convalida e restituisci le metriche
build_optimizer()Costruire l'ottimizzatore
save_model()Salva i punti di controllo dell'allenamento
get_model()Restituisce l'istanza del modello
get_validator()Restituisce l'istanza del validatore
get_dataloader()Costruisci il dataloader
preprocess_batch()Pre-elaborazione batch di input
label_loss_items()Formatta gli elementi di perdita per la registrazione

Per il riferimento completo all'API, consultare il BaseTrainer documentazione.

Posso usare i callback invece di sottoclassificare il trainer?

Sì, per personalizzazioni più semplici, richiami sono spesso sufficienti. Gli eventi di callback disponibili includono on_train_start, on_train_epoch_start, on_train_epoch_end, on_fit_epoch_end, e on_model_save. Questi consentono di collegarsi al ciclo di formazione senza creare sottoclassi. L'esempio di congelamento della struttura portante sopra riportato illustra questo approccio.

Come posso personalizzare la funzione di perdita senza creare una sottoclasse del modello?

Se la modifica è più semplice (ad esempio la regolazione dei guadagni di perdita), è possibile modificare direttamente gli iperparametri:

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

Per apportare modifiche strutturali alla perdita (come l'aggiunta di pesi di classe), è necessario creare una sottoclasse della perdita e del modello come mostrato nella sezione dedicata ai pesi di classe.



📅 Creato 6 giorni fa ✏️ Aggiornato 0 giorni fa
raimbekovmonuralpszr

Commenti