Vai al contenuto

K-Fold Cross Validation con Ultralytics

Introduzione

Questa guida completa illustra l'implementazione della K-Fold Cross Validation per i dataset di object detection all'interno dell'ecosistema Ultralytics. Sfrutteremo il formato di detection YOLO e librerie Python chiave come sklearn, pandas e PyYaml per guidarti attraverso la configurazione necessaria, il processo di generazione di vettori di feature e l'esecuzione di una divisione del dataset K-Fold.

Panoramica sulla K-Fold Cross Validation

Che il tuo progetto coinvolga il dataset Fruit Detection o una fonte di dati personalizzata, questo tutorial mira ad aiutarti a comprendere e applicare la convalida incrociata K-Fold per rafforzare l'affidabilità e la robustezza del tuo apprendimento automatico models. Mentre stiamo applicando k=5 fold per questo tutorial, tieni presente che il numero ottimale di fold può variare a seconda del tuo set di dati e delle specifiche del tuo progetto.

Senza ulteriori indugi, tuffiamoci subito!

Configurazione

  • Le tue annotazioni devono essere nel formato di rilevamento YOLO.

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

  • Per la nostra dimostrazione, utilizziamo il set di dati Fruit Detection.

    • Questo dataset contiene un totale di 8479 immagini.
    • Include 6 etichette di classe, ognuna con il suo conteggio totale di istanze elencato di seguito.
Etichetta Classe Conteggio istanze
Apple 7049
Uva 7202
Ananas 1613
Arancione 15549
Banana 3536
Anguria 1976
  • I pacchetti Python necessari includono:

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

  • Avvia un nuovo ambiente virtuale python (venv) per il tuo progetto e attivalo. Usa pip (o il gestore di pacchetti preferito) per installare:

    • La libreria Ultralytics: pip install -U ultralytics. In alternativa, puoi clonare l'ufficiale repo.
    • Scikit-learn, pandas e PyYAML: pip install -U scikit-learn pandas pyyaml.
  • Verificare che le annotazioni siano nel formato di rilevamento YOLO.

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

Generazione di vettori di caratteristiche per il set di dati di rilevamento oggetti

  1. Inizia creando un nuovo example.py File Python per i passaggi seguenti.

  2. Procedi per recuperare tutti i file di etichette per il tuo set di dati.

    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. Ora, leggi il contenuto del file YAML del set di dati ed estrai gli indici delle etichette di classe.

    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. Inizializza un elemento vuoto 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. Conta le istanze di ogni etichetta di classe presente nei file di annotazione.

    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`
    
  6. Di seguito è riportata una visualizzazione di esempio 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 di etichette, ognuno corrispondente a un'immagine nel tuo dataset, e le colonne corrispondono agli indici delle etichette di classe. 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 l'applicazione della validazione incrociata K-Fold a un dataset di rilevamento oggetti.

Suddivisione del Dataset K-Fold

  1. Ora useremo il KFold classe da sklearn.model_selection per generare k divisioni del dataset.

    • Importante:
      • Impostazione shuffle=True assicura una distribuzione casuale delle classi nelle tue suddivisioni.
      • Impostando random_state=M dove M è un numero intero scelto, puoi ottenere risultati ripetibili.
    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. Il dataset è stato ora suddiviso in k fold, ognuno con 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=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. Ora calcoleremo la distribuzione delle etichette di classe per ogni fold come rapporto delle classi presenti in val a quelli 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 suddivisione e tra le classi. Tuttavia, ciò dipenderà dalle specificità del tuo set di dati.

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

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

    • NOTA: Il tempo necessario per questa porzione di codice varierà in base alle dimensioni del dataset e all'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)
    

Salva i record (opzionale)

Opzionalmente, puoi salvare i record dello split K-Fold e i DataFrame di distribuzione delle etichette come file CSV per riferimento futuro.

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

Addestra YOLO utilizzando gli split di dati K-Fold

  1. Innanzitutto, carica il modello 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. Successivamente, scorri i file YAML del dataset per eseguire il training. I risultati verranno salvati in una directory specificata dal project e name argomenti. Per impostazione predefinita, questa directory è 'runs/detect/train#' dove # è un indice intero.

    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. È inoltre possibile utilizzare la funzione Ultralytics data.utils.autosplit per la suddivisione automatica del dataset:

    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)
    

Conclusione

In questa guida, abbiamo esplorato il processo di utilizzo della cross-validation K-Fold per l'addestramento del modello di rilevamento oggetti YOLO. Abbiamo imparato come dividere il nostro dataset in K partizioni, garantendo una distribuzione di classe bilanciata tra le diverse fold.

Abbiamo anche esplorato la procedura per creare 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 training e validazione.

Opzionalmente, abbiamo salvato i nostri record per riferimento futuro, il che potrebbe essere particolarmente utile in progetti su larga scala o durante la risoluzione dei problemi relativi alle prestazioni del modello.

Infine, abbiamo implementato l'effettivo training del modello utilizzando ogni suddivisione in un ciclo, salvando i nostri risultati di training per ulteriori analisi e confronti.

Questa tecnica di cross-validation K-Fold è un modo efficace per sfruttare al meglio i dati disponibili e aiuta a garantire che le prestazioni del modello siano affidabili e coerenti tra diversi sottoinsiemi di dati. Ciò si traduce in un modello più generalizzabile e affidabile, meno incline all'overfitting su specifici pattern di dati.

Ricorda che, sebbene in questa guida abbiamo utilizzato YOLO, questi passaggi sono per lo più trasferibili ad altri modelli di machine learning. Comprendere questi passaggi ti consente di applicare efficacemente la cross-validation nei tuoi progetti di machine learning. Buon coding!

FAQ

Cos'è la convalida incrociata K-Fold e perché è utile nel rilevamento oggetti?

La K-Fold Cross Validation è una tecnica in cui il dataset viene diviso in 'k' sottoinsiemi (fold) per valutare le prestazioni del modello in modo più affidabile. Ogni fold funge sia da dati di training che di validazione. Nel contesto del rilevamento di oggetti, l'utilizzo della K-Fold Cross Validation aiuta a garantire che le prestazioni del tuo modello Ultralytics YOLO siano robuste e generalizzabili tra diverse suddivisioni dei dati, migliorandone l'affidabilità. Per istruzioni dettagliate sulla configurazione della K-Fold Cross Validation con Ultralytics YOLO, fare riferimento a K-Fold Cross Validation con Ultralytics.

Come posso implementare la K-Fold Cross Validation utilizzando Ultralytics YOLO?

Per implementare la cross-validation K-Fold con Ultralytics YOLO, è necessario seguire questi passaggi:

  1. Verificare che le annotazioni siano nel formato di rilevamento YOLO.
  2. Utilizza librerie Python come sklearn, pandas, e pyyaml.
  3. Crea vettori di feature dal tuo dataset.
  4. Dividi il tuo dataset usando KFold da sklearn.model_selection.
  5. Addestra il modello YOLO su ogni split.

Per una guida completa, consulta la sezione Suddivisione del set di dati K-Fold nella nostra documentazione.

Perché dovrei usare Ultralytics YOLO per il rilevamento di oggetti?

Ultralytics YOLO offre un rilevamento degli oggetti in tempo reale all'avanguardia, con elevata precisione ed efficienza. È versatile e supporta diverse attività di computer vision come il rilevamento, la segmentazione e la classificazione. Inoltre, si integra perfettamente con strumenti come Ultralytics HUB per l'addestramento e la distribuzione di modelli senza codice. Per maggiori dettagli, esplora i vantaggi e le funzionalità sulla nostra pagina Ultralytics YOLO.

Come posso assicurarmi che le mie annotazioni siano nel formato corretto per Ultralytics YOLO?

Le tue annotazioni devono seguire il formato di rilevamento YOLO. Ogni file di annotazione deve elencare la classe dell'oggetto, insieme alle coordinate del suo bounding box nell'immagine. Il formato YOLO garantisce un'elaborazione dei dati semplificata e standardizzata per il training dei modelli di rilevamento degli oggetti. Per ulteriori informazioni sulla corretta formattazione delle annotazioni, visita la guida al formato di rilevamento YOLO.

Posso utilizzare la convalida incrociata K-Fold con set di dati personalizzati diversi dal rilevamento di frutta?

Sì, puoi usare la convalida incrociata K-Fold con qualsiasi dataset personalizzato purché le annotazioni siano nel formato di rilevamento YOLO. Sostituisci i percorsi del dataset e le etichette di classe con quelli specifici del tuo dataset personalizzato. Questa flessibilità assicura che qualsiasi progetto di rilevamento oggetti possa beneficiare di una valutazione robusta del modello usando la convalida incrociata K-Fold. Per un esempio pratico, rivedi la nostra sezione Generazione di Vettori di Caratteristiche.



📅 Creato 1 anno fa ✏️ Aggiornato 2 mesi fa

Commenti