تخطي إلى المحتوى

تخصيص المدرب

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

يشرح هذا الدليل خمسة تخصيصات شائعة:

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

المتطلبات الأساسية

قبل قراءة هذا الدليل، تأكد من أنك على دراية بأساسيات تدريب 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."""

    pass  # Add your customizations here


model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", epochs=10, trainer=CustomTrainer)

يورث المدرب المخصص الخاص بك جميع الوظائف من DetectionTrainer، لذا ما عليك سوى تجاوز الطرق المحددة التي تريد تخصيصها.

تسجيل المقاييس المخصصة

في التحقق خطوة الحساب الدقة, الاسترجاع، و 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

            valid_f1 = f1_per_class[f1_per_class > 0]
            mean_f1 = np.mean(valid_f1) if len(valid_f1) > 0 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) if f1_per_class[j] > 0
            ]
            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 لكل فصل
pالدقة لكل فئة
rاستدعاء لكل فصل
ap50AP IoU .5 لكل فصل دراسي
apAP IoU .5:0.95 لكل فصل دراسي
mp, mrمتوسط الدقة والاسترجاع
map50, mapمتوسط AP

إضافة أوزان الفئات

إذا كانت مجموعة البيانات الخاصة بك تحتوي على فئات غير متوازنة (على سبيل المثال، عيب نادر في فحص التصنيع)، يمكنك زيادة وزن الفئات غير الممثلة بشكل كافٍ في دالة الخسارة. وهذا يجعل النموذج يعاقب بشكل أشد على التصنيفات الخاطئة للفئات النادرة.

لتخصيص الخسارة، قم بإنشاء فئات فرعية لفئات الخسارة والنموذج والمدرب:

import torch
from torch import nn

from ultralytics import YOLO
from ultralytics.models.yolo.detect import DetectionTrainer
from ultralytics.nn.tasks import DetectionModel
from ultralytics.utils import RANK
from ultralytics.utils.loss import E2ELoss, v8DetectionLoss


class WeightedDetectionLoss(v8DetectionLoss):
    """Detection loss with class weights applied to BCE classification loss."""

    def __init__(self, model, class_weights=None, tal_topk=10, tal_topk2=None):
        """Initialize loss with optional per-class weights for BCE."""
        super().__init__(model, tal_topk=tal_topk, tal_topk2=tal_topk2)
        if class_weights is not None:
            self.bce = nn.BCEWithLogitsLoss(
                pos_weight=class_weights.to(self.device),
                reduction="none",
            )


class WeightedE2ELoss(E2ELoss):
    """E2E Loss with class weights for YOLO26."""

    def __init__(self, model, class_weights=None):
        """Initialize E2E loss with weighted detection loss."""

        def weighted_loss_fn(model, tal_topk=10, tal_topk2=None):
            return WeightedDetectionLoss(model, class_weights=class_weights, tal_topk=tal_topk, tal_topk2=tal_topk2)

        super().__init__(model, loss_fn=weighted_loss_fn)


class WeightedDetectionModel(DetectionModel):
    """Detection model that uses class-weighted loss."""

    def init_criterion(self):
        """Initialize weighted loss criterion with per-class weights."""
        class_weights = torch.ones(self.nc)
        class_weights[0] = 2.0  # upweight class 0
        class_weights[1] = 3.0  # upweight rare class 1
        return WeightedE2ELoss(self, class_weights=class_weights)


class WeightedTrainer(DetectionTrainer):
    """Trainer that returns a WeightedDetectionModel."""

    def get_model(self, cfg=None, weights=None, verbose=True):
        """Return a WeightedDetectionModel."""
        model = WeightedDetectionModel(cfg, nc=self.data["nc"], verbose=verbose and RANK == -1)
        if weights:
            model.load(weights)
        return model


model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", epochs=10, trainer=WeightedTrainer)

حساب الأوزان من مجموعة البيانات

يمكنك حساب أوزان الفئات تلقائيًا من توزيع التسميات في مجموعة البيانات الخاصة بك. ومن الأساليب الشائعة في ذلك وزن التردد العكسي:

import numpy as np

# class_counts: number of instances per class
class_counts = np.array([5000, 200, 3000])
# Inverse frequency: rarer classes get higher weight
class_weights = max(class_counts) / class_counts
# Result: [1.0, 25.0, 1.67]

حفظ أفضل نموذج حسب المقياس المخصص

المدرب يحفظ best.pt بناءً على اللياقة البدنية، والتي تكون افتراضياً 0.9 × mAP@0.5:0.95 + 0.1 × mAP@0.5. لاستخدام مقياس مختلف (مثل 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."""
        metrics, fitness = super().validate()
        if metrics:
            fitness = metrics.get("metrics/mAP50(B)", fitness)
            if self.best_fitness is None or fitness > self.best_fitness:
                self.best_fitness = fitness
        return metrics, fitness


model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", epochs=20, trainer=CustomSaveTrainer)

المقاييس المتاحة

المقاييس الشائعة المتاحة في self.metrics بعد التحقق من صحتها تشمل:

مفتاحالوصف
metrics/precision(B)الدقة
metrics/recall(B)الاسترجاع
metrics/mAP50(B)mAP IoU .5
metrics/mAP50-95(B)mAP IoU .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 all layers after FREEZE_EPOCHS."""
    if trainer.epoch == FREEZE_EPOCHS:
        LOGGER.info(f"Epoch {trainer.epoch}: Unfreezing all layers for fine-tuning")
        for name, param in trainer.model.named_parameters():
            if not param.requires_grad:
                param.requires_grad = True
                LOGGER.info(f"  Unfroze: {name}")
        trainer.freeze_layer_names = [".dfl"]


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 طبقات (العمود الفقري) عند بدء التدريب. on_train_epoch_start يتم تشغيل callback في بداية كل حقبة ويقوم بإلغاء تجميد جميع المعلمات بمجرد انتهاء فترة التجميد.

اختيار ما يجب تجميده

  • freeze=10 يجمد الطبقات العشر الأولى (عادةً ما تكون العمود الفقري في YOLO )
  • freeze=[0, 1, 2, 3] تجميد طبقات محددة حسب الفهرس
  • أعلى FREEZE_EPOCHS القيم تمنح الرأس مزيدًا من الوقت للتكيف قبل أن يتغير العمود الفقري

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

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

import torch

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."""

    def build_optimizer(self, model, name="auto", lr=0.001, momentum=0.9, decay=1e-5, iterations=1e5):
        """Build optimizer with separate learning rates for backbone and head."""
        backbone_params = []
        head_params = []

        for k, v in unwrap_model(model).named_parameters():
            if not v.requires_grad:
                continue
            is_backbone = any(k.startswith(f"model.{i}.") for i in range(10))
            if is_backbone:
                backbone_params.append(v)
            else:
                head_params.append(v)

        backbone_lr = lr * 0.1

        optimizer = torch.optim.AdamW(
            [
                {"params": backbone_params, "lr": backbone_lr, "weight_decay": decay},
                {"params": head_params, "lr": lr, "weight_decay": decay},
            ],
        )

        LOGGER.info(
            f"PerLayerLR optimizer: backbone ({len(backbone_params)} params, lr={backbone_lr}) "
            f"| head ({len(head_params)} params, lr={lr})"
        )
        return optimizer


model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", epochs=20, trainer=PerLayerLRTrainer)

جدولة معدل التعلم

جدولة معدل التعلم المدمجة (cosine أو linear) لا يزال ينطبق بالإضافة إلى معدلات التعلم الأساسية لكل مجموعة. سيتبع كل من معدلات التعلم الأساسية ومعدلات التعلم الرئيسية نفس جدول التناقص، مع الحفاظ على النسبة بينهما طوال فترة التدريب.

تقنيات الجمع

يمكن دمج هذه التخصيصات في فئة تدريب واحدة عن طريق تجاوز طرق متعددة وإضافة استدعاءات حسب الحاجة.

الأسئلة الشائعة

كيف يمكنني تمرير مدرب مخصص إلى YOLO؟

قم بتمرير فئة المدرب المخصص (وليس مثيل) إلى trainer المعلمة في model.train():

from ultralytics import YOLO

model = YOLO("yolo26n.pt")
model.train(data="coco8.yaml", trainer=MyCustomTrainer)

في YOLO تتعامل الفئة مع إنشاء مثيل المدرب داخليًا. انظر تخصيص متقدم للحصول على مزيد من التفاصيل حول بنية المدرب.

ما هي طرق BaseTrainer التي يمكنني تجاوزها؟

الطرق الرئيسية المتاحة للتخصيص:

الطريقةالغرض
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. تسمح لك هذه بربط حلقة التدريب دون إنشاء فئة فرعية. يوضح مثال تجميد العمود الفقري أعلاه هذه الطريقة.

كيف يمكنني تخصيص دالة الخسارة دون إنشاء فئة فرعية للنموذج؟

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

model.train(data="coco8.yaml", box=10.0, cls=1.5, dfl=2.0)

لإجراء تغييرات هيكلية على الخسارة (مثل إضافة أوزان الفئات)، تحتاج إلى إنشاء فئة فرعية للخسارة والنموذج كما هو موضح في قسم أوزان الفئات.



📅 تم الإنشاء قبل 6 أيام ✏️ تم التحديث قبل 0 أيام
raimbekovmonuralpszr

تعليقات