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