Ultralytics YOLO27: Únete a la lista de espera.
Get Started
Fundamentos de visión artificial·Consulta las métricas·Lección 8/10
Lecciónprincipiante

Divisiones que reflejan la realidad

Detecta imágenes casi duplicadas que se filtren entre entrenamiento y validación, y divide los datos según la unidad que será nueva durante el despliegue.

Si un modelo obtiene un 95 % en validación y un 60 % en producción, normalmente es porque la división engañaba. Imágenes casi idénticas —fotogramas de vídeo contiguos, la misma cámara el mismo día— acabaron en ambos conjuntos, así que la validación puso a prueba la memoria en lugar de la capacidad de generalización. Encontrarás exactamente esa filtración en un conjunto de datos real y después la corregirás con una división por grupos.

Resultado

Detecta imágenes casi duplicadas entre las distintas divisiones, divide los datos mediante una clave de grupo y comprueba que ningún grupo aparezca en más de una división.

Vía rápida
Si ya tienes experiencia, aquí tienes un resumen.
  1. El conjunto de entrenamiento ajusta los pesos, el de validación guía las decisiones y el de prueba ofrece una única evaluación.

  2. Busca imágenes casi idénticas entre las divisiones antes de fiarte de cualquier puntuación de validación.

  3. Identifica la clave de grupo: clip, cámara, día o sujeto.

  4. Mezcla los grupos, no las imágenes, y después comprueba que todas las clases aparezcan en cada división.

  5. Deja el conjunto de prueba intacto y úsalo una sola vez por experimento terminado.

Práctico

Tres divisiones, tres funciones#

DivisiónQuién aprende de ellaCuándo la consultas
EntrenamientoEl modelo: aquí se ajustan los pesosCada época
ValidaciónTú: hiperparámetros, épocas, tamaño del modelo, umbralesDespués de cada experimento
PruebaNadie: informa del número finalUna vez, después de tomar todas las decisiones

Cada decisión que tomas al observar una partición filtra un poco de información al modelo. Para eso sirve el conjunto de validación. Si ajustas el modelo con el conjunto de prueba, su cifra deja de ser una estimación honesta: habrás sobreajustado tus propias decisiones. En Ultralytics, model.val(split="test") evalúa la partición de prueba indicada en el YAML de tu conjunto de datos.

Encuentra la filtración en un conjunto de datos real#

Las imágenes de medical-pills se llaman Frame_0.jpg, Frame_4.jpg, Frame_8.jpg: son fotogramas de un vídeo. Es una señal de alerta: los fotogramas contiguos son casi idénticos, y una partición aleatoria los coloca a ambos lados. Compruébalo comparando pequeñas huellas en escala de grises de cada imagen de validación con cada imagen de entrenamiento:

from pathlib import Path

import numpy as np
from PIL import Image
from ultralytics.data.utils import check_det_dataset

data = check_det_dataset("medical-pills.yaml")

def thumbnail(path):
    """A 32x18 grayscale fingerprint: enough to spot near-identical frames."""
    return np.asarray(Image.open(path).convert("L").resize((32, 18)), dtype=float)

train = {p.name: thumbnail(p) for p in sorted(Path(data["train"]).glob("*.jpg"))}
pairs = []
for p in sorted(Path(data["val"]).glob("*.jpg")):
    v = thumbnail(p)
    name, diff = min(((n, np.abs(v - t).mean()) for n, t in train.items()), key=lambda x: x[1])
    pairs.append((diff, p.name, name))

pairs.sort()
print(f"{sum(d < 5 for d, _, _ in pairs)} of {len(pairs)} val images have a near-twin in train (difference < 5)")
for diff, val_name, train_name in pairs[:3]:
    print(f"  {val_name} ~ {train_name}  difference {diff:.1f}")
17 of 23 val images have a near-twin in train (difference < 5)
  Frame_344.jpg ~ Frame_56.jpg  difference 0.3
  Frame_28.jpg ~ Frame_312.jpg  difference 0.5
  Frame_24.jpg ~ Frame_308.jpg  difference 1.8

Una diferencia de 0,3 en una escala de 0–255 significa que las dos imágenes son prácticamente la misma. Abre val/Frame_24.jpg y train/Frame_308.jpg una al lado de la otra y verás los mismos blísteres desde el mismo ángulo. 17 de las 23 imágenes de validación tienen una gemela en el conjunto de entrenamiento, así que la puntuación de validación mide sobre todo lo bien que el modelo recuerda imágenes que ya ha visto. Está bien para una demostración rápida con un conjunto de datos, pero es precisamente lo que no debes llevar a producción.

Cuadro de diálogo de Ultralytics Platform para redistribuir las particiones de medical-pills, configurado con un 80 % para entrenamiento, un 20 % para validación y un 0 % para prueba

La misma trampa existe en cualquier herramienta. La propia descripción de este cuadro de diálogo indica que reasigna aleatoriamente todas las imágenes, así que es ideal si las imágenes son fotos independientes, pero provoca filtraciones si son fotogramas de vídeo. Ejecuta primero una comprobación de duplicados casi idénticos y mantén la proporción de prueba por encima del 0 % si quieres obtener una cifra final que nadie haya usado para ajustar el modelo.

Divide por grupos#

La regla: las particiones deben ser independientes respecto a la unidad que será nueva durante el despliegue.

DatosDividir por
VídeoClip o sesión de grabación
Flota de cámaras fijasCámara o ubicación
Escenas que cambian con el tiempoDía o semana
Datos médicos, deportivos o de personasPaciente, deportista o sujeto
Ráfagas de fotosEvento de captura

Los nombres de archivo suelen contener la clave del grupo. Este ejemplo crea 900 imágenes a partir de 6 cámaras y compara las dos estrategias:

import random
from collections import defaultdict

# File names carry the unit that must never straddle splits: camera_day_frame.jpg
images = [f"cam{c:02d}_day{d}_{f:04d}.jpg" for c in range(1, 7) for d in range(1, 4) for f in range(50)]

# WRONG: shuffle individual frames, so near-identical neighbors land in both splits
random.seed(0)
shuffled = random.sample(images, len(images))
train, val = shuffled[: int(0.8 * len(images))], shuffled[int(0.8 * len(images)) :]
shared = {i.split("_")[0] for i in train} & {i.split("_")[0] for i in val}
print(f"per-image split: {len(shared)} of 6 cameras appear in both train and val")

# RIGHT: shuffle whole cameras, then expand them back into images
groups = defaultdict(list)
for image in images:
    groups[image.split("_")[0]].append(image)
cameras = random.sample(sorted(groups), len(groups))
train = [i for cam in cameras[:5] for i in groups[cam]]
val = [i for cam in cameras[5:] for i in groups[cam]]
print(f"per-camera split: train {len(train)} images, val {len(val)} images, val cameras {cameras[5:]}")
per-image split: 6 of 6 cameras appear in both train and val
per-camera split: train 750 images, val 150 images, val cameras ['cam05']

Con la partición por imagen, la validación nunca incluye una cámara nueva. Con la partición por cámara, la puntuación estima qué ocurre con una cámara que el modelo nunca ha visto: justo la cifra que necesitas.

Errores habituales#

Barajados aleatorios en datos correlacionados

Síntoma: la validación da resultados mucho mejores que la primera prueba en campo. Solución: encuentra la clave de grupo —a menudo oculta en los nombres de archivo o en los metadatos de captura—, divide los datos según ella y ejecuta la comprobación de casi duplicados para confirmarlo.

Falta una clase en la validación

Síntoma: una clase poco frecuente sin AP o con AP calculada a partir de dos instancias. Solución: después de dividir por grupos, cuenta cada clase en cada partición e intercambia grupos completos hasta que todas las clases aparezcan en proporciones similares. sklearn.model_selection.StratifiedGroupKFold lo automatiza.

Ajustar con el conjunto de prueba

Síntoma: la puntuación de prueba sube poco a poco entre experimentos. Solución: congela el conjunto de prueba, extráelo de condiciones similares a las de producción e informa de sus resultados una vez por experimento finalizado. En conjuntos de datos pequeños, calcula la media de varias particiones mediante validación cruzada k-fold en lugar de reutilizar una sola.

Divide correctamente el conjunto de datos explica las proporciones y cómo aplicar todo esto a un conjunto de datos completo.

Pruébalo

Ejecuta la comprobación de duplicados casi idénticos en tu conjunto de datos (o en medical-pills). Anota cuántas imágenes de validación tienen una gemela casi idéntica en el conjunto de entrenamiento. Después, define la clave de grupo, vuelve a dividir los datos según esa clave y ejecuta la comprobación de filtraciones de la solución en las nuevas listas de archivos. El resultado que debes entregar es el recuento de gemelas casi idénticas antes y después, y una comprobación de filtraciones que imprima una lista vacía.

Cuándo se considera terminado
Has terminado la lección cuando se cumplen todas estas condiciones.
  • Tienes el recuento de gemelas casi idénticas de tu partición actual.

  • Has definido la clave de grupo y has vuelto a dividir los datos según ella.

  • La comprobación de filtraciones imprime una lista vacía para tus nuevas particiones.

Mostrar solución

Una comprobación de filtraciones que funciona con cualquier lista de nombres de archivo, si se le proporciona una función que extraiga la clave del grupo:

def leaked_groups(splits, key):
    """Return every group key that appears in more than one split."""
    owner, leaks = {}, set()
    for split, files in splits.items():
        for name in files:
            group = key(name)
            if owner.setdefault(group, split) != split:
                leaks.add(group)
    return sorted(leaks)

# Sustituye estas listas por tus nombres de archivo, p. ej., [p.name for p in Path("images/train").glob("*.jpg")]
splits = {
    "train": ["cam01_day1_0001.jpg", "cam01_day1_0002.jpg", "cam02_day1_0001.jpg"],
    "val": ["cam03_day2_0001.jpg", "cam01_day1_0003.jpg"],
    "test": ["cam04_day3_0001.jpg"],
}
print(leaked_groups(splits, key=lambda name: name.split("_")[0]))  # agrupa por cámara
['cam01']

cam01 aparece tanto en entrenamiento como en validación, así que esta partición tiene filtraciones. Mueve todas las imágenes cam01 a una misma partición y vuelve a ejecutarlo hasta que imprima []. En medical-pills, donde los fotogramas de un mismo vídeo vuelven a mostrar la misma vista, los nombres de archivo no contienen una clave de grupo clara: graba vídeos de validación nuevos con blísteres distintos o agrupa las imágenes por su huella de duplicados casi idénticos y asigna cada grupo completo a una sola partición.

Qué viene después

Con las particiones honestas listas, ahora leerás las métricas que producen.