Контроль качества набора данных
Проверь скриптом разметку, баланс классов, дубликаты и охват разбиений, попроси модель указать на вероятные пропущенные метки и исправь найденное до начала обучения.
Исправление разметки обычно повышает точность сильнее, чем настройка гиперпараметров. Команды неделями подбирают настройки ради одного-двух пунктов mAP, а затем за один день аудита обнаруживают, что часть одного класса всё это время была размечена неверно. Контроль качества помогает найти то, что упустило руководство по разметке, прежде чем ошибки в метках исказят результат обучения, которому нельзя доверять.
Практические занятия
Семь проверок#
Выполни все семь проверок до первого запуска обучения и после каждой крупной партии разметки. Некоторые из них подробно описаны в руководстве Предобработка размеченных данных.
| # | Проверка | Что выявляет | Как проверить |
|---|---|---|---|
| 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 вкладка набора данных Диаграммы отображает распределение по разбиениям, число экземпляров классов и статистику рамок (документация по наборам данных). На этом скриншоте уже видна проблема контроля качества: в списке два класса, но у tablet — 0 из 1 755 аннотаций. Класс без меток невозможно выучить. Разметь его или удали из списка классов до начала обучения.
Устраняй дисбаланс классов в правильном порядке#
forklift ████████████████████████ 8000 instances
person ████████████████ 5400
pallet ██ 700 ← red flagИз-за такого дисбаланса модель уверенно распознаёт погрузчики, но пропускает поддоны. Вот три способа решения в порядке приоритета:
- Собери и разметь больше данных для редкого класса. Это всегда лучший вариант: стремись к ≥ 1 500 изображениям и ≥ 10 000 экземплярам каждого класса.
- Используй передискретизацию только для train. Несколько раз добавь изображения с редким классом в список обучающих файлов, чтобы модель видела их чаще каждую эпоху. Не добавляй копии в val и test.
- Взвесь функцию потерь. Аргумент обучения
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Показать решение
Заметки по контролю качества для двух демонстрационных запусков из этого урока:
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, не проходит проверку качества, даже если каждая отдельная метка правильная: метрика для этого класса оценивает то, чему модель не могла научиться.