Ultralytics YOLO27: Встать в лист ожидания.
Get Started
Урокначинающий

Контроль качества набора данных

Проверь скриптом разметку, баланс классов, дубликаты и охват разбиений, попроси модель указать на вероятные пропущенные метки и исправь найденное до начала обучения.

Исправление разметки обычно повышает точность сильнее, чем настройка гиперпараметров. Команды неделями подбирают настройки ради одного-двух пунктов mAP, а затем за один день аудита обнаруживают, что часть одного класса всё это время была размечена неверно. Контроль качества помогает найти то, что упустило руководство по разметке, прежде чем ошибки в метках исказят результат обучения, которому нельзя доверять.

Результат

Выполни автоматический аудит разметки и проверку подозрительных случаев с помощью модели, исправь найденные проблемы и зафиксируй результат в кратких заметках по контролю качества.

Ускоренное прохождение
Если ты уже освоился, вот краткая версия.
  1. Автоматизируй подсчёт: число экземпляров каждого класса, доля фоновых изображений, недопустимые классы, дубликаты и классы, отсутствующие в train.

  2. Попроси предварительно обученную модель отмечать уверенные детекции без соответствующих меток и проверь их визуально.

  3. Устраняй дисбаланс классов в таком порядке: сначала собирай данные, затем используй передискретизацию и только в последнюю очередь — взвешивание функции потерь.

  4. Запиши, что ты проверил и исправил.

Практические занятия

Семь проверок#

Выполни все семь проверок до первого запуска обучения и после каждой крупной партии разметки. Некоторые из них подробно описаны в руководстве Предобработка размеченных данных.

#ПроверкаЧто выявляетКак проверить
1Пропущенные меткиОбъекты, которые разметчики пропустилиПроверка подозрительных случаев ниже; визуальная выборка
2Неверные или недопустимые классыПерепутанные классы, идентификаторы вне списка классовСкрипт аудита; матрица ошибок после первого запуска
3Неточные рамкиРамки с большими отступами или обрезанными краямиВизуальная выборка; поиск слишком маленьких рамок с помощью аудита
4ДубликатыПолные копии и почти идентичные кадрыСкрипт аудита для поиска полных копий; представление Кластеризация в Ultralytics Platform для поиска почти дублирующихся изображений с цветовой маркировкой по разбиению
5Дисбаланс классовКлассы, показатели которых значительно ниже целевых значений спецификацииСкрипт аудита; вкладка 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, значит, при оценке учитывается класс, на котором модель не обучалась. 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, чтобы расширить поиск; когда обучишь свою первую модель с дообучением, замени её на эту модель, поскольку она знает твои классы.

Изучи диаграммы набора данных#

Вкладка «Диаграммы» Ultralytics Platform для набора данных медицинских таблеток: в разделе «Распределение по разбиениям» показано 80 обучающих и 20 валидационных изображений, а в разделе «Популярные классы» — 1 755 аннотаций класса «таблетка» и 0 аннотаций класса «таблетка в оболочке»

На Ultralytics Platform вкладка набора данных Диаграммы отображает распределение по разбиениям, число экземпляров классов и статистику рамок (документация по наборам данных). На этом скриншоте уже видна проблема контроля качества: в списке два класса, но у tablet — 0 из 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.
Не удаляй сложные изображения, чтобы повысить показатель

Удаление из val размытых, загруженных или неоднозначных изображений повышает 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, не проходит проверку качества, даже если каждая отдельная метка правильная: метрика для этого класса оценивает то, чему модель не могла научиться.

Что дальше

В следующем уроке, когда разметка будет очищена, ты разобьёшь набор данных так, чтобы оценки на валидации отражали работу в production, а не запоминание обучающих примеров.

Войди, чтобы сохранять прогресс