YOLO Vision 2026:

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:

  1. Ghi log các metric tùy chỉnh (điểm F1) ở cuối mỗi epoch
  2. Thêm trọng số class để xử lý mất cân bằng class
  3. Lưu model tốt nhất dựa trên một metric khác
  4. Đóng băng backbone trong N epoch đầu tiên, sau đó bỏ đóng băng
  5. Chỉ định learning rate theo từng layer
  6. Đồng bộ BatchNorm giữa các GPU khi training trên nhiều GPU
  7. Cấu hình gradient clipping để tinh chỉnh độ ổn định
Điều kiện tiên quyết

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, recallmAP. 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.

Các Metric khả dụng

Validator cung cấp quyền truy cập đến nhiều metric thông qua self.validator.metrics.box:

Thuộc tínhMô tả
f1Điểm F1 theo từng class
image_metricsDictionary metric theo từng image gồm precision, recall, F1, TP, FP và FN
pPrecision theo từng class
rRecall theo từng class
ap50AP tại IoU 0.5 theo từng class
apAP tại IoU 0.5:0.95 theo từng class
mp, mrPrecision và recall trung bình
map50, mapCác metric AP trung bình

Thêm Trọng số Class#

Đặt cls_pw giữa 0.01.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 khả dụng

Các metric phổ biến có trong self.metrics sau validation gồm:

KeyMô 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.

Chọn Layer cần đóng băng
  • freeze=10 đóng băng 10 layer đầu tiên, index 0-9 (phần lớn backbone YOLO26; dùng freeze=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_EPOCHS cao 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.

Khi nào nên sử dụng SyncBatchNorm
Tình huốngKhuyế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 GPUKhô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.

Giá trị `clip_grad_norm` điển hình
Họ kiến trúcmax_norm điển hình
Họ RT-DETR / DEIM / DETR0.1
YOLO (mặc định của Ultralytics)10.0
Tắt clipping0

FAQ#

  • Truyền class custom trainer của bạn (không phải một instance) vào parameter trainer trong 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)

    Class YOLO tự 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:

    MethodMụ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_endon_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, dfl scale l1_loss được ghi log vì detection head sử dụng reg_max: 1; trên các model có reg_max > 1, nó scale dfl_loss.

Bình luận