Ultralytics YOLO27: Запишись в список ожидания.
Get Started

Настройка Trainer#

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

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

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

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

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

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

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

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

    # Добавь сюда свои настройки

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

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

Логирование пользовательских метрик#

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

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

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

АтрибутОписание
f1F1-мера для каждого класса
image_metricsСловарь метрик для каждого изображения: точность, полнота, F1, TP, FP и FN
pТочность для каждого класса
rПолнота для каждого класса
ap50AP при IoU 0.5 для каждого класса
apAP при IoU 0.5:0.95 для каждого класса
mp, mrСредние точность и полнота
map50, mapМетрики mAP

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

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

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

Тренер сохраняет 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 или полноту), переопредели 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)Точность
metrics/recall(B)Полнота
metrics/mAP50(B)mAP при IoU 0.5
metrics/mAP50-95(B)mAP при IoU 0.5:0.95

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

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

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 гарантирует, что тренер можно безопасно использовать и при обучении на одном GPU: в этом случае преобразование пропускается, а обучение продолжается с обычным BatchNorm2d. Для YOLO подойдет тот же подход, если заменить родительский класс на DetectionTrainer.

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

Настраиваемое отсечение градиентов#

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

    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)

Этот же тренер подойдет для 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 метода 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 сам создает экземпляр тренера. Подробнее об архитектуре тренера см. на странице Расширенная настройка.

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

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

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

  • Да, для более простых настроек часто достаточно callbacks. Среди доступных событий callbacks — on_train_start, on_train_epoch_start, on_train_epoch_end, on_fit_epoch_end и on_model_save. Они позволяют подключаться к циклу обучения без создания подкласса. Пример с заморозкой 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.

Комментарии