Controlo de qualidade do conjunto de dados
Audita os rótulos, o equilíbrio entre classes, as duplicatas e a cobertura das divisões com um script; pede a um modelo que assinale possíveis rótulos em falta e corrige o que encontrares antes do treino.
Corrigir rótulos costuma aumentar a precisão mais do que ajustar hiperparâmetros. As equipas passam muitas vezes semanas a ajustar configurações para ganhar um ou dois pontos de mAP, mas depois descobrem, numa tarde de auditoria, que parte de uma classe estava mal rotulada desde o início. O controlo de qualidade deteta o que o guia de anotação não identificou antes que uma execução de treino transforme erros nos rótulos num número em que ninguém deve confiar.
Prático
As sete verificações#
Executa as sete verificações antes do primeiro treino e novamente após cada lote importante de anotações. O guia de pré-processamento de dados anotados explica várias delas em detalhe.
| # | Verificação | O que deteta | Como detetá-lo |
|---|---|---|---|
| 1 | Rótulos em falta | Objetos ignorados por quem anotou | Revisão de casos suspeitos abaixo; amostra visual |
| 2 | Classes erradas ou inválidas | Classes trocadas, IDs fora da lista de classes | Script de auditoria; matriz de confusão após a primeira execução |
| 3 | Caixas imprecisas | Caixas com margens excessivas ou bordas cortadas | Amostra visual; sinalizações de caixas minúsculas pela auditoria |
| 4 | Duplicatas | Cópias exatas e fotogramas quase idênticos | Script de auditoria para cópias exatas; vista Clustering da Ultralytics Platform, colorida por divisão, para quase duplicatas |
| 5 | Desequilíbrio entre classes | Classes muito abaixo das metas da especificação | Script de auditoria; separador Charts da Platform |
| 6 | Cobertura das divisões e fuga de dados | Classes ausentes de uma divisão; um clipe em duas divisões | Script de auditoria; divisões agrupadas (próxima lição) |
| 7 | Proporção de imagens de fundo | Poucas ou demasiadas imagens vazias | Script de auditoria; objetivo de 0–10% |
Automatiza as contagens#
As verificações 2, 4, 5, 6 e 7 envolvem contagens, por isso automatiza-as com um script. Esta auditoria lê um YAML de dados de deteção cujos valores train: e val: apontam para pastas de imagens. Conta as instâncias por classe e as imagens de fundo em cada divisão, assinala IDs de classe inválidos, caixas minúsculas, linhas de rótulos duplicadas e imagens idênticas byte a byte, e lista as classes que aparecem em val, mas nunca em 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']A última linha mostra o resultado. person tem 10 instâncias em val e nenhuma em train, pelo que val avalia uma classe com a qual o modelo nunca foi treinado. COCO8 é um conjunto de teste rápido com 8 imagens, por isso é esperado nesse caso; no teu conjunto de dados, significa que deves corrigir a divisão ou recolher mais dados antes do treino. Quando há problemas, estes aparecem sob a respetiva divisão como ! 000000000025.jpg: class 99 is not in names ou ! 000000000009.jpg: duplicate label rows.
Pede a um modelo que assinale rótulos suspeitos#
Os rótulos em falta são o erro mais prejudicial e o mais difícil de detetar percorrendo as imagens. Um modelo pré-treinado pode assinalá-los: uma deteção confiável sem nenhum rótulo nas proximidades pode indicar um rótulo em falta ou um erro do modelo, e vale a pena analisar qualquer uma das hipóteses. Este exemplo é executado em 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.jpgDas 128 imagens, o script reduz a revisão a três. Ao abri-las, encontras os dois tipos de resultado:
000000000438.jpgmostra uma caixa com uma dúzia de donuts, em que duas deteções confiáveis não se sobrepõem a nenhum donut rotulado: possíveis rótulos em falta ou mal posicionados.000000000531.jpgassinala uma «pessoa» que é a sombra do fotógrafo num campo de ténis. O rótulo está correto e o modelo está errado, precisamente por isso uma pessoa deve rever cada caso suspeito em vez de o adicionar automaticamente.
Reduz conf para ampliar a procura e, quando tiveres o teu primeiro ajuste fino, substitui-o por esse modelo, pois conhecerá as tuas classes.
Lê os gráficos do conjunto de dados#

Na Ultralytics Platform, o separador Charts do conjunto de dados apresenta gráficos da distribuição das divisões, das contagens de classes e das estatísticas das caixas (documentação dos conjuntos de dados). Esta captura de ecrã já revela um problema de controlo de qualidade: a lista contém duas classes, mas tablet tem 0 de 1,755 anotações. Não é possível aprender uma classe sem rótulos. Rotula-a ou remove-a da lista de classes antes do treino.
Corrige o desequilíbrio entre classes pela ordem certa#
forklift ████████████████████████ 8000 instances
person ████████████████ 5400
pallet ██ 700 ← red flagUma diferença como esta produz um modelo confiante com empilhadeiras, mas que não deteta paletes. Há três soluções, por esta ordem:
- Recolhe e rotula mais exemplos da classe minoritária. É sempre a melhor opção: procura obter ≥ 1,500 imagens e ≥ 10,000 instâncias de cada classe.
- Sobreamostra-a apenas em train. Inclui mais do que uma vez, na lista de ficheiros de treino, as imagens que contêm a classe minoritária, para que apareçam com maior frequência em cada época. Não incluas as cópias em val nem em test.
- Pondera a função de perda. O argumento de treino
cls_pw(0.0 por predefinição, até 1.0 para ponderação inversa à frequência completa) aumenta o peso da perda de classificação para as classes raras. Recorre a esta opção apenas depois das opções 1 e 2.
Remover imagens desfocadas, cheias ou confusas do conjunto de validação aumenta o mAP sem melhorar o modelo, apenas o torna menos honesto. Se a imagem for um quadro real de implantação, mantenha-a; corrija os rótulos. Remova apenas verdadeiros casos atípicos (erro 9) e duplicatas exatas.
Regista as notas de CQ#
Bastam algumas linhas e permitem responder «o que verificaste?» quando um cliente ou auditor perguntar meses depois:
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: yesMostrar solução
Notas de CQ das duas execuções de demonstração desta lição:
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 firstRepara na última linha. Um conjunto de dados com uma classe que aparece apenas em val não passa no controlo de qualidade, mesmo que todos os rótulos individuais estejam corretos, porque a métrica dessa classe avalia algo que o modelo nunca teve oportunidade de aprender.