Zum Inhalt springen

K-fache Kreuzvalidierung mit Ultralytics

Einführung

Dieser umfassende Leitfaden veranschaulicht die Implementierung der K-Fold Cross Validation für Objekterkennungsdatensätze innerhalb des Ultralytics Ökosystems. Wir nutzen das Erkennungsformat YOLO und die wichtigsten Python Bibliotheken wie sklearn, pandas und PyYaml, um dich durch die notwendigen Einstellungen, die Erzeugung von Merkmalsvektoren und die Durchführung einer K-Fold-Datensatzteilung zu führen.

K-Fold Cross Validation Überblick

Ganz gleich, ob dein Projekt den Fruit Detection-Datensatz oder eine benutzerdefinierte Datenquelle verwendet, dieses Tutorial soll dir helfen, die K-Fold Cross Validation zu verstehen und anzuwenden, um die Zuverlässigkeit und Robustheit deiner Machine Learning-Modelle zu verbessern. Während wir die k=5 Die optimale Anzahl von Foldings kann je nach Datensatz und den Besonderheiten deines Projekts variieren.

Ohne Umschweife, lasst uns eintauchen!

Einrichtung

  • Deine Anmerkungen sollten im ErkennungsformatYOLO sein.

  • Dieser Leitfaden geht davon aus, dass die Anmerkungsdateien lokal verfügbar sind.

  • Für unsere Demonstration verwenden wir den Datensatz " Fruit Detection".

    • Dieser Datensatz enthält insgesamt 8479 Bilder.
    • Sie enthält 6 Klassenlabels, deren Gesamtanzahl der Instanzen unten aufgeführt ist.
Klasse Etikett Anzahl der Instanzen
Apfel 7049
Weintrauben 7202
Ananas 1613
Orange 15549
Banane 3536
Wassermelone 1976
  • Zu den notwendigen Python Paketen gehören:

    • ultralytics
    • sklearn
    • pandas
    • pyyaml
  • Dieses Lernprogramm arbeitet mit k=5 Falten. Du solltest jedoch die beste Anzahl von Faltungen für deinen spezifischen Datensatz ermitteln.

  • Initiiere eine neue Python virtuelle Umgebung (venv) für dein Projekt und aktiviere es. Verwenden Sie pip (oder deinen bevorzugten Paketmanager) zu installieren:

    • Die Ultralytics Bibliothek: pip install -U ultralytics. Alternativ kannst du auch die offizielle Repo.
    • Scikit-learn, Pandas und PyYAML: pip install -U scikit-learn pandas pyyaml.
  • Vergewissere dich, dass deine Anmerkungen im ErkennungsformatYOLO vorliegen.

    • Für dieses Tutorial werden alle Annotationsdateien im Ordner Fruit-Detection/labels Verzeichnis.

Erzeugen von Merkmalsvektoren für den Objektdetektionsdatensatz

  1. Beginne mit der Erstellung einer neuen Python Datei und importiere die benötigten Bibliotheken.

    import datetime
    import shutil
    from pathlib import Path
    from collections import Counter
    
    import yaml
    import numpy as np
    import pandas as pd
    from ultralytics import YOLO
    from sklearn.model_selection import KFold
    
  2. Fahre fort, um alle Etikettendateien für deinen Datensatz abzurufen.

    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. Lies nun den Inhalt der YAML-Datei des Datensatzes und extrahiere die Indizes der Klassenbezeichnungen.

    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. Initialisiere eine leere 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. Zähle die Instanzen jedes Klassen-Labels in den Annotationsdateien.

    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. Es folgt eine Beispielansicht des ausgefüllten DataFrame:

                                                           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
    

Die Zeilen indizieren die Beschriftungsdateien, die jeweils einem Bild deines Datensatzes entsprechen, und die Spalten entsprechen den Indizes deiner Klassenlabels. Jede Zeile stellt einen Pseudo-Feature-Vektor dar, der die Anzahl der in deinem Datensatz vorhandenen Klassen-Labels enthält. Diese Datenstruktur ermöglicht die Anwendung der K-Fold Cross Validation auf einen Objekterkennungsdatensatz.

K-fache Datensatzaufteilung

  1. Jetzt werden wir die KFold Klasse von sklearn.model_selection zu erzeugen k Splits des Datensatzes.

    • Wichtig!
      • Einstellung shuffle=True sorgt für eine zufällige Verteilung der Klassen in deinen Splits.
      • Durch die Einstellung random_state=M wo M eine ausgewählte ganze Zahl ist, kannst du wiederholbare Ergebnisse erzielen.
    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. Der Datensatz wurde nun aufgeteilt in k Falten, jede mit einer Liste von train und val Indizes. Wir werden einen DataFrame erstellen, um diese Ergebnisse deutlicher darzustellen.

    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. Nun berechnen wir die Verteilung der Klassenlabels für jede Falte als Verhältnis der Klassen, die in val für die Anwesenden 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
    

Im Idealfall sind alle Klassenverhältnisse für jeden Split und über alle Klassen hinweg einigermaßen ähnlich. Dies hängt jedoch von den Besonderheiten deines Datensatzes ab.

  1. Als Nächstes erstellen wir die Verzeichnisse und Datensatz-YAML-Dateien für jeden Split.

    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. Zum Schluss kopierst du die Bilder und Beschriftungen in das jeweilige Verzeichnis ("train" oder "val") für jeden Split.

    • HINWEIS: Die Zeit, die für diesen Teil des Codes benötigt wird, hängt von der Größe deines Datensatzes und deiner Systemhardware ab.
    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)
    

Datensätze speichern (optional)

Optional kannst du die Datensätze der K-Fold Split- und Label Distribution DataFrames als CSV-Dateien für spätere Zwecke speichern.

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

Trainiere YOLO mit K-Fold Data Splits

  1. Lade zunächst das Modell YOLO .

    weights_path = 'path/to/weights.pt'
    model = YOLO(weights_path, task='detect')
    
  2. Als Nächstes iterierst du über die YAML-Dateien des Datensatzes, um das Training durchzuführen. Die Ergebnisse werden in einem Verzeichnis gespeichert, das mit der Option project und name Argumente. Standardmäßig ist dieses Verzeichnis "exp/runs#", wobei # ein ganzzahliger Index ist.

    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
    

Fazit

In diesem Leitfaden haben wir den Prozess der K-Fold-Kreuzvalidierung für das Training des Objekterkennungsmodells YOLO erkundet. Wir haben gelernt, wie wir unseren Datensatz in K Partitionen aufteilen, um eine ausgewogene Klassenverteilung über die verschiedenen Foldings zu gewährleisten.

Wir haben auch das Verfahren zur Erstellung von Report DataFrames erforscht, um die Datenaufteilung und die Verteilung der Labels über diese Aufteilung zu visualisieren, was uns einen klaren Einblick in die Struktur unserer Trainings- und Validierungssätze gibt.

Optional haben wir unsere Aufzeichnungen für die Zukunft gespeichert, was besonders bei großen Projekten oder bei der Fehlersuche in der Modellleistung nützlich sein kann.

Schließlich haben wir das eigentliche Modelltraining mit jedem Split in einer Schleife durchgeführt und unsere Trainingsergebnisse für weitere Analysen und Vergleiche gespeichert.

Diese Technik der K-Fold-Kreuzvalidierung ist eine robuste Methode, um das Beste aus deinen verfügbaren Daten herauszuholen und sicherzustellen, dass die Leistung deines Modells über verschiedene Datenuntergruppen hinweg zuverlässig und konsistent ist. Das Ergebnis ist ein verallgemeinerbares und zuverlässiges Modell, das sich weniger leicht an bestimmte Datenmuster anpassen lässt.

Denke daran, dass wir in diesem Leitfaden zwar YOLO verwendet haben, diese Schritte aber größtenteils auch auf andere maschinelle Lernmodelle übertragbar sind. Wenn du diese Schritte verstehst, kannst du die Kreuzvalidierung in deinen eigenen Projekten zum maschinellen Lernen effektiv anwenden. Viel Spaß beim Programmieren!



Erstellt am 2023-11-12, Aktualisiert am 2023-12-03
Autoren: glenn-jocher (5), Burhan-Q (1)

Kommentare