YOLO Vision 2026:

Настройка Trainer#

Конвейер обучения Ultralytics построен вокруг BaseTrainer и специализированных Trainer, таких как DetectionTrainer. Эти классы из коробки обрабатывают цикл обучения, валидацию, создание контрольных точек и ведение журналов. Если тебе нужен больший контроль — например, отслеживание пользовательских метрик, настройка весов функции потерь или реализация расписаний скорости обучения, — ты можешь унаследовать свой класс от Trainer и переопределить нужные методы.

В этом руководстве рассматриваются семь распространённых вариантов настройки:

  1. Регистрация пользовательских метрик (F1 score) в конце каждой эпохи
  2. Добавление весов классов для обработки дисбаланса классов
  3. Сохранение лучшей модели на основе другой метрики
  4. Заморозка backbone на первые N эпох с последующей разморозкой
  5. Задание скорости обучения для каждого слоя
  6. Синхронизация BatchNorm между GPU для обучения на нескольких GPU
  7. Настройка клиппинга градиентов для стабилизации обучения
Предварительные требования

Перед чтением этого руководства убедись, что ты знаком с основами обучения моделей YOLO и страницей Расширенная настройка, где рассматривается архитектура BaseTrainer.

Как работают пользовательские Trainer#

Класс модели YOLO принимает параметр trainer в методе train(). Это позволяет передать собственный класс Trainer, расширяющий поведение по умолчанию:

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)

Твой пользовательский Trainer наследует всю функциональность DetectionTrainer, поэтому тебе нужно переопределить только те методы, которые ты хочешь настроить.

Регистрация пользовательских метрик#

На этапе валидации вычисляются precision, recall и mAP. Если тебе нужны дополнительные метрики, например F1 score для каждого класса, переопредели 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)

После каждого запуска валидации этот код регистрирует среднее значение F1 score по всем представленным в валидации классам и разбивку по классам.

Доступные метрики

Validator предоставляет доступ ко множеству метрик через self.validator.metrics.box:

АтрибутОписание
f1F1 score для каждого класса
image_metricsСловарь метрик для каждого изображения с precision, recall, F1, TP, FP и FN
pPrecision для каждого класса
rRecall для каждого класса
ap50AP при IoU 0.5 для каждого класса
apAP при IoU 0.5:0.95 для каждого класса
mp, mrСредние precision и recall
map50, mapСредние метрики AP

Добавление весов классов#

Задай cls_pw между 0.0 и 1.0, чтобы применить нормализованные веса, обратно пропорциональные частоте классов, к функции потерь классификации. Переопределяй существующее вычисление весов только в тех случаях, когда тебе нужны заданные вручную соотношения:

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() нормализует эти значения до среднего 1.0 и сохраняет их в модели, где существующая функция потерь детектирования применяет их. Для указанных выше индексов нужен датасет как минимум с двумя классами.

Сохранение лучшей модели по пользовательской метрике#

Trainer сохраняет best.pt на основе fitness, который для детектирования по умолчанию равен mAP@0.5:0.95 (веса [0.0, 0.0, 0.0, 1.0] для [P, R, mAP@0.5, mAP@0.5:0.95]). Чтобы использовать другую метрику, например mAP@0.5 или recall, переопредели validate() и верни выбранную метрику в качестве значения fitness. Встроенный save_model() затем использует её автоматически:

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() обновляет best_fitness с использованием метрики по умолчанию, поэтому сохрани её предыдущее значение до вызова этого метода.

Доступные метрики

К распространённым метрикам, доступным в self.metrics после валидации, относятся:

КлючОписание
metrics/precision(B)Precision
metrics/recall(B)Recall
metrics/mAP50(B)mAP при IoU 0.5
metrics/mAP50-95(B)mAP при IoU 0.5:0.95

Заморозка и разморозка backbone#

В процессах transfer learning часто полезно заморозить предварительно обученный backbone на первые N эпох, чтобы голова детектирования адаптировалась перед дообучением всей сети. Ultralytics предоставляет параметр freeze для заморозки слоёв в начале обучения, а после N эпох ты можешь разморозить их с помощью callback:

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)

Параметр freeze=10 замораживает первые 10 слоёв (индексы 0–9) в начале обучения, охватывая большую часть backbone YOLO26. Backbone включает слои 0–10, поэтому freeze=10 оставляет последний блок C2PSA (слой 10) обучаемым; используй freeze=11, чтобы заморозить весь backbone. Callback on_train_epoch_start выполняется в начале каждой эпохи и размораживает запрошенные слои после завершения периода заморозки, сохраняя навсегда замороженными параметры DFL и учителя дистилляции.

Выбор замораживаемых слоёв
  • freeze=10 замораживает первые 10 слоёв, индексы 0–9 (большую часть backbone YOLO26; используй freeze=11, чтобы включить последний блок C2PSA на слое 10)
  • freeze=[0, 1, 2, 3] замораживает определённые слои по индексу
  • Более высокие значения FREEZE_EPOCHS дают голове больше времени на адаптацию до изменения backbone

Скорость обучения для каждого слоя#

Разные части сети могут эффективно обучаться с разными скоростями обучения. Распространённая стратегия — использовать меньшую скорость обучения для предварительно обученного backbone, чтобы сохранить выученные признаки, и позволить голове детектирования быстрее адаптироваться с более высокой скоростью:

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#

Для RT-DETR используй такое же переопределение с RTDETRTrainer в качестве родительского класса и загружай контрольную точку с помощью RTDETR("rtdetr-l.pt").

Синхронизированный BatchNorm для обучения на нескольких GPU#

При обучении на нескольких GPU с помощью DistributedDataParallel слои BatchNorm2d по умолчанию вычисляют статистики независимо на каждом GPU. При дообучении RT-DETR и использовании других рецептов с небольшими размерами пакета на каждом GPU статистики отдельного GPU могут быть шумными. SyncBatchNorm в PyTorch синхронизирует среднее и дисперсию между всеми рангами, формируя единую статистику глобального пакета, что часто улучшает сходимость ценой небольших накладных расходов на меж-GPU-коммуникацию.

Преобразование должно выполняться после размещения модели на GPU, но до её обёртки в DDP. Наиболее удобная точка для этого — set_model_attributes(), который BaseTrainer вызывает именно в этот промежуток:

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)

Проверка world_size > 1 гарантирует безопасность использования Trainer и при запуске на одном GPU; на одном GPU преобразование пропускается, и обучение продолжается с обычным BatchNorm2d. Тот же подход работает для YOLO при замене родительского класса на DetectionTrainer.

Когда использовать SyncBatchNorm
СценарийРекомендация
Обучение на нескольких GPU, небольшой пакет на каждом GPU (≤ 16)Включить
Обучение на нескольких GPU, большой пакет на каждом GPU (≥ 32)Необязательно; небольшой выигрыш
Обучение на одном GPUНеприменимо (пропускается)

Настраиваемый клиппинг градиентов#

Trainer по умолчанию ограничивает градиенты значением max_norm=10.0 в optimizer_step() — это большое значение, настроенное для моделей YOLO, где градиенты редко его превышают. Детекторы семейства DETR (RT-DETR, DEIM, DINO) обычно используют гораздо меньшие значения, например 0.1, чтобы стабилизировать слои cross-attention декодера, где величина градиентов может резко возрастать. Чтобы переопределить значение клиппинга, унаследуй свой класс от Trainer и переопредели 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)

Тот же Trainer работает для YOLO при замене родительского класса на DetectionTrainer (from ultralytics.models.yolo.detect import DetectionTrainer) и загрузке контрольной точки YOLO с помощью YOLO("yolo26n.pt"). Содержимое optimizer_step не изменяется.

Типичные значения `clip_grad_norm`
Семейство архитектурТипичное значение max_norm
Семейство RT-DETR / DEIM / DETR0.1
YOLO (значение по умолчанию в Ultralytics)10.0
Отключить клиппинг0

Часто задаваемые вопросы#

  • Передай свой класс Trainer (не экземпляр) параметру trainer в 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)

    Класс YOLO самостоятельно создаёт экземпляр Trainer. Подробнее об архитектуре Trainer см. на странице Расширенная настройка.

  • Основные методы, доступные для настройки:

    МетодНазначение
    validate()Запустить валидацию и вернуть метрики
    build_optimizer()Создать оптимизатор
    save_model()Сохранить контрольные точки обучения
    get_model()Вернуть экземпляр модели
    get_validator()Вернуть экземпляр validator
    get_dataloader()Создать dataloader
    preprocess_batch()Предварительно обработать входной пакет
    label_loss_items()Форматировать элементы функции потерь для журналирования

    Полную справочную информацию по API см. в документации BaseTrainer.

  • Да, для более простых вариантов настройки часто достаточно callback. Доступные события callback включают on_train_start, on_train_epoch_start, on_train_epoch_end, on_fit_epoch_end и on_model_save. Они позволяют подключаться к циклу обучения без наследования от Trainer. Пример с заморозкой backbone выше демонстрирует этот подход.

  • Если изменение проще, например настройка коэффициентов функции потерь, ты можешь напрямую изменить гиперпараметры:

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

    В YOLO26 dfl масштабирует записываемое значение l1_loss, поскольку его голова детектирования использует reg_max: 1; в моделях с reg_max > 1 он масштабирует dfl_loss.

Комментарии