تخصيص المُدرِّب#
تتمحور مسارات تدريب Ultralytics حول BaseTrainer ومُدرِّبات خاصة بالمهام مثل DetectionTrainer. تتولى هذه الفئات حلقة التدريب والتحقق وحفظ نقاط التحقق والتسجيل تلقائيًا. وعندما تحتاج إلى مزيد من التحكم — مثل تتبّع المقاييس المخصصة أو ضبط أوزان دالة الخسارة أو تطبيق جداول معدلات التعلم — يمكنك إنشاء فئة فرعية من المُدرِّب وتجاوز أساليب محددة.
يستعرض هذا الدليل سبعة تخصيصات شائعة:
- تسجيل المقاييس المخصصة (درجة F1) في نهاية كل حقبة
- إضافة أوزان الفئات لمعالجة عدم توازن الفئات
- حفظ أفضل نموذج استنادًا إلى مقياس مختلف
- تجميد العمود الفقري خلال أول N من الحقب، ثم إلغاء تجميده
- تحديد معدلات تعلم لكل طبقة
- مزامنة BatchNorm عبر وحدات GPU للتدريب باستخدام وحدات GPU متعددة
- تهيئة قص التدرجات لضبط الاستقرار
قبل قراءة هذا الدليل، تأكد من إلمامك بأساسيات تدريب نماذج 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."""
# Add your customizations here
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 | الاسترجاع لكل فئة |
ap50 | AP عند IoU يساوي 0.5 لكل فئة |
ap | AP عند 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 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() هذه القيم إلى متوسط يساوي 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.
| السيناريو | التوصية |
|---|---|
| التدريب باستخدام وحدات 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 # 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)يعمل المُدرِّب نفسه مع YOLO عند تبديل الفئة الأصل إلى DetectionTrainer (from ultralytics.models.yolo.detect import DetectionTrainer) وتحميل نقطة تحقق لـ YOLO باستخدام YOLO("yolo26n.pt"). يظل نص optimizer_step دون تغيير.
| عائلة البنية | القيمة المعتادة لـ max_norm |
|---|---|
| عائلة RT-DETR / DEIM / DETR | 0.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.