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 Instanzzahl
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