Ultralytics YOLO27: انضم إلى قائمة الانتظار.
Get Started

تخصيص Trainer#

يتمحور مسار التدريب في Ultralytics حول BaseTrainer ومدرّبات خاصة بالمهام مثل DetectionTrainer. تتولى هذه الفئات، مباشرةً، حلقة التدريب والتحقق وحفظ نقاط التحقق والتسجيل. عندما تحتاج إلى مزيد من التحكم — مثل تتبّع مقاييس مخصصة أو ضبط أوزان الخسارة أو تطبيق جداول لمعدل التعلّم — يمكنك إنشاء فئة فرعية من المدرّب وتجاوز طرائق محددة.

يتناول هذا الدليل سبعة تخصيصات شائعة:

  1. تسجيل المقاييس المخصصة (درجة F1) في نهاية كل حقبة
  2. إضافة أوزان للفئات للتعامل مع اختلال توازن الفئات
  3. حفظ أفضل نموذج استنادًا إلى مقياس مختلف
  4. تجميد العمود الفقري خلال أول N حقبة، ثم إلغاء تجميده
  5. تحديد معدلات تعلم لكل طبقة
  6. مزامنة BatchNorm عبر وحدات GPU للتدريب باستخدام وحدات GPU متعددة
  7. تهيئة قصّ التدرجات لضبط الاستقرار
المتطلبات الأساسية

قبل قراءة هذا الدليل، تأكد من إلمامك بأساسيات تدريب نماذج 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الاستدعاء لكل فئة
ap50AP عند IoU يساوي 0.5 لكل فئة
apAP عند IoU من 0.5 إلى 0.95 لكل فئة
mp, mrمتوسط الدقة والاستدعاء
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 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 استنادًا إلى قيمة الملاءمة، التي تكون افتراضيًا في مهام الكشف 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() وأعد المقياس الذي اخترته بوصفه قيمة الملاءمة. عندئذٍ ستستخدمه 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

تجميد العمود الفقري وإلغاء تجميده#

غالبًا ما تستفيد مسارات عمل التعلّم بالنقل من تجميد العمود الفقري المدرّب مسبقًا خلال أول 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) عند بدء التدريب، ويشمل ذلك معظم العمود الفقري لـ YOLO26. يمتد العمود الفقري عبر الطبقات 0-10، لذا يُبقي freeze=10 كتلة C2PSA الأخيرة (الطبقة 10) قابلة للتدريب؛ استخدم freeze=11 لتجميد العمود الفقري بأكمله. تُستدعى دالة رد النداء on_train_epoch_start في بداية كل حقبة، وتلغي تجميد الطبقات المطلوبة بعد انتهاء فترة التجميد، مع إبقاء معاملات DFL ومعلم التقطير المجمدة دائمًا دون تغيير.

اختيار الطبقات المراد تجميدها
  • يجمّد freeze=10 أول 10 طبقات، ذات الفهارس 0-9 (معظم العمود الفقري لـ YOLO26؛ استخدم freeze=11 لتضمين كتلة C2PSA الأخيرة في الطبقة 10)
  • يجمّد freeze=[0, 1, 2, 3] طبقات محددة حسب الفهرس
  • تمنح القيم الأعلى لـ FREEZE_EPOCHS الرأس وقتًا أطول للتكيّف قبل تغيير العمود الفقري

معدلات تعلم لكل طبقة#

قد تستفيد أجزاء مختلفة من الشبكة من معدلات تعلم مختلفة. تتمثل إحدى الاستراتيجيات الشائعة في استخدام معدل تعلم أقل للعمود الفقري المدرّب مسبقًا للحفاظ على الميزات التي تعلمها، مع السماح لرأس الكشف بالتكيّف بسرعة أكبر عبر معدل أعلى:

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 واحدة أيضًا؛ إذ يُتخطى التحويل على وحدة GPU واحدة، ويتواصل التدريب باستخدام BatchNorm2d العادي. وينطبق النمط نفسه على YOLO بتغيير الصنف الأب إلى DetectionTrainer.

متى تستخدم SyncBatchNorm
السيناريوالتوصية
التدريب باستخدام وحدات GPU متعددة، وحجم دفعة صغير لكل وحدة GPU (≤ 16)تفعيل
التدريب باستخدام وحدات GPU متعددة، وحجم دفعة كبير لكل وحدة GPU (≥ 32)اختياري؛ فائدة طفيفة
التدريب باستخدام وحدة GPU واحدةغير منطبق (يُتخطى)

قصّ التدرجات القابل للتهيئة#

يقصّ المدرّب الافتراضي التدرجات إلى max_norm=10.0 في optimizer_step()، وهي قيمة واسعة مضبوطة لنماذج 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 دون تغيير.

القيم المعتادة لـ `clip_grad_norm`
عائلة البنيةالقيمة المعتادة لـ max_norm
عائلة RT-DETR / DEIM / DETR0.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.

  • نعم، غالبًا ما تكون دوال رد النداء كافية لإجراء تخصيصات أبسط. تشمل أحداث دوال رد النداء المتاحة on_train_start وon_train_epoch_start وon_train_epoch_end وon_fit_epoch_end وon_model_save. تتيح لك هذه الأحداث إدراج وظائف في حلقة التدريب دون إنشاء صنف فرعي. ويوضح مثال تجميد العمود الفقري أعلاه هذا الأسلوب.

  • إذا كان التغيير الذي تريده أبسط (مثل ضبط معاملات الخسارة)، فيمكنك تعديل المعلمات الفائقة مباشرةً:

    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.

التعليقات