Vai al contenuto

Convalida incrociata K-Fold con Ultralytics

Introduzione

Questa guida completa illustra l'implementazione della convalida incrociata K-Fold per i set di dati di rilevamento di oggetti all'interno dell'ecosistema Ultralytics . Sfrutteremo il formato di rilevamento YOLO e le librerie chiave di Python come sklearn, pandas e PyYaml per guidarti attraverso la configurazione necessaria, il processo di generazione dei vettori di caratteristiche e l'esecuzione di una divisione del dataset K-Fold.

Panoramica della convalida incrociata K-Fold

Sia che il tuo progetto coinvolga il dataset Fruit Detection o una fonte di dati personalizzata, questo tutorial ha lo scopo di aiutarti a comprendere e ad applicare la convalida incrociata K-Fold per rafforzare l'affidabilità e la robustezza dei tuoi modelli di apprendimento automatico. Mentre applichiamo k=5 Tieni presente che il numero ottimale di pieghe per questo tutorial può variare a seconda del tuo set di dati e delle specifiche del tuo progetto.

Senza ulteriori indugi, tuffiamoci!

Configurazione

  • Le tue annotazioni devono essere nel formato di rilevamentoYOLO .

  • Questa guida presuppone che i file di annotazione siano disponibili localmente.

  • Per la nostra dimostrazione, utilizziamo il dataset Fruit Detection.

    • Questo set di dati contiene un totale di 8479 immagini.
    • Include 6 etichette di classe, ciascuna con il numero totale di istanze elencate di seguito.
Etichetta di classe Conteggio delle istanze
Apple 7049
Uva 7202
Ananas 1613
Arancione 15549
Banana 3536
Anguria 1976
  • I pacchetti necessari di Python includono:

    • ultralytics
    • sklearn
    • pandas
    • pyyaml
  • Questo tutorial opera con k=5 pieghe. Tuttavia, devi determinare il numero migliore di pieghe per il tuo set di dati specifico.

  • Avvia un nuovo ambiente virtuale Python (venv) per il tuo progetto e attivarlo. Utilizza pip (o il tuo gestore di pacchetti preferito) per l'installazione:

    • La biblioteca di Ultralytics : pip install -U ultralytics. In alternativa, puoi clonare il sito ufficiale repo.
    • Scikit-learn, pandas e PyYAML: pip install -U scikit-learn pandas pyyaml.
  • Verifica che le annotazioni siano nel formato di rilevamentoYOLO .

    • Per questo tutorial, tutti i file di annotazione si trovano nella cartella Fruit-Detection/labels directory.

Generazione di vettori di caratteristiche per il dataset di rilevamento degli oggetti

  1. Inizia creando un nuovo file Python e importa le librerie necessarie.

    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. Procedi con il recupero di tutti i file di etichetta per il tuo set di dati.

    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. Ora, leggi il contenuto del file YAML del dataset ed estrai gli indici delle etichette delle classi.

    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. Inizializza un file vuoto pandas DataFrame.

    indx = [l.stem for l in labels]  # uses base filename as ID (no extension)
    labels_df = pd.DataFrame([], columns=cls_idx, index=indx)
    
  5. Conta le istanze di ogni etichetta di classe presenti nei file di annotazione.

    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. Di seguito è riportato un esempio di visualizzazione del DataFrame popolato:

                                                           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
    

Le righe indicizzano i file delle etichette, ciascuno corrispondente a un'immagine del tuo set di dati, mentre le colonne corrispondono agli indici delle etichette delle classi. Ogni riga rappresenta uno pseudo vettore di caratteristiche, con il conteggio di ogni etichetta di classe presente nel tuo dataset. Questa struttura di dati consente di applicare la convalida incrociata K-Fold a un set di dati per il rilevamento di oggetti.

Divisione del set di dati K-Fold

  1. Ora utilizzeremo l'opzione KFold classe da sklearn.model_selection per generare k suddivisione del set di dati.

    • Importante:
      • Impostazione shuffle=True garantisce una distribuzione randomizzata delle classi negli split.
      • Impostando random_state=M dove M è un numero intero scelto, puoi ottenere risultati ripetibili.
    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. Il set di dati è stato suddiviso in k pieghe, ognuna delle quali ha un elenco di train e val indici. Costruiremo un DataFrame per visualizzare questi risultati in modo più chiaro.

    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. Ora calcoleremo la distribuzione delle etichette delle classi per ogni piega come rapporto delle classi presenti in val a coloro che sono presenti in 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 scenario ideale è che tutti i rapporti tra le classi siano ragionevolmente simili per ogni divisione e tra le classi. Questo, tuttavia, dipende dalle caratteristiche specifiche del tuo set di dati.

  4. Successivamente, creiamo le directory e i file YAML del dataset per ogni suddivisione.

    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,
            )
    
  5. Infine, copia le immagini e le etichette nella rispettiva directory ("train" o "val") per ogni divisione.

    • NOTA: il tempo richiesto per questa parte del codice varia in base alle dimensioni del set di dati e all'hardware del 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)
    

Salva i record (opzionale)

Opzionalmente, puoi salvare i record dei DataFrames della divisione K-Fold e della distribuzione delle etichette come file CSV per riferimenti futuri.

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

Addestrare YOLO utilizzando K-Fold Data Splits

  1. Per prima cosa, carica il modello YOLO .

    weights_path = "path/to/weights.pt"
    model = YOLO(weights_path, task="detect")
    
  2. Quindi, itera sui file YAML del set di dati per eseguire l'addestramento. I risultati verranno salvati in una directory specificata dall'opzione project e name argomenti. Per impostazione predefinita, questa directory è 'exp/runs#' dove # è un indice intero.

    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
    

Conclusione

In questa guida abbiamo esplorato il processo di utilizzo della convalida incrociata K-Fold per l'addestramento del modello di rilevamento degli oggetti di YOLO . Abbiamo imparato a dividere il nostro set di dati in K partizioni, assicurando una distribuzione bilanciata delle classi nelle diverse pieghe.

Abbiamo anche esplorato la procedura per la creazione di DataFrames di report per visualizzare le suddivisioni dei dati e le distribuzioni delle etichette tra queste suddivisioni, fornendoci una chiara visione della struttura dei nostri set di formazione e convalida.

Opzionalmente, abbiamo salvato i nostri record per riferimenti futuri, il che potrebbe essere particolarmente utile in progetti su larga scala o per la risoluzione di problemi legati alle prestazioni del modello.

Infine, abbiamo implementato l'addestramento vero e proprio del modello utilizzando ogni split in un ciclo, salvando i risultati dell'addestramento per ulteriori analisi e confronti.

Questa tecnica di convalida incrociata K-Fold è un modo robusto per sfruttare al meglio i dati a disposizione e aiuta a garantire che le prestazioni del modello siano affidabili e coerenti tra i diversi sottoinsiemi di dati. In questo modo si ottiene un modello più generalizzabile e affidabile che ha meno probabilità di adattarsi eccessivamente a modelli di dati specifici.

Ricorda che, sebbene in questa guida abbiamo utilizzato YOLO , questi passaggi sono per lo più trasferibili ad altri modelli di apprendimento automatico. La comprensione di questi passaggi ti permette di applicare la convalida incrociata in modo efficace nei tuoi progetti di apprendimento automatico. Buona programmazione!



Created 2023-11-12, Updated 2024-06-10
Authors: glenn-jocher (8), IvorZhu331 (1), Burhan-Q (1)

Commenti