Настройка Trainer#
Конвейер обучения Ultralytics построен на BaseTrainer и специализированных для отдельных задач тренерах, таких как DetectionTrainer. Эти классы из коробки выполняют цикл обучения, валидацию, сохранение контрольных точек и ведение журналов. Если тебе нужен больший контроль — например, отслеживание пользовательских метрик, настройка весов функции потерь или реализация расписаний скорости обучения, — создай подкласс тренера и переопредели нужные методы.
В этом руководстве рассмотрены семь распространенных вариантов настройки:
- Регистрация пользовательских метрик (F1 score) в конце каждой эпохи
- Добавление весов классов для работы с дисбалансом классов
- Сохранение лучшей модели по другой метрике
- Заморозка backbone на первые N эпох с последующей разморозкой
- Задание скорости обучения для каждого слоя
- Синхронизация BatchNorm между GPU для обучения на нескольких GPU
- Настройка отсечения градиентов для повышения стабильности
Прежде чем читать это руководство, убедись, что ты знаком с основами обучения моделей 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:
| Атрибут | Описание |
|---|---|
f1 | F1-мера для каждого класса |
image_metrics | Словарь метрик для каждого изображения: точность, полнота, F1, TP, FP и FN |
p | Точность для каждого класса |
r | Полнота для каждого класса |
ap50 | AP при IoU 0.5 для каждого класса |
ap | AP при 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.
| Сценарий | Рекомендация |
|---|---|
| Обучение на нескольких 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 остается без изменений.
| Семейство архитектур | Типичное значение max_norm |
|---|---|
| Семейство RT-DETR / DEIM / DETR | 0.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.