Ultralytics YOLO27: Únete a la lista de espera.
Get Started

Personalización del entrenador#

El flujo de entrenamiento de Ultralytics se basa en BaseTrainer y en entrenadores específicos para cada tarea, como DetectionTrainer. Estas clases gestionan de forma predeterminada el bucle de entrenamiento, la validación, los puntos de control y el registro. Si necesitas más control —para realizar un seguimiento de métricas personalizadas, ajustar la ponderación de la función de pérdida o implementar programaciones de la tasa de aprendizaje—, puedes crear una subclase del entrenador y sobrescribir métodos concretos.

Esta guía explica siete personalizaciones habituales:

  1. Registrar métricas personalizadas (puntuación F1) al final de cada época
  2. Añadir ponderaciones de clase para gestionar el desequilibrio entre clases
  3. Guardar el mejor modelo según una métrica diferente
  4. Congelar el backbone durante las primeras N épocas y, después, descongelarlo
  5. Especificar tasas de aprendizaje por capa
  6. Sincronizar BatchNorm entre GPU para el entrenamiento con varias GPU
  7. Configurar el recorte del gradiente para ajustar la estabilidad
Requisitos previos

Antes de leer esta guía, asegúrate de conocer los conceptos básicos del entrenamiento de modelos YOLO y la página de personalización avanzada, que explica la arquitectura BaseTrainer.

Cómo funcionan los entrenadores personalizados#

La clase de modelo YOLO acepta un parámetro trainer en el método train(). Esto te permite pasar tu propia clase de entrenador, que amplía el comportamiento predeterminado:

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

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

    # Añade aquí tus personalizaciones

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

Tu entrenador personalizado hereda toda la funcionalidad de DetectionTrainer, así que solo tienes que sobrescribir los métodos concretos que quieras personalizar.

Registrar métricas personalizadas#

El paso de validación calcula la precisión, la exhaustividad y el mAP. Si necesitas métricas adicionales, como la puntuación F1 por clase, sobrescribe 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)

Esto registra la puntuación F1 media de todas las clases representadas en la validación y un desglose por clase después de cada ejecución de validación.

Métricas disponibles

El validador da acceso a muchas métricas a través de self.validator.metrics.box:

AtributoDescripción
f1Puntuación F1 por clase
image_metricsDiccionario de métricas por imagen con precisión, exhaustividad, F1, TP, FP y FN
pPrecisión por clase
rExhaustividad por clase
ap50AP con IoU 0.5 por clase
apAP con IoU 0.5:0.95 por clase
mp, mrPrecisión y exhaustividad medias
map50, mapMétricas AP medias

Añadir pesos por clase#

Establece cls_pw entre 0.0 y 1.0 para aplicar pesos normalizados de frecuencia inversa a la pérdida de clasificación. Sobrescribe el cálculo de pesos existente solo si necesitas proporciones elegidas manualmente:

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() normaliza estos valores para que su media sea 1.0 y los almacena en el modelo, donde la pérdida de detección existente los aplica. Los índices anteriores requieren un conjunto de datos con al menos dos clases.

Guardar el mejor modelo según una métrica personalizada#

El entrenador guarda best.pt según la aptitud, que para la detección es, de forma predeterminada, mAP@0.5:0.95 (ponderaciones [0.0, 0.0, 0.0, 1.0] para [P, R, mAP@0.5, mAP@0.5:0.95]). Para usar una métrica diferente (como mAP@0.5 o la exhaustividad), sobrescribe validate() y devuelve la métrica que elijas como valor de aptitud. El save_model() integrado la utilizará automáticamente:

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() actualiza best_fitness con la métrica predeterminada, así que guarda su valor anterior antes de llamarlo.

Métricas disponibles

Entre las métricas habituales disponibles en self.metrics después de la validación se incluyen:

ClaveDescripción
metrics/precision(B)Precisión
metrics/recall(B)Exhaustividad
metrics/mAP50(B)mAP con IoU 0.5
metrics/mAP50-95(B)mAP con IoU 0.5:0.95

Congelar y descongelar el backbone#

Los flujos de trabajo de aprendizaje por transferencia suelen beneficiarse de congelar el backbone preentrenado durante las primeras N épocas, lo que permite que la cabeza de detección se adapte antes de ajustar toda la red. Ultralytics proporciona un parámetro freeze para congelar capas al inicio del entrenamiento, y puedes usar una devolución de llamada para descongelarlas después de N épocas:

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)

El parámetro freeze=10 congela las 10 primeras capas (índices 0-9) al inicio del entrenamiento, que abarcan la mayor parte del backbone de YOLO26. El backbone comprende las capas 0-10, por lo que freeze=10 deja entrenable el último bloque C2PSA (capa 10); usa freeze=11 para congelar todo el backbone. La devolución de llamada on_train_epoch_start se ejecuta al principio de cada época y descongela las capas solicitadas cuando termina el periodo de congelación, a la vez que mantiene congelados permanentemente los parámetros DFL y del profesor de destilación.

Elegir qué congelar
  • freeze=10 congela las 10 primeras capas, índices 0-9 (la mayor parte del backbone de YOLO26; usa freeze=11 para incluir el último bloque C2PSA en la capa 10)
  • freeze=[0, 1, 2, 3] congela capas concretas por índice
  • Los valores más altos de FREEZE_EPOCHS dan a la cabeza más tiempo para adaptarse antes de que cambie el backbone

Tasas de aprendizaje por capa#

Distintas partes de la red pueden beneficiarse de diferentes tasas de aprendizaje. Una estrategia habitual consiste en usar una tasa de aprendizaje menor para el backbone preentrenado, con el fin de conservar las características aprendidas, y permitir que la cabeza de detección se adapte más rápido con una tasa mayor:

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)

Variante RT-DETR#

Para RT-DETR, usa la misma sobrescritura con RTDETRTrainer como clase base y carga el punto de control con RTDETR("rtdetr-l.pt").

BatchNorm sincronizado para entrenamiento con varias GPU#

Al entrenar con varias GPU mediante DistributedDataParallel, las capas BatchNorm2d predeterminadas calculan estadísticas de forma independiente en cada GPU. En el ajuste fino de RT-DETR y otras configuraciones que usan lotes pequeños por GPU, las estadísticas de los lotes de cada GPU pueden ser ruidosas. SyncBatchNorm de PyTorch sincroniza la media y la varianza en todos los procesos para obtener una única estadística del lote global, lo que suele mejorar la convergencia a costa de una pequeña sobrecarga de comunicación entre GPU.

La conversión debe realizarse después de que el modelo esté en la GPU, pero antes de que DDP lo envuelva. El punto de enganche más sencillo para esto es set_model_attributes(), que BaseTrainer llama justo en ese intervalo:

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)

La comprobación world_size > 1 garantiza que el entrenador también sea seguro para ejecuciones con una sola GPU; en ese caso, se omite la conversión y el entrenamiento continúa con BatchNorm2d normal. El mismo patrón funciona con YOLO si cambias la clase base por DetectionTrainer.

Cuándo usar SyncBatchNorm
EscenarioRecomendación
Entrenamiento con varias GPU y lote pequeño por GPU (≤ 16)Activar
Entrenamiento con varias GPU y lote grande por GPU (≥ 32)Opcional; beneficio menor
Entrenamiento con una sola GPUNo aplicable (se omite)

Recorte del gradiente configurable#

El entrenador predeterminado recorta los gradientes a max_norm=10.0 en optimizer_step(), un valor holgado ajustado para los modelos YOLO, cuyos gradientes rara vez lo superan. Los detectores de la familia DETR (RT-DETR, DEIM, DINO) suelen usar valores mucho más bajos, como 0.1, para estabilizar las capas de atención cruzada del decodificador, donde la magnitud de los gradientes puede dispararse. Para cambiar el valor de recorte, crea una subclase del entrenador y sobrescribe 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  # norma máxima del gradiente; establece 0 para desactivar el recorte

    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)

El mismo entrenador funciona con YOLO si cambias la clase base por DetectionTrainer (from ultralytics.models.yolo.detect import DetectionTrainer) y cargas un punto de control de YOLO con YOLO("yolo26n.pt"). El cuerpo de optimizer_step no cambia.

Valores habituales de `clip_grad_norm`
Familia de arquitecturasmax_norm habitual
Familia RT-DETR / DEIM / DETR0.1
YOLO (valor predeterminado de Ultralytics)10.0
Desactivar el recorte0

Preguntas frecuentes#

  • Pasa tu clase de entrenador personalizada (no una instancia) al parámetro trainer de 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)

    La clase YOLO se encarga internamente de crear una instancia del entrenador. Consulta la página de personalización avanzada para obtener más información sobre la arquitectura del entrenador.

  • Métodos clave disponibles para la personalización:

    MétodoFinalidad
    validate()Ejecutar la validación y devolver las métricas
    build_optimizer()Construir el optimizador
    save_model()Guardar los puntos de control del entrenamiento
    get_model()Devolver la instancia del modelo
    get_validator()Devolver la instancia del validador
    get_dataloader()Crear el cargador de datos
    preprocess_batch()Preprocesar el lote de entrada
    label_loss_items()Dar formato a los elementos de pérdida para el registro

    Para consultar la referencia completa de la API, consulta la documentación de BaseTrainer.

  • Sí; para personalizaciones sencillas, las devoluciones de llamada suelen ser suficientes. Entre los eventos de devolución de llamada disponibles se incluyen on_train_start, on_train_epoch_start, on_train_epoch_end, on_fit_epoch_end y on_model_save. Estos te permiten intervenir en el bucle de entrenamiento sin crear una subclase. El ejemplo anterior de congelación del backbone muestra este enfoque.

  • Si el cambio es más sencillo (por ejemplo, ajustar las ganancias de la pérdida), puedes modificar directamente los hiperparámetros:

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

    En YOLO26, dfl escala el valor registrado l1_loss porque su cabeza de detección usa reg_max: 1; en los modelos con reg_max > 1, escala dfl_loss.

Comentarios