Ir al contenido

Validación cruzada K-Fold con Ultralytics

Introducción

Esta completa guía ilustra la implementación de la validación cruzada K-Fold para conjuntos de datos de detección de objetos dentro del ecosistema Ultralytics . Aprovecharemos el formato de detección YOLO y las bibliotecas clave de Python como sklearn, pandas y PyYaml para guiarle 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 de conjuntos de datos K-Fold.

Resumen de la validación cruzada K-Fold

Tanto si su proyecto incluye el conjunto de datos de Fruit Detection como una fuente de datos personalizada, este tutorial pretende ayudarle a comprender y aplicar la validación cruzada K-Fold para reforzar la fiabilidad y solidez de su proyecto. aprendizaje automático modelos. Mientras aplicamos k=5 para este tutorial, tenga en cuenta que el número óptimo de pliegues puede variar en función de su conjunto de datos y de las características específicas de su proyecto.

Sin más dilación, ¡entremos en materia!

Configurar

  • Sus anotaciones deben estar en el formato de detecciónYOLO .

  • Esta guía asume que los archivos de anotación están disponibles localmente.

  • Para nuestra demostración, utilizamos el conjunto de datos Fruit Detection.

    • Este conjunto de datos contiene un total de 8479 imágenes.
    • Incluye 6 etiquetas de clase, cada una con su recuento total de instancias indicado a continuación.
Etiqueta de clase Recuento de instancias
Manzana 7049
Uvas 7202
Piña 1613
Naranja 15549
Plátano 3536
Sandía 1976
  • Los paquetes necesarios de Python incluyen:

    • ultralytics
    • sklearn
    • pandas
    • pyyaml
  • Este tutorial funciona con k=5 pliegues. Sin embargo, debe determinar el mejor número de pliegues para su conjunto de datos específico.

  • Inicie un nuevo entorno virtual Python (venv) para su proyecto y actívelo. Utilice pip (o su gestor de paquetes preferido) para instalar:

    • La biblioteca Ultralytics : pip install -U ultralytics. Como alternativa, puede clonar la página oficial repo.
    • Scikit-learn, pandas y PyYAML: pip install -U scikit-learn pandas pyyaml.
  • Compruebe que sus anotaciones están en el formato de detecciónYOLO .

    • Para este tutorial, todos los archivos de anotación se encuentran en la carpeta Fruit-Detection/labels directorio.

Generación de vectores de características para el conjunto de datos de detección de objetos

  1. Empiece por crear un nuevo example.py Python para los pasos siguientes.

  2. Proceda a recuperar todos los archivos de etiquetas de su conjunto de datos.

    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'
    
  3. Ahora, lea el contenido del archivo YAML del conjunto de datos y extraiga 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())
    
  4. Inicializar un pandas DataFrame.

    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)
    
  5. Contar las instancias de cada clase-etiqueta presentes 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(" ")[0])] += 1
    
        labels_df.loc[label.stem] = lbl_counter
    
    labels_df = labels_df.fillna(0.0)  # replace `nan` values with `0.0`
    
  6. A continuación se muestra una vista de ejemplo del DataFrame rellenado:

                                                           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 de los cuales corresponde a una imagen del conjunto de datos, y las columnas corresponden a los índices de las etiquetas de clase. Cada fila representa un pseudo-vector de características, con el recuento de cada etiqueta de clase presente en el conjunto de datos. Esta estructura de datos permite la aplicación de la Validación Cruzada K-Fold a un conjunto de datos de detección de objetos.

Dividir el conjunto de datos en K-Fold

  1. Ahora utilizaremos el KFold clase de sklearn.model_selection para generar k divisiones del conjunto de datos.

    • Importante:
      • Configuración shuffle=True garantiza una distribución aleatoria de las clases en sus divisiones.
      • Estableciendo random_state=M donde M es un número entero elegido, puede obtener resultados repetibles.
    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))
    
  2. El conjunto de datos se ha dividido en k pliegues, cada uno con una lista de train y val índices. Construiremos un DataFrame para mostrar estos resultados con mayor claridad.

    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"
    
  3. Ahora calcularemos la distribución de las etiquetas de clase de cada pliegue como proporción de las clases presentes en val a los presentes en train.

    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}"] = ratio
    

    Lo ideal es que todas las proporciones de clase sean razonablemente similares en cada división y en todas las clases. Sin embargo, esto dependerá de las características específicas de su conjunto de datos.

  4. A continuación, creamos los directorios y los archivos YAML del conjunto de datos 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,
            )
    
  5. Por último, copie 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á en función del tamaño del conjunto de datos y del hardware del 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, puede guardar los registros de los DataFrames de división K-Fold y distribución de etiquetas como archivos CSV para futuras consultas.

folds_df.to_csv(save_path / "kfold_datasplit.csv")
fold_lbl_distrb.to_csv(save_path / "kfold_label_distribution.csv")

Entrenar YOLO utilizando K-Fold Data Splits

  1. En primer lugar, cargue el modelo YOLO .

    from ultralytics import YOLO
    
    weights_path = "path/to/weights.pt"  # use yolo11n.pt for a small model
    model = YOLO(weights_path, task="detect")
    
  2. A continuación, itere sobre los archivos YAML del conjunto de datos para ejecutar el entrenamiento. Los resultados se guardarán en un directorio especificado por el parámetro project y name argumentos. 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
    
  3. También puede utilizar la función data.utils.autosplitUltralytics para dividir automáticamente los conjuntos de datos:

    from ultralytics.data.utils 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 utilizar la validación cruzada K-Fold para entrenar el modelo de detección de objetos YOLO . Hemos aprendido a dividir nuestro conjunto de datos en K particiones, garantizando una distribución de clases equilibrada en los distintos pliegues.

También exploramos el procedimiento de creación de informes DataFrames para visualizar las divisiones de datos y las distribuciones de etiquetas en estas divisiones, lo que nos proporciona una visión clara de la estructura de nuestros conjuntos de entrenamiento y validación.

Opcionalmente, guardamos nuestros registros para futuras consultas, lo que podría resultar especialmente útil en proyectos a gran escala o a la hora de solucionar problemas de rendimiento del modelo.

Por último, implementamos el entrenamiento real del modelo utilizando cada división en un bucle, guardando nuestros resultados de entrenamiento para su posterior análisis y comparación.

Esta técnica de validación cruzada K-Fold es una forma sólida de aprovechar al máximo los datos disponibles, y ayuda a garantizar que el rendimiento del modelo es fiable y coherente en diferentes subconjuntos de datos. El resultado es un modelo más generalizable y fiable, con menos probabilidades de sobreajustarse a patrones de datos específicos.

Recuerde que, aunque en esta guía hemos utilizado YOLO , estos pasos son en su mayoría transferibles a otros modelos de aprendizaje automático. Comprender estos pasos te permitirá aplicar la validación cruzada de forma efectiva en tus propios proyectos de aprendizaje automático. ¡Feliz programación!

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 por K pliegues es una técnica en la que el conjunto de datos se divide en "k" subconjuntos (pliegues) para evaluar el rendimiento del modelo de forma más fiable. Cada pliegue sirve tanto de datos de entrenamiento como de datos de validación. En el contexto de la detección de objetos, el uso de la Validación Cruzada K-Fold ayuda a asegurar que el rendimiento de su modelo Ultralytics YOLO es robusto y generalizable a través de diferentes divisiones de datos, mejorando su fiabilidad. Para obtener instrucciones detalladas sobre la configuración de la validación cruzada K-Fold con Ultralytics YOLO , consulte Validación cruzada K-Fold con Ultralytics.

¿Cómo implemento la validación cruzada K-Fold utilizando Ultralytics YOLO ?

Para implementar la Validación Cruzada K-Fold con Ultralytics YOLO , debe seguir los siguientes pasos:

  1. Verifique que las anotaciones estén en el formato de detecciónYOLO .
  2. Utilice las bibliotecas Python como sklearn, pandasy pyyaml.
  3. Cree vectores de características a partir de su conjunto de datos.
  4. Divida el conjunto de datos mediante KFold de sklearn.model_selection.
  5. Entrene el modelo YOLO en cada división.

Para obtener una guía completa, consulte la sección Dividir conjuntos de datos K-Fold de nuestra documentación.

¿Por qué utilizar Ultralytics YOLO para la detección de objetos?

Ultralytics YOLO ofrece detección de objetos de última generación en tiempo real con gran precisión y eficacia. Es versátil y admite múltiples tareas de visión por ordenador, como la detección, la segmentación y la clasificación. Además, se integra a la perfección con herramientas como Ultralytics HUB para el entrenamiento y despliegue de modelos sin código. Para más detalles, explore las ventajas y características en nuestra páginaUltralytics YOLO .

¿Cómo puedo asegurarme de que mis anotaciones están en el formato correcto para Ultralytics YOLO ?

Sus anotaciones deben seguir el formato de detección YOLO . Cada archivo de anotación debe enumerar la clase de objeto, junto con las coordenadas de su cuadro delimitador en la imagen. El formato YOLO garantiza un procesamiento de datos racionalizado y estandarizado para el entrenamiento de modelos de detección de objetos. Para más información sobre el formato adecuado de las anotaciones, visite la guía del formato de detecciónYOLO .

¿Puedo utilizar la validación cruzada K-Fold con conjuntos de datos personalizados distintos de la detección de frutas?

Sí, puede utilizar la validación cruzada K-Fold con cualquier conjunto de datos personalizado siempre que las anotaciones estén en el formato de detección YOLO . Sustituya las rutas del conjunto de datos y las etiquetas de clase por las específicas de su conjunto de datos personalizado. Esta flexibilidad garantiza que cualquier proyecto de detección de objetos pueda beneficiarse de una sólida evaluación del modelo mediante la validación cruzada K-Fold. Para ver un ejemplo práctico, consulte nuestra sección Generación de vectores de características.

Creado hace 1 año ✏️ Actualizado hace 8 días

Comentarios