Validación cruzada K-Fold con Ultralytics
Introducción
Esta guía completa ilustra la implementación de la validación cruzada K-Fold para datasets de detección de objetos dentro del ecosistema de Ultralytics. Aprovecharemos el formato de detección de YOLO y bibliotecas clave de Python como sklearn, pandas y PyYAML para guiarte a través de la configuración necesaria, el proceso de generación de vectores de características y la ejecución de una división del dataset K-Fold.
Tanto si tu proyecto involucra el dataset Fruit Detection como una fuente de datos personalizada, este tutorial tiene como objetivo ayudarte a comprender y aplicar la validación cruzada K-Fold para reforzar la fiabilidad y robustez de tus modelos de aprendizaje automático. Aunque estamos aplicando k=5 pliegues (folds) para este tutorial, ten en cuenta que el número óptimo de pliegues puede variar dependiendo de tu dataset y de las especificaciones de tu proyecto.
Empecemos.
Configuración
-
Tus anotaciones deben estar en el formato de detección YOLO.
-
Esta guía asume que los archivos de anotación están disponibles localmente.
-
Para nuestra demostración, usamos el dataset Fruit Detection.
- Este dataset contiene un total de 8479 imágenes.
- Incluye 6 etiquetas de clase, cada una con sus recuentos totales de instancias listados a continuación.
| Etiqueta de clase | Recuento de instancias |
|---|---|
| Apple | 7049 |
| Grapes | 7202 |
| Pineapple | 1613 |
| Orange | 15549 |
| Banana | 3536 |
| Watermelon | 1976 |
-
Los paquetes de Python necesarios incluyen:
ultralyticssklearnpandaspyyaml
-
Este tutorial opera con
k=5pliegues. Sin embargo, debes determinar el mejor número de pliegues para tu dataset específico.
-
Inicia un nuevo entorno virtual de Python (
venv) para tu proyecto y actívalo. Usapip(o tu gestor de paquetes preferido) para instalar:- La biblioteca de Ultralytics:
pip install -U ultralytics. Alternativamente, puedes clonar el repo oficial. - Scikit-learn, pandas y PyYAML:
pip install -U scikit-learn pandas pyyaml.
- La biblioteca de Ultralytics:
-
Verifica que tus anotaciones estén en el formato de detección YOLO.
- Para este tutorial, todos los archivos de anotación se encuentran en el directorio
Fruit-Detection/labels.
- Para este tutorial, todos los archivos de anotación se encuentran en el directorio
Generación de vectores de características para el dataset de detección de objetos
-
Empieza creando un nuevo archivo de Python
example.pypara los pasos a continuación. -
Procede a recuperar todos los archivos de etiquetas para tu dataset.
from pathlib import Path dataset_path = Path("./Fruit-detection") # replace with 'path/to/dataset' for your custom data labels = sorted(dataset_path.rglob("*labels/*.txt")) # all data in 'labels' -
Ahora, lee el contenido del archivo YAML del dataset y extrae los índices de las etiquetas de clase.
import yaml yaml_file = "path/to/data.yaml" # your data YAML with data directories and names dictionary with open(yaml_file, encoding="utf8") as y: classes = yaml.safe_load(y)["names"] cls_idx = sorted(classes.keys()) -
Inicializa un DataFrame de
pandasvacío.import pandas as pd index = [label.stem for label in labels] # uses base filename as ID (no extension) labels_df = pd.DataFrame([], columns=cls_idx, index=index) -
Cuenta las instancias de cada etiqueta de clase presente en los archivos de anotación.
from collections import Counter for label in labels: lbl_counter = Counter() with open(label) as lf: lines = lf.readlines() for line in lines: # classes for YOLO label uses integer at first position of each line lbl_counter[int(line.split(" ", 1)[0])] += 1 labels_df.loc[label.stem] = lbl_counter labels_df = labels_df.fillna(0.0) # replace `nan` values with `0.0` -
La siguiente es una vista de muestra del DataFrame poblado:
0 1 2 3 4 5 '0000a16e4b057580_jpg.rf.00ab48988370f64f5ca8ea4...' 0.0 0.0 0.0 0.0 0.0 7.0 '0000a16e4b057580_jpg.rf.7e6dce029fb67f01eb19aa7...' 0.0 0.0 0.0 0.0 0.0 7.0 '0000a16e4b057580_jpg.rf.bc4d31cdcbe229dd022957a...' 0.0 0.0 0.0 0.0 0.0 7.0 '00020ebf74c4881c_jpg.rf.508192a0a97aa6c4a3b6882...' 0.0 0.0 0.0 1.0 0.0 0.0 '00020ebf74c4881c_jpg.rf.5af192a2254c8ecc4188a25...' 0.0 0.0 0.0 1.0 0.0 0.0 ... ... ... ... ... ... ... 'ff4cd45896de38be_jpg.rf.c4b5e967ca10c7ced3b9e97...' 0.0 0.0 0.0 0.0 0.0 2.0 'ff4cd45896de38be_jpg.rf.ea4c1d37d2884b3e3cbce08...' 0.0 0.0 0.0 0.0 0.0 2.0 'ff5fd9c3c624b7dc_jpg.rf.bb519feaa36fc4bf630a033...' 1.0 0.0 0.0 0.0 0.0 0.0 'ff5fd9c3c624b7dc_jpg.rf.f0751c9c3aa4519ea3c9d6a...' 1.0 0.0 0.0 0.0 0.0 0.0 'fffe28b31f2a70d4_jpg.rf.7ea16bd637ba0711c53b540...' 0.0 6.0 0.0 0.0 0.0 0.0
Las filas indexan los archivos de etiquetas, cada uno correspondiente a una imagen en tu dataset, y las columnas corresponden a tus índices de etiquetas de clase. Cada fila representa un pseudo vector de características, con el recuento de cada etiqueta de clase presente en tu dataset. Esta estructura de datos permite la aplicación de la validación cruzada K-Fold a un dataset de detección de objetos.
División del dataset K-Fold
-
Ahora usaremos la clase
KFolddesklearn.model_selectionpara generarkdivisiones del dataset.- Importante:
- Establecer
shuffle=Truegarantiza una distribución aleatoria de clases en tus divisiones. - Al establecer
random_state=MdondeMes un número entero elegido, puedes obtener resultados repetibles.
- Establecer
import random from sklearn.model_selection import KFold random.seed(0) # for reproducibility ksplit = 5 kf = KFold(n_splits=ksplit, shuffle=True, random_state=20) # setting random_state for repeatable results kfolds = list(kf.split(labels_df)) - Importante:
-
El dataset ahora se ha dividido en
kpliegues, cada uno con una lista de índices detrain(entrenamiento) yval(validación). Construiremos un DataFrame para mostrar estos resultados más claramente.folds = [f"split_{n}" for n in range(1, ksplit + 1)] folds_df = pd.DataFrame(index=index, columns=folds) for i, (train, val) in enumerate(kfolds, start=1): folds_df[f"split_{i}"].loc[labels_df.iloc[train].index] = "train" folds_df[f"split_{i}"].loc[labels_df.iloc[val].index] = "val" -
Ahora calcularemos la distribución de las etiquetas de clase para cada pliegue como una proporción de las clases presentes en
valrespecto a las presentes entrain.fold_lbl_distrb = pd.DataFrame(index=folds, columns=cls_idx) for n, (train_indices, val_indices) in enumerate(kfolds, start=1): train_totals = labels_df.iloc[train_indices].sum() val_totals = labels_df.iloc[val_indices].sum() # To avoid division by zero, we add a small value (1E-7) to the denominator ratio = val_totals / (train_totals + 1e-7) fold_lbl_distrb.loc[f"split_{n}"] = ratioEl escenario ideal es que todas las proporciones de clase sean razonablemente similares para cada división y a través de las clases. Esto, sin embargo, estará sujeto a los detalles de tu dataset.
-
A continuación, creamos los directorios y los archivos YAML del dataset para cada división.
import datetime supported_extensions = [".jpg", ".jpeg", ".png"] # Initialize an empty list to store image file paths images = [] # Loop through supported extensions and gather image files for ext in supported_extensions: images.extend(sorted((dataset_path / "images").rglob(f"*{ext}"))) # Create the necessary directories and dataset YAML files save_path = Path(dataset_path / f"{datetime.date.today().isoformat()}_{ksplit}-Fold_Cross-val") save_path.mkdir(parents=True, exist_ok=True) ds_yamls = [] for split in folds_df.columns: # Create directories split_dir = save_path / split split_dir.mkdir(parents=True, exist_ok=True) (split_dir / "train" / "images").mkdir(parents=True, exist_ok=True) (split_dir / "train" / "labels").mkdir(parents=True, exist_ok=True) (split_dir / "val" / "images").mkdir(parents=True, exist_ok=True) (split_dir / "val" / "labels").mkdir(parents=True, exist_ok=True) # Create dataset YAML files dataset_yaml = split_dir / f"{split}_dataset.yaml" ds_yamls.append(dataset_yaml) with open(dataset_yaml, "w") as ds_y: yaml.safe_dump( { "path": split_dir.as_posix(), "train": "train", "val": "val", "names": classes, }, ds_y, ) -
Por último, copia las imágenes y las etiquetas en el directorio respectivo ('train' o 'val') para cada división.
- NOTA: El tiempo necesario para esta parte del código variará según el tamaño de tu dataset y el hardware de tu sistema.
import shutil from tqdm import tqdm for image, label in tqdm(zip(images, labels), total=len(images), desc="Copying files"): for split, k_split in folds_df.loc[image.stem].items(): # Destination directory img_to_path = save_path / split / k_split / "images" lbl_to_path = save_path / split / k_split / "labels" # Copy image and label files to new directory (SamefileError if file already exists) shutil.copy(image, img_to_path / image.name) shutil.copy(label, lbl_to_path / label.name)
Guardar registros (opcional)
Opcionalmente, puedes guardar los registros de la división K-Fold y los DataFrames de distribución de etiquetas como archivos CSV para referencia futura.
folds_df.to_csv(save_path / "kfold_datasplit.csv")
fold_lbl_distrb.to_csv(save_path / "kfold_label_distribution.csv")Entrenar YOLO usando divisiones de datos K-Fold
-
Primero, carga el modelo YOLO.
from ultralytics import YOLO weights_path = "path/to/weights.pt" # use yolo26n.pt for a small model model = YOLO(weights_path, task="detect") -
A continuación, itera sobre los archivos YAML del dataset para ejecutar el entrenamiento. Los resultados se guardarán en un directorio especificado por los argumentos
projectyname. Por defecto, este directorio es 'runs/detect/train#' donde # es un índice entero.results = {} # Define your additional arguments here batch = 16 project = "kfold_demo" epochs = 100 for k, dataset_yaml in enumerate(ds_yamls): model = YOLO(weights_path, task="detect") results[k] = model.train( data=dataset_yaml, epochs=epochs, batch=batch, project=project, name=f"fold_{k + 1}" ) # include any additional train arguments -
También puedes usar la función Ultralytics data.utils.autosplit para la división automática de datasets:
from ultralytics.data.split import autosplit # Automatically split dataset into train/val/test autosplit(path="path/to/images", weights=(0.8, 0.2, 0.0), annotated_only=True)
Conclusión
En esta guía, hemos explorado el proceso de usar la validación cruzada K-Fold para entrenar el modelo de detección de objetos YOLO. Aprendimos cómo dividir nuestro dataset en K particiones, asegurando una distribución de clase equilibrada a través de los diferentes pliegues.
También exploramos el procedimiento para crear DataFrames de informe para visualizar las divisiones de datos y las distribuciones de etiquetas a través de estas divisiones, proporcionándonos una visión clara de la estructura de nuestros conjuntos de entrenamiento y validación.
Opcionalmente, guardamos nuestros registros para futura referencia, lo cual podría ser particularmente útil en proyectos a gran escala o al solucionar problemas de rendimiento del modelo.
Finalmente, implementamos el entrenamiento del modelo real usando cada división en un bucle, guardando nuestros resultados de entrenamiento para análisis y comparación posteriores.
Esta técnica de validación cruzada K-Fold es una forma robusta de aprovechar al máximo tus datos disponibles, y ayuda a asegurar que el rendimiento de tu modelo sea fiable y consistente a través de diferentes subconjuntos de datos. Esto resulta en un modelo más generalizable y fiable que es menos probable que sufra sobreajuste a patrones de datos específicos.
Recuerda que aunque usamos YOLO en esta guía, estos pasos son mayormente transferibles a otros modelos de aprendizaje automático. Entender estos pasos te permite aplicar la validación cruzada eficazmente en tus propios proyectos de aprendizaje automático.
Preguntas frecuentes
¿Qué es la validación cruzada K-Fold y por qué es útil en la detección de objetos?
La validación cruzada K-Fold es una técnica donde el dataset se divide en 'k' subconjuntos (pliegues) para evaluar el rendimiento del modelo de forma más fiable. Cada pliegue sirve como datos de entrenamiento y datos de validación. En el contexto de la detección de objetos, usar la validación cruzada K-Fold ayuda a asegurar que el rendimiento de tu modelo Ultralytics YOLO sea robusto y generalizable a través de diferentes divisiones de datos, mejorando su fiabilidad. Para instrucciones detalladas sobre cómo configurar la validación cruzada K-Fold con Ultralytics YOLO, consulta Validación cruzada K-Fold con Ultralytics.
¿Cómo implemento la validación cruzada K-Fold usando Ultralytics YOLO?
Para implementar la validación cruzada K-Fold con Ultralytics YOLO, debes seguir estos pasos:
- Verifica que las anotaciones estén en el formato de detección YOLO.
- Usa bibliotecas de Python como
sklearn,pandasypyyaml. - Crea vectores de características a partir de tu dataset.
- Divide tu dataset usando
KFolddesklearn.model_selection. - Entrena el modelo YOLO en cada división.
Para una guía completa, mira la sección División del dataset K-Fold en nuestra documentación.
¿Por qué debería usar Ultralytics YOLO para la detección de objetos?
Ultralytics YOLO ofrece detección de objetos en tiempo real de última generación con alta precisión y eficiencia. Es versátil, soportando múltiples tareas de visión artificial tales como detección, segmentación y clasificación. Además, se integra perfectamente con herramientas como Ultralytics Platform para el entrenamiento y despliegue de modelos sin código. Para más detalles, explora los beneficios y características en nuestra página de Ultralytics YOLO.
¿Cómo puedo asegurar que mis anotaciones estén en el formato correcto para Ultralytics YOLO?
Tus anotaciones deben seguir el formato de detección YOLO. Cada archivo de anotación debe listar la clase del objeto, junto con sus coordenadas de caja delimitadora en la imagen. El formato YOLO asegura un procesamiento de datos optimizado y estandarizado para entrenar modelos de detección de objetos. Para más información sobre el formato correcto de anotación, visita la guía de formato de detección YOLO.
¿Puedo usar la validación cruzada K-Fold con datasets personalizados distintos al Fruit Detection?
Sí, puedes usar la validación cruzada K-Fold con cualquier dataset personalizado siempre que las anotaciones estén en el formato de detección YOLO. Reemplaza las rutas del dataset y las etiquetas de clase con las específicas de tu dataset personalizado. Esta flexibilidad asegura que cualquier proyecto de detección de objetos pueda beneficiarse de una evaluación de modelo robusta usando la validación cruzada K-Fold. Para un ejemplo práctico, revisa nuestra sección Generación de vectores de características.