Skip to main content

K-Fold Cross Validation mit Ultralytics

Einführung

Dieser umfassende Leitfaden zeigt die Implementierung von K-Fold Cross Validation 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 die notwendige Einrichtung, den Prozess der Erzeugung von Merkmalsvektoren und die Ausführung eines K-Fold Datensatz-Splits zu führen.

K-fold cross validation data splitting

Egal ob dein Projekt den Fruit Detection Datensatz oder eine eigene Datenquelle verwendet, dieses Tutorial hilft dir dabei, K-Fold Cross Validation zu verstehen und anzuwenden, um die Zuverlässigkeit und Robustheit deiner Machine Learning Modelle zu stärken. Während wir für dieses Tutorial k=5 Folds verwenden, beachte bitte, dass die optimale Anzahl der Folds je nach Datensatz und Projektspezifikationen variieren kann.

Fangen wir an.

Setup

  • Deine Annotationen sollten im YOLO detection format.

  • vorliegen. Dieser Leitfaden geht davon aus, dass Annotationsdateien lokal verfügbar sind.

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

    • Datensatz. Dieser Datensatz enthält insgesamt 8479 Bilder.
    • Er umfasst 6 Klassen-Labels, deren gesamte Instanzanzahl unten aufgeführt ist.
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 ermitteln.

  1. Erstelle eine neue virtuelle Python-Umgebung (venv) für dein Projekt und aktiviere sie. Verwende 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. Stelle sicher, dass deine Annotationen im YOLO detection format.

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

Erzeugung von Merkmalsvektoren für Objekterkennungs-Datensätze

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

  2. Rufe nun alle Label-Dateien für deinen Datensatz ab.

    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 jetzt 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 in den Annotationsdateien.

    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 befüllten DataFrames:

                                                           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 dar, mit der Anzahl der in deinem Datensatz vorhandenen Klassen-Labels. Diese Datenstruktur ermöglicht die Anwendung von K-Fold Kreuzvalidierung auf einen Objekterkennungs-Datensatz.

K-Fold Datensatz-Split

  1. Nun verwenden wir die KFold Klasse aus sklearn.model_selection, um k Datensatz-Splits zu generieren.

    • Wichtig:
      • Das Setzen von shuffle=True stellt eine randomisierte Verteilung der Klassen in deinen Splits sicher.
      • Durch das Setzen von random_state=M aus, wobei M ist ein gewählter Integer, mit dem du reproduzierbare Ergebnisse erzielen kannst.
    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, von denen jeder eine Liste von train und val Indizes enthält. Wir werden einen DataFrame erstellen, um diese Ergebnisse klarer 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 Klassen, die in val vorhanden sind, zu denen 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

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

    • HINWEIS: Die benötigte Zeit für diesen Teil des Codes 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)

Du kannst optional die Datensätze des K-Fold-Splits und die DataFrames der Label-Verteilung zur späteren Referenz als CSV-Dateien speichern.

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

YOLO trainieren 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 project und name Argumente angegeben wird. Standardmäßig ist dieses Verzeichnis 'runs/detect/train#', wobei # ein Integer-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 automatisches Datensatz-Splitting 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 von K-Fold Cross Validation zum Trainieren des YOLO Objekterkennungsmodells untersucht. Wir haben gelernt, wie man unseren Datensatz in K Partitionen aufteilt und dabei eine ausgewogene Klassenverteilung über die verschiedenen Folds sicherstellt.

Wir haben außerdem das Verfahren zum Erstellen von Berichts-DataFrames erforscht, um die Datensplits und Label-Verteilungen über diese Splits zu visualisieren, was uns einen klaren Einblick in die Struktur unserer Trainings- und Validierungssets gibt.

Optional haben wir unsere Aufzeichnungen zur späteren Verwendung gespeichert, was besonders bei Großprojekten oder bei der Fehlerbehebung der Modellleistung nützlich sein kann.

Schließlich haben wir das tatsächliche Modelltraining in einer Schleife mit jedem Split implementiert und unsere Trainingsergebnisse für weitere Analysen und Vergleiche gespeichert.

Diese Technik der K-Fold Cross Validation ist eine robuste Methode, um das Beste aus deinen verfügbaren Daten herauszuholen, und sie hilft sicherzustellen, 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 dazu neigt, overfit auf spezifische Datenmuster zu zeigen.

Denk daran, dass wir in diesem Leitfaden zwar YOLO verwendet haben, diese Schritte aber weitgehend auf andere Machine-Learning-Modelle übertragbar sind. Wenn du diese Schritte verstehst, kannst du Cross-Validation effektiv in deinen eigenen Machine-Learning-Projekten anwenden.

FAQ

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

K-Fold Cross Validation 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 Training als auch als Validierungsdaten. Im Kontext der Objekterkennung hilft die Verwendung von K-Fold Cross Validation sicherzustellen, dass die Leistung deines Ultralytics YOLO Modells robust und über verschiedene Datensplits hinweg generalisierbar ist, was seine Zuverlässigkeit erhöht. Detaillierte Anweisungen zum Einrichten von K-Fold Cross Validation mit Ultralytics YOLO findest du in K-Fold Cross Validation mit Ultralytics.

Wie implementiere ich K-Fold Cross Validation mit Ultralytics YOLO?

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

  1. Stelle sicher, dass die Annotationen im YOLO detection format.
  2. vorliegen. Verwende Python-Bibliotheken wie sklearn, pandas, und pyyaml.
  3. Erstelle Merkmalsvektoren aus deinem Datensatz.
  4. Teile deinen Datensatz mit KFold von sklearn.model_selection.
  5. Trainiere das YOLO Modell auf jedem Split.

Einen umfassenden Leitfaden findest du im K-Fold Datensatz-Split Abschnitt in unserer Dokumentation.

Warum solltest du Ultralytics YOLO für die Objekterkennung verwenden?

Ultralytics YOLO bietet hochmoderne Echtzeit-Objekterkennung mit hoher accuracy 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 an, oder verwende die für No-Code-Modelltraining und Deployment integrieren. Weitere Details zu den Vorteilen und Funktionen findest du auf unserer Ultralytics YOLO Seite.

Wie stelle ich sicher, dass meine Annotationen das richtige Format für Ultralytics YOLO haben?

Deine Annotationen sollten dem YOLO detection format 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 beim Training von Objekterkennungsmodellen. Weitere Informationen zur korrekten Annotationsformatierung findest du im YOLO detection format guide.

Kann ich K-Fold Cross Validation auch mit anderen benutzerdefinierten Datensätzen als Fruit Detection verwenden?

Ja, du kannst K-Fold Cross Validation mit jedem benutzerdefinierten Datensatz verwenden, solange die Annotationen im YOLO detection format vorliegen. Ersetze die Dataset-Pfade und Klassenbezeichnungen durch die deines benutzerdefinierten Datensatzes. Diese Flexibilität stellt sicher, dass jedes Objekterkennungsprojekt von einer robusten Modellevaluierung mittels K-Fold Cross Validation profitieren kann. Für ein praktisches Beispiel, siehe unsere Generierung von Feature-Vektoren.

Kommentare