K-Fold-Kreuzvalidierung mit Ultralytics

Einführung

Dieser umfassende Leitfaden illustriert die Implementierung der K-Fold-Kreuzvalidierung für Objekterkennungs-Datensätze innerhalb des Ultralytics-Ökosystems. Wir nutzen das YOLO-Erkennungsformat und wichtige Python-Bibliotheken wie sklearn, pandas und PyYAML, um dich durch das notwendige Setup, den Prozess der Erstellung von Merkmalsvektoren und die Ausführung eines K-Fold-Datensatz-Splits zu führen.

K-fold cross validation data splitting

Ob dein Projekt den Fruit-Detection-Datensatz oder eine benutzerdefinierte Datenquelle umfasst, dieses Tutorial soll dir helfen, die K-Fold-Kreuzvalidierung zu verstehen und anzuwenden, um die Zuverlässigkeit und Robustheit deiner Machine Learning-Modelle zu stärken. Obwohl wir in diesem Tutorial k=5 Folds anwenden, denke daran, dass die optimale Anzahl an Folds je nach Datensatz und den Spezifikationen deines Projekts variieren kann.

Lass uns anfangen.

Setup

  • Deine Annotationen sollten im YOLO-Erkennungsformat vorliegen.

  • Dieser Leitfaden setzt voraus, dass die Annotationsdateien lokal verfügbar sind.

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

    • Dieser Datensatz enthält insgesamt 8479 Bilder.
    • Er beinhaltet 6 Klassen-Labels, deren gesamte Instanzzahlen unten aufgeführt sind.
Klassen-LabelInstanzanzahl
Apple7049
Grapes7202
Pineapple1613
Orange15549
Banana3536
Watermelon1976
  • Notwendige Python-Pakete sind:

    • ultralytics
    • sklearn
    • pandas
    • pyyaml
  • Dieses Tutorial arbeitet mit k=5 Folds. Du solltest jedoch die beste Anzahl an Folds für deinen spezifischen Datensatz bestimmen.

  1. Initialisiere eine neue virtuelle Python-Umgebung (venv) für dein Projekt und aktiviere sie. Nutze pip (oder deinen bevorzugten Paketmanager), um Folgendes zu installieren:

    • Die Ultralytics-Bibliothek: pip install -U ultralytics. Alternativ kannst du das offizielle Repo klonen.
    • Scikit-learn, pandas und PyYAML: pip install -U scikit-learn pandas pyyaml.
  2. Überprüfe, ob deine Annotationen im YOLO-Erkennungsformat vorliegen.

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

Generierung von Merkmalsvektoren für einen Objekterkennungs-Datensatz

  1. Beginne damit, eine neue example.py Python-Datei für die folgenden Schritte zu erstellen.

  2. Fahre fort, indem du alle Label-Dateien für deinen Datensatz abrufst.

    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. Lies nun den Inhalt der Datensatz-YAML-Datei und extrahiere die Indizes der Klassen-Labels.

    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. Initialisiere einen leeren 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. Zähle die Instanzen jedes Klassen-Labels, die in den Annotationsdateien vorhanden sind.

    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. Das Folgende ist eine Beispielansicht des gefü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 Label-Dateien, von denen jede einem Bild in deinem Datensatz entspricht, und die Spalten entsprechen deinen Klassen-Label-Indizes. Jede Zeile stellt einen Pseudo-Merkmalsvektor mit der Anzahl jedes Klassen-Labels dar, das in deinem Datensatz vorhanden ist. Diese Datenstruktur ermöglicht die Anwendung der K-Fold-Kreuzvalidierung auf einen Objekterkennungs-Datensatz.

K-Fold-Datensatz-Split

  1. Jetzt verwenden wir die KFold-Klasse aus sklearn.model_selection, um k Splits des Datensatzes zu generieren.

    • Wichtig:
      • Das Setzen von shuffle=True stellt eine zufällige Verteilung der Klassen in deinen Splits sicher.
      • Durch das Setzen von random_state=M, wobei M eine gewählte Ganzzahl ist, kannst du reproduzierbare Ergebnisse erhalten.
    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. Der Datensatz wurde nun in k Folds unterteilt, wobei jedes eine Liste von train- und val-Indizes hat. Wir erstellen einen DataFrame, um diese Ergebnisse übersichtlicher darzustellen.

    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. Jetzt berechnen wir die Verteilung der Klassen-Labels für jeden Fold als Verhältnis der in val vorhandenen Klassen zu den in train vorhandenen.

    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

    Das ideale Szenario ist, dass alle Klassenverhältnisse für jeden Split und über alle Klassen hinweg einigermaßen ähnlich sind. Dies hängt jedoch von den Besonderheiten deines Datensatzes ab.

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

    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. Zuletzt kopiere die Bilder und Labels in das jeweilige Verzeichnis ('train' oder 'val') für jeden Split.

    • HINWEIS: Die Zeit, die für diesen Teil des Codes benötigt wird, variiert je nach Größe deines Datensatzes und deiner System-Hardware.
    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)

Datensätze speichern (Optional)

Optional kannst du die Datensätze der K-Fold-Splits und die DataFrames der Label-Verteilung als CSV-Dateien für zukünftige Referenz speichern.

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

YOLO-Training mit K-Fold-Datensatz-Splits

  1. Lade zuerst das YOLO-Modell.

    from ultralytics import YOLO
    
    weights_path = "path/to/weights.pt"  # use yolo26n.pt for a small model
    model = YOLO(weights_path, task="detect")
  2. Iteriere als Nächstes über die Datensatz-YAML-Dateien, um das Training auszuführen. Die Ergebnisse werden in einem Verzeichnis gespeichert, das durch die Argumente project und name angegeben wird. Standardmäßig ist dieses Verzeichnis 'runs/detect/train#', wobei # ein ganzzahliger Index ist.

    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. Du kannst auch die Ultralytics data.utils.autosplit-Funktion für das automatische Splitten von Datensätzen verwenden:

    from ultralytics.data.split import autosplit
    
    # Automatically split dataset into train/val/test
    autosplit(path="path/to/images", weights=(0.8, 0.2, 0.0), annotated_only=True)

Fazit

In diesem Leitfaden haben wir den Prozess der Verwendung der K-Fold-Kreuzvalidierung für das Training des YOLO-Objekterkennungsmodells erkundet. Wir haben gelernt, wie wir unseren Datensatz in K-Partitionen aufteilen, um eine ausgeglichene Klassenverteilung über die verschiedenen Folds hinweg sicherzustellen.

Wir haben zudem die Vorgehensweise zur Erstellung von Report-DataFrames erforscht, um die Datensatz-Splits und Label-Verteilungen über diese Splits hinweg zu visualisieren, was uns einen klaren Einblick in die Struktur unserer Trainings- und Validierungssets gibt.

Optional haben wir unsere Datensätze für eine zukünftige Referenz gespeichert, was besonders bei groß angelegten Projekten oder bei der Fehlersuche in der Modellleistung nützlich sein könnte.

Schließlich haben wir das eigentliche Modelltraining unter Verwendung jedes Splits in einer Schleife implementiert 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 sie trägt dazu bei, dass deine Modellleistung über verschiedene Datensubsets hinweg zuverlässig und konsistent ist. Dies führt zu einem besser generalisierbaren und zuverlässigeren Modell, das weniger anfällig für Overfitting auf spezifische Datenmuster ist.

Denk daran, dass diese Schritte, obwohl wir in diesem Leitfaden YOLO verwendet haben, größtenteils auf andere Machine-Learning-Modelle übertragbar sind. Das Verständnis dieser Schritte ermöglicht es dir, die Kreuzvalidierung effektiv in deinen eigenen Machine-Learning-Projekten anzuwenden.

FAQ

Was ist K-Fold-Kreuzvalidierung und warum ist sie bei der Objekterkennung nützlich?

K-Fold-Kreuzvalidierung ist eine Technik, bei der der Datensatz in 'k' Teilmengen (Folds) unterteilt wird, um die Modellleistung zuverlässiger zu bewerten. Jeder Fold dient sowohl als Trainings- als auch als Validierungsdatensatz. Im Kontext der Objekterkennung hilft die Verwendung der K-Fold-Kreuzvalidierung sicherzustellen, dass die Leistung deines Ultralytics YOLO-Modells über verschiedene Datensplits hinweg robust und generalisierbar ist, was dessen Zuverlässigkeit erhöht. Für detaillierte Anweisungen zur Einrichtung der K-Fold-Kreuzvalidierung mit Ultralytics YOLO, siehe K-Fold-Kreuzvalidierung mit Ultralytics.

Wie implementiere ich die K-Fold-Kreuzvalidierung mit Ultralytics YOLO?

Um die K-Fold-Kreuzvalidierung mit Ultralytics YOLO zu implementieren, musst du diese Schritte befolgen:

  1. Überprüfe, ob die Annotationen im YOLO-Erkennungsformat vorliegen.
  2. Verwende Python-Bibliotheken wie sklearn, pandas und pyyaml.
  3. Erstelle Merkmalsvektoren aus deinem Datensatz.
  4. Teile deinen Datensatz mit KFold aus sklearn.model_selection.
  5. Trainiere das YOLO-Modell auf jedem Split.

Für eine umfassende Anleitung siehe den Abschnitt K-Fold-Datensatz-Split in unserer Dokumentation.

Warum sollte ich Ultralytics YOLO für die Objekterkennung verwenden?

Ultralytics YOLO bietet hochmoderne Echtzeit-Objekterkennung mit hoher Genauigkeit und Effizienz. Es ist vielseitig und unterstützt mehrere Computer Vision-Aufgaben wie Erkennung, Segmentierung und Klassifizierung. Zudem lässt es sich nahtlos in Tools wie die Ultralytics Plattform für No-Code-Modelltraining und Deployment integrieren. Für weitere Details entdecke die Vorteile und Funktionen auf unserer Ultralytics YOLO-Seite.

Wie kann ich sicherstellen, dass meine Annotationen im richtigen Format für Ultralytics YOLO sind?

Deine Annotationen sollten dem YOLO-Erkennungsformat folgen. Jede Annotationsdatei muss die Objektklasse zusammen mit ihren Bounding-Box-Koordinaten im Bild auflisten. Das YOLO-Format sorgt für eine optimierte und standardisierte Datenverarbeitung für das Training von Objekterkennungsmodellen. Für weitere Informationen zur korrekten Annotationsformatierung besuche den Leitfaden zum YOLO-Erkennungsformat.

Kann ich die K-Fold-Kreuzvalidierung mit anderen benutzerdefinierten Datensätzen außer Fruit Detection verwenden?

Ja, du kannst die K-Fold-Kreuzvalidierung mit jedem benutzerdefinierten Datensatz verwenden, solange die Annotationen im YOLO-Erkennungsformat vorliegen. Ersetze die Datensatzpfade und Klassen-Labels durch die für deinen spezifischen Datensatz geltenden. Diese Flexibilität stellt sicher, dass jedes Objekterkennungsprojekt von einer robusten Modellbewertung mittels K-Fold-Kreuzvalidierung profitieren kann. Für ein praktisches Beispiel, überprüfe unseren Abschnitt Merkmalsvektoren generieren.

Kommentare