Tùy chỉnh Trainer#
Pipeline huấn luyện của Ultralytics được xây dựng xoay quanh BaseTrainer và các trainer dành riêng cho từng tác vụ như DetectionTrainer. Các class này xử lý sẵn vòng lặp huấn luyện, validation, lưu checkpoint và ghi log. Khi cần kiểm soát nhiều hơn — theo dõi metric tùy chỉnh, điều chỉnh trọng số loss hoặc triển khai lịch learning rate — bạn có thể kế thừa trainer và ghi đè các phương thức cụ thể.
Hướng dẫn này trình bày bảy cách tùy chỉnh phổ biến:
- Ghi log metric tùy chỉnh (điểm F1) vào cuối mỗi epoch
- Thêm trọng số class để xử lý tình trạng 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, sau đó mở băng
- Chỉ định learning rate cho từng layer
- Đồng bộ BatchNorm trên các GPU để huấn luyện đa 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 kiến thức cơ bản về huấn luyện model YOLO và trang Tùy chỉnh nâng cao, trong đó trình bày kiến trúc BaseTrainer.
Cách hoạt động của Custom Trainer#
Lớp model YOLO nhận tham số trainer trong phương thức train(). Điều này cho phép bạn truyền vào lớp trainer riêng kế thừa hành vi 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."""
# Thêm các tùy chỉnh của bạn tại đây
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 ghi đè các phương thức cụ thể muốn tùy chỉnh.
Ghi log metric tùy chỉnh#
Bước validation tính toán precision, recall và mAP. Nếu cần các metric bổ sung như F1 score theo từng class, hãy ghi đè 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)Sau mỗi lần validation, thao tác này ghi log F1 score trung bình trên tất cả class xuất hiện trong tập validation và bảng phân tích theo từng class.
Validator cung cấp quyền truy cập vào nhiều metric thông qua self.validator.metrics.box:
| Thuộc tính | Mô tả |
|---|---|
f1 | F1 score cho từng class |
image_metrics | Dictionary metric cho từng ảnh gồm precision, recall, F1, TP, FP và FN |
p | Precision cho từng class |
r | Recall cho từng class |
ap50 | AP tại IoU 0.5 cho từng class |
ap | AP tại IoU 0.5:0.95 cho từng class |
mp, mr | Precision và recall trung bình |
map50, map | Các metric mAP trung bình |
Thêm trọng số cho class#
Đặt cls_pw trong khoảng từ 0.0 đến 1.0 để áp dụng trọng số nghịch đảo tần suất đã chuẩn hóa cho classification loss. Chỉ ghi đè 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 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() chuẩn hóa các giá trị này về giá trị trung bình 1.0 và lưu chúng trong model, nơi detection loss hiện có sẽ áp dụng. Các chỉ số ở 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; 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 ghi đè validate() và trả về metric đã chọn làm giá trị fitness. Sau đó, save_model() tích hợp sẵn 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 phương thức này.
Các metric phổ biến có trong self.metrics sau validation gồm:
| Khóa | 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à mở băng backbone#
Các quy trình transfer learning thường được hưởng lợi từ việc đóng băng backbone đã pretrained trong N epoch đầu, cho phép detection head thích nghi trước khi fine-tuning toàn bộ mạng. Ultralytics cung cấp tham số freeze để đóng băng các layer khi bắt đầu huấn luyện; bạn có thể dùng callback để mở 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)Tham số freeze=10 đóng băng 10 layer đầu tiên (chỉ số 0-9) khi bắt đầu huấn luyện, bao gồm phần lớn backbone YOLO26. Backbone trải dài từ layer 0-10, vì vậy freeze=10 giữ block C2PSA cuối cùng (layer 10) ở trạng thái có thể huấn luyện; dùng freeze=11 để đóng băng toàn bộ backbone. Callback on_train_epoch_start được gọi vào đầu mỗi epoch và mở băng các layer đã chỉ định sau khi hết thời gian đóng băng, đồng thời giữ nguyên các tham số DFL và distillation-teacher được đóng băng vĩnh viễn.
freeze=10đóng băng 10 layer đầu tiên, chỉ số 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 chỉ số- 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 mạng có thể đạt hiệu quả tốt hơn với learning rate khác nhau. Một chiến lược phổ biến là 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#
Với RT-DETR, hãy dùng cùng cách ghi đè với RTDETRTrainer làm lớp cha và tải checkpoint bằng RTDETR("rtdetr-l.pt").
BatchNorm đồng bộ khi huấn luyện đa GPU#
Khi huấn luyện trên nhiều GPU bằng DistributedDataParallel, các layer BatchNorm2d mặc định tính toán thống kê độc lập trên từng GPU. Với fine-tuning RT-DETR và các cấu hình khác dùng batch size nhỏ trên mỗi GPU, thống kê 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 thống kê batch toàn cục, thường giúp cải thiện hội tụ với chi phí là một lượng nhỏ overhead giao tiếp 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 dùng trong các lần chạy một GPU; trên một GPU, thao tác chuyển đổi được bỏ qua và quá trình huấn luyện tiếp tục với BatchNorm2d thông thường. Có thể áp dụng cách tương tự cho YOLO bằng cách đổi lớp cha thành DetectionTrainer.
| Kịch bản | Khuyến nghị |
|---|---|
| Huấn luyện đa GPU, batch size nhỏ trên mỗi GPU (≤ 16) | Bật |
| Huấn luyện đa GPU, batch size lớn trên mỗi GPU (≥ 32) | Không bắt buộc; lợi ích nhỏ |
| Huấn luyện một GPU | Không áp dụng (bỏ qua) |
Gradient clipping có thể cấu hình#
Trainer mặc định clip gradient ở mức max_norm=10.0 trong optimizer_step(), một giá trị khá rộng được tinh chỉnh cho các model YOLO, vốn hiếm khi có gradient vượt mức này. Các detector thuộc họ DETR (RT-DETR, DEIM, DINO) thường dùng 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 đột biến. Để ghi đè giá trị clip, hãy tạo subclass của trainer và ghi đè 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 # norm gradient tối đa; đặt thành 0 để tắt 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 này cũng dùng được cho YOLO bằng cách đổi lớp cha thành DetectionTrainer (from ultralytics.models.yolo.detect import DetectionTrainer) và tải checkpoint YOLO bằng YOLO("yolo26n.pt"). Phần thân optimizer_step không thay đổi.
| Họ kiến trúc | Giá trị max_norm thường dùng |
|---|---|
| Họ RT-DETR / DEIM / DETR | 0.1 |
| YOLO (mặc định của Ultralytics) | 10.0 |
| Tắt clipping | 0 |
Câu hỏi thường gặp#
Truyền lớp custom trainer của bạn (không phải một instance) vào tham số
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)Lớp
YOLOtự xử lý việc khởi tạo trainer. Xem trang Tùy chỉnh nâng cao để biết thêm chi tiết về kiến trúc trainer.Các phương thức chính có thể tùy chỉnh:
Phương thức Mục đích validate()Chạy validation và trả về metric build_optimizer()Khởi tạo optimizer save_model()Lưu checkpoint huấn luyện get_model()Trả về instance của model get_validator()Trả về instance của validator get_dataloader()Xây dựng dataloader preprocess_batch()Tiền xử lý batch đầu vào label_loss_items()Định dạng các thành phần loss để ghi log Để xem tài liệu tham khảo API đầy đủ, hãy xem tài liệu
BaseTrainer.Có, với các tùy chỉnh đơn giản hơn, callback thường là đủ. Các event callback hiện có gồm
on_train_start,on_train_epoch_start,on_train_epoch_end,on_fit_epoch_endvàon_model_save. Những event này cho phép bạn móc nối vào vòng lặp huấn luyện mà không cần tạo subclass. 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 các hệ số loss), 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, tham số này scaledfl_loss.