YOLO Vision 2026:

Personalización del entrenador#

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

En esta guía se explican siete personalizaciones habituales:

  1. Registrar métricas personalizadas (puntuación F1) al final de cada época
  2. Añadir pesos 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 descongelarlo después
  5. Especificar tasas de aprendizaje por capa
  6. Sincronizar BatchNorm entre GPU para el entrenamiento con varias GPU
  7. Configurar el recorte de 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."""

    # Add your customizations here

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

Tu entrenador personalizado hereda toda la funcionalidad de DetectionTrainer, por lo que solo tienes que sobrescribir los métodos específicos 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 proporciona acceso a muchas métricas mediante 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 de clase#

Establece cls_pw entre 0.0 y 1.0 para aplicar pesos normalizados inversamente proporcionales a la frecuencia a la pérdida de clasificación. Sobrescribe el cálculo de pesos existente solo cuando necesites proporciones definidas 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 for the production loss owner."""
        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 a una media de 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 el fitness, que para la detección utiliza de forma predeterminada mAP@0.5:0.95 (pondera [0.0, 0.0, 0.0, 1.0] para [P, R, mAP@0.5, mAP@0.5:0.95]). Para utilizar una métrica diferente, como mAP@0.5 o la exhaustividad, sobrescribe validate() y devuelve la métrica elegida como valor de fitness. 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 utilizando la métrica predeterminada, así que captura 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, para permitir 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 utilizar 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 primeras 10 capas (índices 0-9) al inicio del entrenamiento, lo que abarca la mayor parte del backbone de YOLO26. El backbone ocupa las capas 0-10, por lo que freeze=10 deja entrenable el bloque C2PSA final (capa 10); utiliza 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 una vez finalizado el periodo de congelación, al tiempo que mantiene congelados permanentemente los parámetros de DFL y del profesor de destilación.

Elegir qué congelar
  • freeze=10 congela las primeras 10 capas, índices 0-9 (la mayor parte del backbone de YOLO26; utiliza freeze=11 para incluir el bloque C2PSA final de la capa 10)
  • freeze=[0, 1, 2, 3] congela capas específicas 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#

Las distintas partes de la red pueden beneficiarse de diferentes tasas de aprendizaje. Una estrategia habitual consiste en utilizar 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ápidamente 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 de RT-DETR#

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

BatchNorm sincronizado para el entrenamiento con varias GPU#

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

La conversión debe realizarse después de colocar el modelo en la GPU, pero antes de envolverlo con DDP. El punto de conexión más limpio para esto es set_model_attributes(), que BaseTrainer llama exactamente 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 se pueda utilizar de forma segura en ejecuciones con una sola GPU; en una sola GPU se omite la conversión y el entrenamiento continúa con BatchNorm2d normal. El mismo patrón funciona para YOLO cambiando la clase principal a DetectionTrainer.

Cuándo utilizar 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 de gradiente configurable#

El entrenador predeterminado recorta los gradientes a max_norm=10.0 en optimizer_step(), un valor poco restrictivo ajustado para modelos YOLO, cuyos gradientes rara vez lo superan. Los detectores de la familia DETR (RT-DETR, DEIM, DINO) suelen utilizar valores mucho más restrictivos, como 0.1, para estabilizar las capas de atención cruzada del decodificador, donde la magnitud de los gradientes puede aumentar bruscamente. Para sobrescribir 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  # max gradient norm; set to 0 to disable clipping

    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 para YOLO cambiando la clase principal a DetectionTrainer (from ultralytics.models.yolo.detect import DetectionTrainer) y cargando 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 arquitecturasValor habitual de max_norm
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 gestiona internamente la creación de instancias del entrenador. Consulta la página de Personalización avanzada para obtener más información sobre la arquitectura del entrenador.

  • Métodos principales 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 más 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. Te permiten conectarte al bucle de entrenamiento sin crear una subclase. El ejemplo anterior de congelación del backbone demuestra este enfoque.

  • Si tu cambio es más sencillo, como ajustar las ganancias de 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 l1_loss registrado porque su cabeza de detección utiliza reg_max: 1; en los modelos con reg_max > 1, escala dfl_loss.

Comentarios