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

ضبط جودة مجموعة البيانات

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

عادةً ما يؤدي إصلاح الوسوم إلى دقة أعلى مما يحققه ضبط المعلمات الفائقة. فكثيرًا ما تقضي الفرق أسابيع في ضبط الإعدادات لكسب نقطة أو نقطتين من mAP، ثم تكتشف في مراجعة تدقيقية لا تستغرق سوى ظهيرة واحدة أن جزءًا من إحدى الفئات وُسِم خطأً طوال الوقت. يكشف ضبط الجودة ما أغفله دليل وضع الوسوم قبل أن يحوّل تشغيل التدريب أخطاء الوسوم إلى رقم لا ينبغي لأحد الوثوق به.

النتيجة

نفّذ تدقيقًا آليًا للوسوم ومراجعةً بمساعدة النموذج للعناصر المشتبه بها، وأصلح ما تكشفه، وسجّل النتيجة في ملاحظات موجزة لضبط الجودة.

المسار السريع
إذا كنت تعرف الأساسيات بالفعل، فإليك النسخة المختصرة.
  1. أنشئ نصًا برمجيًا لإحصاء عدد النماذج لكل فئة، ونسبة صور الخلفية، والفئات غير الصالحة، والتكرارات، والفئات الغائبة عن مجموعة التدريب.

  2. دع نموذجًا مدرّبًا مسبقًا يحدد الاكتشافات الواثقة التي لا يقابلها وسم، ثم راجعها بصريًا.

  3. عالج اختلال توازن الفئات بجمع مزيد من البيانات أولًا، ثم بزيادة أخذ العينات، وأخيرًا بترجيح الخسارة.

  4. دوّن ما تحققت منه وما أصلحته.

عملي

الفحوصات السبع#

أجرِ الفحوصات السبع كلها قبل أول عملية تدريب، ثم أعدها بعد كل دفعة كبيرة من الوسوم. يشرح دليل المعالجة المسبقة للبيانات المشروحة عددًا منها بالتفصيل.

#الفحصما الذي يكشفهطريقة الكشف
1الوسوم المفقودةالكائنات التي أغفلها القائمون بالوسممراجعة العناصر المشتبه بها أدناه؛ وفحص عينة بصريًا
2الفئات الخاطئة أو غير الصالحةفئات متبادلة، ومعرّفات خارج قائمة الفئاتبرنامج التدقيق النصي؛ ومصفوفة الالتباس بعد أول عملية تدريب
3المربعات الفضفاضةمربعات تتضمن هوامش أو حوافًا مقصوصةفحص عينة بصريًا؛ ورصد المربعات الصغيرة جدًا في التدقيق
4التكراراتنسخ مطابقة وإطارات متطابقة تقريبًابرنامج التدقيق النصي للنسخ المطابقة؛ وعرض Clustering في Ultralytics Platform، مع تلوين العناصر بحسب التقسيم، للكشف عن التكرارات شبه المطابقة
5اختلال توازن الفئاتفئات تقل أعدادها كثيرًا عن المستهدف في المواصفاتبرنامج التدقيق النصي؛ وعلامة التبويب Charts في Platform
6تغطية التقسيمات وتسرب البياناتفئات غائبة عن أحد التقسيمات؛ مقطع واحد موجود في تقسيمينبرنامج التدقيق النصي؛ والتقسيمات المجمعة (الدرس التالي)
7نسبة صور الخلفيةعدد قليل جدًا أو كبير جدًا من الصور الفارغةبرنامج التدقيق النصي؛ والهدف 0–10%

أتمتة الإحصاء#

الفحوصات 2 و4 و5 و6 و7 مسائل إحصاء، لذا أنشئ نصوصًا برمجية لتنفيذها. يقرأ هذا التدقيق ملف YAML لبيانات الكشف، حيث يشير train: وval: إلى مجلدي الصور. ويحصي النماذج لكل فئة وصور الخلفية في كل تقسيم، ويرصد معرّفات الفئات غير الصالحة، والمربعات الصغيرة جدًا، وصفوف الوسوم المكررة، والصور المتطابقة بايتًا ببايت، ويسرد الفئات الموجودة في val وغير الموجودة في train:

import hashlib
from collections import Counter
from pathlib import Path

from ultralytics.data.utils import IMG_FORMATS, check_det_dataset, img2label_paths

data = check_det_dataset("coco8.yaml")  # downloads COCO8 on first run; swap in your data YAML
names, seen = data["names"], {}

for split in ("train", "val"):
    images = sorted(p for p in Path(data[split]).rglob("*.*") if p.suffix[1:].lower() in IMG_FORMATS)
    instances, hashes, problems, background = Counter(), Counter(), [], 0
    for img, label in zip(images, img2label_paths([str(p) for p in images])):
        hashes[hashlib.md5(img.read_bytes()).hexdigest()] += 1
        rows = [r.split() for r in Path(label).read_text().splitlines() if r.strip()] if Path(label).exists() else []
        background += not rows
        if len(rows) != len({tuple(r) for r in rows}):
            problems.append(f"{img.name}: duplicate label rows")
        for r in rows:
            cls, w, h = int(r[0]), float(r[3]), float(r[4])
            if cls not in names:
                problems.append(f"{img.name}: class {cls} is not in names")
            elif w * h < 1e-4:
                problems.append(f"{img.name}: tiny {names[cls]} box")
            instances[cls] += 1
    seen[split] = set(instances)
    duplicates = sum(n - 1 for n in hashes.values())
    print(f"{split}: {len(images)} images, {background} background, {duplicates} duplicate images")
    for cls, n in instances.most_common():
        print(f"  {names.get(cls, cls):>12}  {n}")
    for problem in problems:
        print("  !", problem)

print("in val but never in train:", sorted(names[c] for c in seen["val"] - seen["train"] if c in names))
train: 4 images, 0 background, 0 duplicate images
        orange  4
          bowl  3
       giraffe  2
...
val: 4 images, 0 background, 0 duplicate images
        person  10
         horse  2
...
in val but never in train: ['dog', 'elephant', 'horse', 'person', 'umbrella']

السطر الأخير هو النتيجة. يحتوي person على 10 نماذج في val ولا يحتوي على أي منها في train، لذا تقيّم val فئةً لم يسبق للنموذج أن تدرّب عليها. مجموعة COCO8 مخصصة لاختبار أولي من 8 صور، لذلك هذا متوقع فيها؛ أما في مجموعة بياناتك فيعني ذلك ضرورة إصلاح التقسيم أو جمع مزيد من البيانات قبل التدريب. عند وجود مشكلات، تظهر تحت اسم التقسيم الخاص بها، مثل ! 000000000025.jpg: class 99 is not in names أو ! 000000000009.jpg: duplicate label rows.

دع النموذج يشير إلى الوسوم المشتبه بها#

الوسوم المفقودة هي أكثر الأخطاء ضررًا وأصعبها اكتشافًا بمجرد تصفح الصور. يستطيع نموذج مدرّب مسبقًا الإشارة إليها: فالاكتشاف الواثق الذي لا يقابله أي وسم في جواره إما وسم مفقود وإما خطأ من النموذج، وكلاهما يستحق المراجعة. يعمل هذا على COCO128:

from pathlib import Path

import torch

from ultralytics import YOLO
from ultralytics.data.utils import check_det_dataset, img2label_paths
from ultralytics.utils.metrics import box_iou
from ultralytics.utils.ops import xywh2xyxy

data = check_det_dataset("coco128.yaml")  # swap in your data YAML
model = YOLO("yolo26n.pt")  # a pretrained model, or your own first fine-tune
images = sorted(Path(data["train"]).glob("*.jpg"))

suspects = []
for result, label in zip(model(images, conf=0.6, stream=True, verbose=False), img2label_paths(map(str, images))):
    rows = [r.split() for r in Path(label).read_text().splitlines() if r.strip()] if Path(label).exists() else []
    boxes = xywh2xyxy(torch.tensor([[float(v) for v in r[1:5]] for r in rows]).reshape(-1, 4))
    iou = box_iou(result.boxes.xyxyn, boxes)  # model boxes vs labeled boxes
    for j, (cls, conf) in enumerate(zip(result.boxes.cls.int().tolist(), result.boxes.conf.tolist())):
        if not rows or iou[j].max() < 0.3:  # confident detection with no label anywhere near it
            suspects.append((conf, Path(result.path).name, model.names[cls]))

print(f"{len(suspects)} confident detections with no matching label; top 5:")
for conf, name, cls in sorted(suspects, reverse=True)[:5]:
    print(f"  {conf:.2f}  {cls:<12} {name}")
4 confident detections with no matching label; top 5:
  0.77  donut        000000000438.jpg
  0.75  person       000000000531.jpg
  0.71  donut        000000000438.jpg
  0.66  bottle       000000000436.jpg

من أصل 128 صورة، يضيّق البرنامج النصي نطاق المراجعة إلى ثلاث صور. ويُظهر فتحها نوعَي النتائج:

  • 000000000438.jpg صندوق يضم اثنتي عشرة قطعة دونات، حيث لا يتداخل اكتشافان واثقان مع أي قطعة دونات موسومة: وهما مرشحان لوسوم مفقودة أو موضوعة في غير مكانها.
  • 000000000531.jpg يرصد «شخصًا» تبيّن أنه ظل المصوّر على ملعب تنس. الوسم صحيح والنموذج مخطئ، وهذا بالضبط سبب مراجعة شخص لكل عنصر مشتبه به بدلًا من إضافته تلقائيًا.

خفّض قيمة conf لتوسيع نطاق البحث، واستبدله بأول نموذج ضبط دقيق خاص بك عند توفره، لأنه يعرف فئاتك.

اقرأ مخططات مجموعة البيانات#

علامة التبويب Charts في Ultralytics Platform لمجموعة بيانات الحبوب الطبية: يعرض Split Distribution وجود 80 صورة تدريب و20 صورة تحقق، ويعرض Top Classes فئة pill مع 1,755 وسمًا وفئة tablet مع 0

في Ultralytics Platform، تعرض علامة التبويب Charts لمجموعة البيانات توزيع التقسيمات وأعداد الفئات وإحصاءات المربعات (وثائق مجموعة البيانات). تكشف لقطة الشاشة هذه بالفعل نتيجةً لضبط الجودة: تضم قائمة الفئات فئتين، لكن tablet لا يحتوي على أي وسم من أصل 1,755. لا يمكن تعلّم فئة بلا وسوم. إما أن تضع لها وسومًا، أو تحذفها من قائمة الفئات قبل التدريب.

عالج اختلال توازن الفئات بالترتيب الصحيح#

   forklift  ████████████████████████ 8000 instances
   person    ████████████████ 5400
   pallet    ██ 700                              ← red flag

يؤدي تفاوت كهذا إلى نموذج يثق باكتشاف الرافعات الشوكية، لكنه لا يرصد المنصات. هناك ثلاثة حلول، بالترتيب:

  1. اجمع مزيدًا من بيانات الفئة الأقل تمثيلًا وضع وسومًا لها. هذا هو الحل الأفضل دائمًا: استهدف ≥ 1,500 صورة و≥ 10,000 نموذج لكل فئة.
  2. زِد أخذ العينات منها في train فقط. أدرج الصور التي تحتوي على الفئة الأقل تمثيلًا أكثر من مرة في قائمة ملفات التدريب، لكي يراها النموذج بوتيرة أعلى في كل حقبة. لا تضع النسخ في val أو test.
  3. زِن الخسارة. ترفع وسيطة التدريب cls_pw (تكون 0.0 افتراضيًا، وتصل إلى 1.0 عند تطبيق الترجيح الكامل عكسيًا بحسب التكرار) وزن خسارة التصنيف للفئات النادرة. لا تلجأ إليها إلا بعد تنفيذ الحلين 1 و2.
لا تحذف الصور الصعبة أبدًا لرفع النتيجة

إزالة الصور الضبابية أو المزدحمة أو المربكة من مجموعة التحقق ترفع mAP ولا تحسّن النموذج، بل تجعله أقل نزاهة. إذا كانت الصورة إطارًا حقيقيًا من النشر، فأبقها؛ وأصلح تسمياتها بدلًا من ذلك. أزل فقط القيم الشاذة الحقيقية (الخطأ 9) والتكرارات التامة.

دوّن ملاحظات ضبط الجودة#

تكفي بضعة أسطر للإجابة عن سؤال «ما الذي تحققت منه؟» عندما يطرحه عميل أو مدقق بعد أشهر:

Dataset: forklift-v1   Audit date: 2026-05-09
Images: 4,820   Labels: 18,442   Background: 380 (7.9%)
Audit script: 3 invalid class IDs fixed, 41 duplicate label rows removed
Suspect review: 62 flagged, 19 missing labels added, 43 model errors ignored
Visual sample: 120 images, 4 mislabeled (3.3%), fixed
Split check: grouped by camera-day, every class in every split
Imbalance: pallet 2.5x below person; collecting more next round
Ready to train: yes
جرّبه

وجّه qc_audit.py وsuspect_labels.py إلى ملف YAML لبياناتك (أو COCO8 وCOCO128) وشغّلهما. أصلح كل مشكلة يُبلغ عنها، وافتح كل صورة مشتبه بها وحدّد ما إذا كان الخطأ في الوسم أم في النموذج، ثم أعد تشغيل النصين البرمجيين. المخرج المطلوب: qc-notes.md متضمنًا الأعداد قبل الإصلاحات وبعدها.

متى يُعدّ مكتملًا
تكون قد أتممت الدرس عند تحقق جميع الشروط التالية.
  • لا يُبلغ برنامج التدقيق النصي عن أي فئات غير صالحة أو صفوف وسوم مكررة أو فئات غائبة عن train.

  • فُتحت كل صورة مشتبه بها من المراجعة بمساعدة النموذج، وحُسمت بوصفها إصلاحًا للوسم أو خطأً من النموذج.

  • تسجل ملاحظات ضبط الجودة ما جرى التحقق منه، وما تم العثور عليه، وما تم إصلاحه.

عرض الحل

ملاحظات ضبط الجودة لعمليتي التشغيل التجريبيتين في هذا الدرس:

Dataset: COCO8 + COCO128 demo   Audit date: 2026-05-09
qc_audit.py (COCO8): 0 invalid classes, 0 duplicate rows, 0 duplicate images
  FINDING: dog, elephant, horse, person, umbrella appear in val only
  ACTION: re-split by group or collect these classes into train before training
suspect_labels.py (COCO128, conf 0.6): 4 suspects in 3 images
  000000000438.jpg: 2 donuts without matching labels -> relabel the donut box
  000000000531.jpg: "person" is the photographer's shadow -> model error, label kept
  000000000436.jpg: bottle -> open and decide
Ready to train: no, fix the val-only classes first

انتبه إلى السطر الأخير. تفشل مجموعة البيانات في ضبط الجودة إذا كانت إحدى الفئات موجودة في val فقط، حتى لو كان كل وسم منفرد صحيحًا؛ لأن المقياس الخاص بهذه الفئة يقيس شيئًا لم تتح للنموذج فرصة تعلّمه.

ما الخطوة التالية

بعد تنظيف الوسوم، يقسّم الدرس التالي مجموعة البيانات بحيث تعكس درجات التحقق بيئة الإنتاج بدلًا من حفظ البيانات.

سجّل الدخول لحفظ التقدم