تخصيص Trainer#
يتمحور مسار التدريب في 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."""
# أضف التخصيصات الخاصة بك هنا
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 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.
| السيناريو | التوصية |
|---|---|
| التدريب باستخدام وحدات 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 دون تغيير.
| عائلة البنية | القيمة المعتادة لـ 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.