Ultralytics YOLO27: Únete a la lista de espera.
Get Started
Lecciónprincipiante

Control de calidad del conjunto de datos

Audita las etiquetas, el equilibrio de clases, los duplicados y la cobertura de las particiones con un script; deja que un modelo señale las posibles etiquetas que faltan y corrige lo que encuentres antes de entrenar.

Corregir las etiquetas suele mejorar la precisión más que ajustar los hiperparámetros. Los equipos suelen dedicar semanas a configurar opciones para ganar uno o dos puntos de mAP y luego descubren, en una tarde de auditoría, que llevaban tiempo etiquetando mal parte de una clase. El control de calidad detecta lo que la guía de etiquetado pasó por alto antes de que una ejecución de entrenamiento convierta errores de etiquetado en una cifra en la que nadie debería confiar.

Resultado

Ejecuta una auditoría automática de etiquetas y una revisión asistida por un modelo de los casos sospechosos, corrige lo que encuentres y registra el resultado en unas breves notas de control de calidad.

Vía rápida
Si ya tienes experiencia, aquí tienes un resumen.
  1. Automatiza el recuento: instancias por clase, proporción de fondos, clases no válidas, duplicados y clases ausentes del conjunto de entrenamiento.

  2. Deja que un modelo preentrenado marque las detecciones de alta confianza que no tengan una etiqueta coincidente y revísalas visualmente.

  3. Corrige el desequilibrio de clases primero recopilando datos, después sobremuestreando y, por último, ponderando la función de pérdida.

  4. Anota lo que has comprobado y corregido.

Práctico

Las siete comprobaciones#

Realiza las siete comprobaciones antes de la primera ejecución de entrenamiento y de nuevo después de cada lote importante de etiquetado. La guía sobre preprocesamiento de datos anotados explica varias de ellas en profundidad.

#ComprobaciónQué detectaCómo encontrarlo
1Etiquetas ausentesObjetos que los anotadores han pasado por altoRevisión de casos sospechosos más abajo; muestra visual
2Clases incorrectas o no válidasClases intercambiadas, ID fuera de la lista de clasesScript de auditoría; matriz de confusión tras una primera ejecución
3Cuadros demasiado holgadosCuadros con márgenes o bordes recortadosMuestra visual; avisos de cuadros diminutos de la auditoría
4DuplicadosCopias exactas y fotogramas casi idénticosScript de auditoría para las copias exactas; vista Clustering de Ultralytics Platform, coloreada por partición, para los casi duplicados
5Desequilibrio de clasesClases muy por debajo de los objetivos de la especificaciónScript de auditoría; pestaña Charts de Platform
6Cobertura de las particiones y fuga de datosClases ausentes de una partición; un mismo clip en dos particionesScript de auditoría; particiones agrupadas (siguiente lección)
7Proporción de fondosDemasiadas o muy pocas imágenes vacíasScript de auditoría; objetivo: 0–10 %

Automatiza el recuento#

Las comprobaciones 2, 4, 5, 6 y 7 son recuentos, así que automatízalas con un script. Esta auditoría lee un YAML de datos de detección cuyos valores train: y val: apuntan a carpetas de imágenes. Cuenta las instancias por clase y las imágenes de fondo de cada partición, señala los ID de clase no válidos, los cuadros diminutos, las filas de etiquetas duplicadas y las imágenes idénticas byte a byte, y enumera las clases que aparecen en val pero nunca en 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']

La última línea es el hallazgo. person tiene 10 instancias en val y ninguna en train, así que val evalúa una clase con la que el modelo nunca se ha entrenado. COCO8 es un conjunto de prueba rápida de 8 imágenes, por lo que allí es algo esperable; en tu conjunto de datos significa que debes corregir la partición o recopilar más datos antes de entrenar. Si hay problemas, se muestran debajo de su partición como ! 000000000025.jpg: class 99 is not in names o ! 000000000009.jpg: duplicate label rows.

Deja que un modelo señale las etiquetas sospechosas#

Las etiquetas ausentes son el error más perjudicial y el más difícil de detectar al revisar las imágenes una por una. Un modelo preentrenado puede señalarlas: una detección de alta confianza sin ninguna etiqueta cerca puede ser una etiqueta ausente o un error del modelo, y merece la pena revisar cualquiera de los dos casos. Esto se ejecuta en 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

De 128 imágenes, el script reduce la revisión a tres. Al abrirlas, se ven ambos tipos de hallazgos:

  • 000000000438.jpg es un cuadro con una docena de donuts en el que dos detecciones de alta confianza no se superponen a ningún donut etiquetado: posibles etiquetas ausentes o mal colocadas.
  • 000000000531.jpg señala una «persona» que en realidad es la sombra del fotógrafo en una pista de tenis. La etiqueta es correcta y el modelo se equivoca, justo por eso una persona debe revisar cada caso sospechoso en lugar de añadirlo automáticamente.

Reduce conf para ampliar la búsqueda y, cuando tengas un primer ajuste fino, sustitúyelo por tu propio modelo, ya que conoce tus clases.

Consulta los gráficos del conjunto de datos#

Pestaña Charts de Ultralytics Platform para el conjunto de datos medical-pills: Split Distribution muestra 80 imágenes de entrenamiento y 20 de validación, y Top Classes muestra pill con 1.755 anotaciones y tablet con 0

En Ultralytics Platform, la pestaña Charts del conjunto de datos representa gráficamente la distribución de las particiones, el recuento de clases y las estadísticas de los cuadros (documentación de conjuntos de datos). Esta captura ya muestra un hallazgo de control de calidad: la lista contiene dos clases, pero tablet tiene 0 de 1.755 anotaciones. No se puede aprender una clase sin etiquetas. Etiquétala o elimínala de la lista de clases antes de entrenar.

Corrige el desequilibrio de clases en el orden adecuado#

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

Una diferencia como esta produce un modelo que detecta con confianza las carretillas elevadoras, pero no los palés. Hay tres soluciones, en este orden:

  1. Recopila y etiqueta más ejemplos de la clase minoritaria. Siempre es la mejor opción: intenta reunir ≥ 1.500 imágenes y ≥ 10.000 instancias de cada clase.
  2. Sobremuestrea solo en train. Incluye más de una vez en la lista de archivos de entrenamiento las imágenes que contienen la clase minoritaria para que cada época las vea con más frecuencia. No incluyas las copias en val ni en test.
  3. Pondera la función de pérdida. El argumento de entrenamiento cls_pw (0.0 de forma predeterminada, hasta 1.0 para una ponderación inversa a la frecuencia completa) aumenta el peso de la pérdida de clasificación para las clases poco frecuentes. Recurre a esta opción solo después de aplicar las soluciones 1 y 2.
No elimines nunca imágenes difíciles para subir la puntuación

Eliminar de la validación imágenes borrosas, abarrotadas o confusas hace que suba el mAP y que el modelo no mejore, sino que sea menos honesto. Si una imagen es un fotograma real del despliegue, consérvala y corrige sus etiquetas. Elimina únicamente los valores atípicos verdaderos (error 9) y los duplicados exactos.

Escribe notas de control de calidad#

Bastan unas pocas líneas para responder a la pregunta «¿qué has comprobado?» cuando un cliente o auditor te lo pregunte meses después:

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
Pruébalo

Apunta qc_audit.py y suspect_labels.py a tu propio YAML de datos (o a COCO8 y COCO128) y ejecútalos. Corrige todos los problemas notificados, abre cada imagen sospechosa y decide si se trata de un error de etiquetado o del modelo; después, vuelve a ejecutar ambos scripts. El resultado: qc-notes.md con los recuentos antes y después de las correcciones.

Cuándo se considera terminado
Has terminado la lección cuando se cumplen todas estas condiciones.
  • El script de auditoría no informa de clases no válidas, filas de etiquetas duplicadas ni clases ausentes del conjunto de entrenamiento.

  • Se han abierto todos los casos sospechosos de la revisión asistida por el modelo y se han resuelto corrigiendo la etiqueta o identificando un error del modelo.

  • Las notas de control de calidad registran qué se ha comprobado, qué se ha encontrado y qué se ha corregido.

Mostrar solución

Notas de control de calidad de las dos ejecuciones de demostración de esta lección:

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

Fíjate en la última línea. Un conjunto de datos con una clase que solo aparece en val no supera el control de calidad, aunque todas las etiquetas individuales sean correctas, porque la métrica de esa clase mide algo que el modelo nunca tuvo la oportunidad de aprender.

Qué viene después

Con las etiquetas limpias, en la siguiente lección dividirás el conjunto de datos para que las puntuaciones de validación reflejen la producción y no la memoria.