Saltar 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 guiarte por la configuraci贸n necesaria, el proceso de generaci贸n de vectores de caracter铆sticas y la ejecuci贸n de una divisi贸n K-Fold de conjuntos de datos.

Resumen de la validaci贸n cruzada K-Fold

Tanto si tu proyecto incluye el conjunto de datos de Fruit Detection como una fuente de datos personalizada, este tutorial pretende ayudarte a comprender y aplicar la Validaci贸n Cruzada K-Fold para reforzar la fiabilidad y solidez de tus modelos de aprendizaje autom谩tico. Mientras aplicamos k=5 pliegues para este tutorial, ten en cuenta que el n煤mero 贸ptimo de pliegues puede variar en funci贸n de tu conjunto de datos y de las particularidades de tu proyecto.

Sin m谩s pre谩mbulos, 隆vamos a sumergirnos!

Configurar

  • Tus 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 Detecci贸n de Frutas.

    • 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, debes determinar el mejor n煤mero de pliegues para tu conjunto de datos espec铆fico.

  • Inicia un nuevo entorno virtual Python (venv) para tu proyecto y act铆valo. Utiliza pip (o tu gestor de paquetes preferido) para instalarlo:

    • La biblioteca Ultralytics : pip install -U ultralytics. Alternativamente, puedes clonar el archivo oficial repo.
    • Scikit-learn, pandas y PyYAML: pip install -U scikit-learn pandas pyyaml.
  • Comprueba que tus 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. Empieza creando un nuevo archivo Python e importa las bibliotecas necesarias.

    import datetime
    import shutil
    from collections import Counter
    from pathlib import Path
    
    import numpy as np
    import pandas as pd
    import yaml
    from sklearn.model_selection import KFold
    from ultralytics import YOLO
    
  2. Procede a recuperar todos los archivos de etiquetas de tu conjunto de datos.

    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, lee el contenido del archivo YAML del conjunto de datos y extrae los 铆ndices de las etiquetas de clase.

    yaml_file = "path/to/data.yaml"  # your data YAML with data directories and names dictionary
    with open(yaml_file, "r", encoding="utf8") as y:
        classes = yaml.safe_load(y)["names"]
    cls_idx = sorted(classes.keys())
    
  4. Inicializa una casilla vac铆a pandas Marco de datos.

    indx = [l.stem for l in labels]  # uses base filename as ID (no extension)
    labels_df = pd.DataFrame([], columns=cls_idx, index=indx)
    
  5. Cuenta las instancias de cada clase-etiqueta presentes en los archivos de anotaci贸n.

    for label in labels:
        lbl_counter = Counter()
    
        with open(label, "r") as lf:
            lines = lf.readlines()
    
        for l in lines:
            # classes for YOLO label uses integer at first position of each line
            lbl_counter[int(l.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 Marco de Datos 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 de tu conjunto de datos, y las columnas corresponden a tus 铆ndices de etiquetas de clase. Cada fila representa un pseudovector de caracter铆sticas, con el recuento de cada etiqueta de clase presente en tu conjunto de datos. Esta estructura de datos permite aplicar la Validaci贸n Cruzada K-Fold a un conjunto de datos de detecci贸n de objetos.

Dividir el conjunto de datos K-Fold

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

    • Importante:
      • Configurar shuffle=True garantiza una distribuci贸n aleatoria de las clases en tus divisiones.
      • Estableciendo random_state=M donde M es un n煤mero entero elegido, puedes obtener resultados repetibles.
    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=indx, columns=folds)
    
    for idx, (train, val) in enumerate(kfolds, start=1):
        folds_df[f"split_{idx}"].loc[labels_df.iloc[train].index] = "train"
        folds_df[f"split_{idx}"].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
    

El escenario ideal es que todas las proporciones de clase sean razonablemente similares para cada divisi贸n y entre las clases. Sin embargo, esto depender谩 de las caracter铆sticas espec铆ficas de tu conjunto de datos.

  1. A continuaci贸n, creamos los directorios y los archivos YAML del conjunto de datos para cada divisi贸n.

    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 (unchanged)
    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,
            )
    
  2. Por 煤ltimo, copia las im谩genes y las etiquetas en el directorio respectivo ("tren" o "val") para cada divisi贸n.

    • NOTA: El tiempo necesario para esta parte del c贸digo variar谩 en funci贸n del tama帽o de tu conjunto de datos y del hardware de tu sistema.
    for image, label in zip(images, labels):
        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 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")

Entrena YOLO utilizando K-Divisiones de Datos

  1. Primero, carga el modelo YOLO .

    weights_path = "path/to/weights.pt"
    model = YOLO(weights_path, task="detect")
    
  2. A continuaci贸n, itera sobre los archivos YAML del conjunto de datos para ejecutar el entrenamiento. Los resultados se guardar谩n en un directorio especificado por la opci贸n project y name argumentos. Por defecto, este directorio es 'exp/runs#', donde # es un 铆ndice entero.

    results = {}
    
    # Define your additional arguments here
    batch = 16
    project = "kfold_demo"
    epochs = 100
    
    for k in range(ksplit):
        dataset_yaml = ds_yamls[k]
        model.train(data=dataset_yaml, epochs=epochs, batch=batch, project=project)  # include any train arguments
        results[k] = model.metrics  # save output metrics for further analysis
    

Conclusi贸n

En esta gu铆a, hemos explorado el proceso de utilizar la validaci贸n cruzada de K pliegues 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 DataFrames de informes para visualizar las divisiones de datos y las distribuciones de etiquetas en dichas divisiones, lo que nos proporcion贸 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 ser especialmente 煤til en proyectos a gran escala o al 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 de tu 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.

Recuerda 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 eficazmente la validaci贸n cruzada en tus propios proyectos de aprendizaje autom谩tico. 隆Feliz programaci贸n!



Creado 2023-11-12, Actualizado 2024-05-18
Autores: glenn-jocher (6), Burhan-Q (1)

Comentarios