Tùy chỉnh Trainer#
Pipeline training của Ultralytics được xây dựng xoay quanh BaseTrainer và các trainer dành riêng cho từng task như DetectionTrainer. Các class này xử lý vòng lặp training, validation, checkpointing và logging ngay từ đầu. Khi cần kiểm soát nhiều hơn — theo dõi các metric tùy chỉnh, điều chỉnh trọng số loss hoặc triển khai learning rate schedule — bạn có thể kế thừa trainer và override các method cụ thể.
Hướng dẫn này trình bày bảy tùy chỉnh phổ biến:
- Ghi log các metric tùy chỉnh (điểm F1) ở cuối mỗi epoch
- Thêm trọng số class để xử lý mất cân bằng class
- Lưu model tốt nhất dựa trên một metric khác
- Đóng băng backbone trong N epoch đầu tiên, sau đó bỏ đóng băng
- Chỉ định learning rate theo từng layer
- Đồng bộ BatchNorm giữa các GPU khi training trên nhiều GPU
- Cấu hình gradient clipping để tinh chỉnh độ ổn định
Trước khi đọc hướng dẫn này, hãy đảm bảo bạn đã nắm các kiến thức cơ bản về training model YOLO và trang Tùy chỉnh nâng cao, trong đó trình bày kiến trúc BaseTrainer.
Cách Custom Trainer hoạt động#
Class model YOLO chấp nhận parameter trainer trong method train(). Điều này cho phép bạn truyền vào class trainer của riêng mình, mở rộng behavior mặc định:
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)Custom trainer của bạn kế thừa toàn bộ chức năng từ DetectionTrainer, vì vậy bạn chỉ cần override các method cụ thể muốn tùy chỉnh.
Ghi log Metric tùy chỉnh#
Bước validation tính precision, recall và mAP. Nếu cần thêm các metric như điểm F1 theo từng class, hãy override 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)Thao tác này ghi log điểm F1 trung bình trên tất cả class xuất hiện trong validation và bảng phân tích theo từng class sau mỗi lần chạy validation.
Validator cung cấp quyền truy cập đến nhiều metric thông qua self.validator.metrics.box:
| Thuộc tính | Mô tả |
|---|---|
f1 | Điểm F1 theo từng class |
image_metrics | Dictionary metric theo từng image gồm precision, recall, F1, TP, FP và FN |
p | Precision theo từng class |
r | Recall theo từng class |
ap50 | AP tại IoU 0.5 theo từng class |
ap | AP tại IoU 0.5:0.95 theo từng class |
mp, mr | Precision và recall trung bình |
map50, map | Các metric AP trung bình |
Thêm Trọng số Class#
Đặt cls_pw giữa 0.0 và 1.0 để áp dụng trọng số nghịch đảo tần suất đã chuẩn hóa cho classification loss. Chỉ override phép tính trọng số hiện có khi bạn cần các tỷ lệ được chỉ định thủ công:
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() chuẩn hóa các giá trị này về trung bình 1.0 và lưu chúng trên model, nơi detection loss hiện có sẽ áp dụng chúng. Các index trên yêu cầu dataset có ít nhất hai class.
Lưu Model tốt nhất theo Metric tùy chỉnh#
Trainer lưu best.pt dựa trên fitness; đối với detection, mặc định là mAP@0.5:0.95 (trọng số [0.0, 0.0, 0.0, 1.0] cho [P, R, mAP@0.5, mAP@0.5:0.95]). Để sử dụng metric khác (chẳng hạn mAP@0.5 hoặc recall), hãy override validate() và trả về metric đã chọn dưới dạng giá trị fitness. save_model() tích hợp sẵn sau đó sẽ tự động sử dụng giá trị này:
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() cập nhật best_fitness bằng metric mặc định, vì vậy hãy lưu lại giá trị trước đó trước khi gọi method này.
Các metric phổ biến có trong self.metrics sau validation gồm:
| Key | Mô tả |
|---|---|
metrics/precision(B) | Precision |
metrics/recall(B) | Recall |
metrics/mAP50(B) | mAP tại IoU 0.5 |
metrics/mAP50-95(B) | mAP tại IoU 0.5:0.95 |
Đóng băng và Bỏ đóng băng Backbone#
Các workflow transfer learning thường hưởng lợi từ việc đóng băng backbone pretrained trong N epoch đầu tiên, cho phép detection head thích nghi trước khi fine-tune toàn bộ network. Ultralytics cung cấp parameter freeze để đóng băng các layer khi bắt đầu training, và bạn có thể dùng một callback để bỏ đóng băng chúng sau N epoch:
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)Parameter freeze=10 đóng băng 10 layer đầu tiên (index 0-9) khi bắt đầu training, bao phủ phần lớn backbone của YOLO26. Backbone trải dài từ layer 0-10, vì vậy freeze=10 để block C2PSA cuối cùng (layer 10) ở trạng thái có thể training; dùng freeze=11 để đóng băng toàn bộ backbone. Callback on_train_epoch_start được kích hoạt khi bắt đầu mỗi epoch và bỏ đóng băng các layer được yêu cầu sau khi kết thúc thời gian đóng băng, đồng thời giữ nguyên các parameter DFL và distillation-teacher bị đóng băng vĩnh viễn.
freeze=10đóng băng 10 layer đầu tiên, index 0-9 (phần lớn backbone YOLO26; dùngfreeze=11để bao gồm block C2PSA cuối cùng ở layer 10)freeze=[0, 1, 2, 3]đóng băng các layer cụ thể theo index- Giá trị
FREEZE_EPOCHScao hơn giúp head có thêm thời gian thích nghi trước khi backbone thay đổi
Learning Rate theo từng Layer#
Các phần khác nhau của network có thể hưởng lợi từ learning rate khác nhau. Một chiến lược phổ biến là sử dụng learning rate thấp hơn cho backbone pretrained để bảo toàn các feature đã học, đồng thời cho phép detection head thích nghi nhanh hơn với learning rate cao hơn:
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)Biến thể RT-DETR#
Đối với RT-DETR, hãy sử dụng cùng override với RTDETRTrainer làm parent và load checkpoint bằng RTDETR("rtdetr-l.pt").
BatchNorm đồng bộ cho Training nhiều GPU#
Khi training trên nhiều GPU với DistributedDataParallel, các layer BatchNorm2d mặc định tính statistic độc lập trên từng GPU. Đối với fine-tuning RT-DETR và các recipe khác sử dụng batch size nhỏ trên mỗi GPU, statistic theo từng GPU có thể nhiễu. SyncBatchNorm của PyTorch đồng bộ mean và variance trên tất cả rank để tạo một statistic batch toàn cục duy nhất, thường cải thiện khả năng hội tụ với chi phí overhead giao tiếp nhỏ giữa các GPU.
Việc chuyển đổi phải diễn ra sau khi model được đưa lên GPU nhưng trước khi DDP bọc model. Hook phù hợp nhất cho việc này là set_model_attributes(), được BaseTrainer gọi đúng trong khoảng thời gian đó:
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)Điều kiện world_size > 1 đảm bảo trainer cũng an toàn khi chạy trên một GPU; trên một GPU, việc chuyển đổi được bỏ qua và training tiếp tục với BatchNorm2d thông thường. Pattern tương tự cũng áp dụng cho YOLO bằng cách đổi parent class thành DetectionTrainer.
| Tình huống | Khuyến nghị |
|---|---|
| Training nhiều GPU, batch trên mỗi GPU nhỏ (≤ 16) | Bật |
| Training nhiều GPU, batch trên mỗi GPU lớn (≥ 32) | Tùy chọn; lợi ích nhỏ |
| Training một GPU | Không áp dụng (được bỏ qua) |
Gradient Clipping có thể cấu hình#
Trainer mặc định clip gradient về max_norm=10.0 trong optimizer_step(), một giá trị tương đối rộng được tinh chỉnh cho các model YOLO, nơi gradient hiếm khi vượt quá ngưỡng này. Các detector thuộc họ DETR (RT-DETR, DEIM, DINO) thường sử dụng các giá trị chặt hơn nhiều, chẳng hạn 0.1, để ổn định các layer cross-attention của decoder, nơi độ lớn gradient có thể tăng vọt. Để override giá trị clip, hãy kế thừa trainer và override 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 tương tự cũng hoạt động cho YOLO bằng cách đổi parent class thành DetectionTrainer (from ultralytics.models.yolo.detect import DetectionTrainer) và load checkpoint YOLO bằng YOLO("yolo26n.pt"). Phần thân optimizer_step không thay đổi.
| Họ kiến trúc | max_norm điển hình |
|---|---|
| Họ RT-DETR / DEIM / DETR | 0.1 |
| YOLO (mặc định của Ultralytics) | 10.0 |
| Tắt clipping | 0 |
FAQ#
Truyền class custom trainer của bạn (không phải một instance) vào parameter
trainertrongmodel.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)Class
YOLOtự xử lý việc khởi tạo trainer internally. Xem trang Tùy chỉnh nâng cao để biết thêm chi tiết về kiến trúc trainer.Các method chính có thể tùy chỉnh:
Method Mục đích validate()Chạy validation và trả về metric build_optimizer()Xây dựng optimizer save_model()Lưu checkpoint training get_model()Trả về instance model get_validator()Trả về instance validator get_dataloader()Xây dựng dataloader preprocess_batch()Preprocess batch input label_loss_items()Định dạng các thành phần loss để logging Để xem tài liệu API đầy đủ, hãy tham khảo tài liệu
BaseTrainer.Có, đối với các tùy chỉnh đơn giản hơn, callback thường là đủ. Các event callback khả dụng gồm
on_train_start,on_train_epoch_start,on_train_epoch_end,on_fit_epoch_endvàon_model_save. Chúng cho phép bạn hook vào vòng lặp training mà không cần kế thừa. Ví dụ đóng băng backbone ở trên minh họa cách tiếp cận này.Nếu thay đổi của bạn đơn giản hơn (chẳng hạn điều chỉnh loss gain), bạn có thể sửa trực tiếp hyperparameter:
from ultralytics import YOLO model = YOLO("yolo26n.pt") model.train(data="coco8.yaml", box=10.0, cls=1.5, dfl=2.0)Trên YOLO26,
dflscalel1_lossđược ghi log vì detection head sử dụngreg_max: 1; trên các model córeg_max > 1, nó scaledfl_loss.