コンテンツへスキップ

Kフォールド・クロス・バリデーションUltralytics

はじめに

この包括的なガイドでは、Ultralytics エコシステム内のオブジェクト検出データセットに対する K-Fold Cross Validation の実装について説明します。YOLO 検出フォーマットと、sklearn、pandas、PyYaml などの主要なPython ライブラリを活用して、必要なセットアップ、特徴ベクトルの生成プロセス、K-Fold データセット分割の実行をガイドします。

Kフォールド交差検証の概要

このチュートリアルでは、Fruit Detection データセットまたはカスタム・データ・ソースのいずれを使用するプロジェクトであっても、K-Fold Cross Validation を理解し適用することで、機械学習モデルの信頼性と頑健性を高めることを目的としています。このチュートリアルでは k=5 このチュートリアルでは、最適な回数はデータセットやプロジェクトの仕様によって異なることを念頭に置いています。

では、さっそく見ていこう!

セットアップ

  • 注釈は、YOLO 検出フォーマットでなければなりません。

  • このガイドでは、注釈ファイルがローカルにあることを前提としています。

  • このデモでは、Fruit Detectionデータセットを使用する。

    • このデータセットには合計8479枚の画像が含まれている。
    • これは6つのクラス・ラベルを含み、それぞれのインスタンス総数は以下の通りである。
クラスラベル インスタンス数
アップル 7049
ブドウ 7202
パイナップル 1613
オレンジ 15549
バナナ 3536
スイカ 1976
  • 必要なPython パッケージは以下の通り:

    • ultralytics
    • sklearn
    • pandas
    • pyyaml
  • このチュートリアルでは k=5 回である。しかし、特定のデータセットに最適なフォールド数を決定する必要がある。

  • 新しいPython 仮想環境 (venv)をプロジェクトに追加し、アクティベートする。使用方法 pip (またはお好みのパッケージ・マネージャー)をインストールしてください:

    • Ultralytics ライブラリー: pip install -U ultralytics.または、公式の レポ.
    • Scikit-learn、pandas、PyYAML: pip install -U scikit-learn pandas pyyaml.
  • 注釈がYOLO 検出フォーマットであることを確認してください。

    • このチュートリアルでは、すべての注釈ファイルは Fruit-Detection/labels ディレクトリにある。

物体検出データセットの特徴ベクトルの生成

  1. まずPython ファイルを新規作成し、必要なライブラリをインポートする。

    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. データセットのすべてのラベルファイルを取得する。

    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. さて、データセットのYAMLファイルの中身を読んで、クラスラベルのインデックスを抽出しましょう。

    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. 空の pandas データフレーム。

    indx = [l.stem for l in labels] # uses base filename as ID (no extension)
    labels_df = pd.DataFrame([], columns=cls_idx, index=indx)
    
  5. 注釈ファイルに存在する各クラスラベルのインスタンスを数える。

    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. 以下は、入力された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
    

行はラベルファイルのインデックスを表し、それぞれがデータセット中の画像に対応し、列はクラスラベルのインデックスに対応する。各行は擬似的な特徴ベクトルで、データセットに存在する各クラスラベルのカウントを表す。このデータ構造により、物体検出データセットにK-Foldクロスバリデーションを適用することができる。

Kフォールド・データセット分割

  1. では KFold クラス sklearn.model_selection を生成する。 k データセットの分割。

    • 重要だ:
      • セッティング shuffle=True は、分割におけるクラスのランダムな分布を保証します。
      • セッティング random_state=M どこ M を整数にすれば、再現性のある結果を得ることができる。
    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. データセットは現在、以下のように分割されている。 k のリストを持つ。 train そして val インデックスを作成します。これらの結果をより明確に表示するために、DataFrameを作成する。

    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. では、各フォールドのクラス・ラベルの分布を、次のように計算する。 val に出席した。 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
    

理想的なシナリオは、すべてのクラスの比率が、各スプリットで、またクラス間で、適度に似ていることです。しかし、これはデータセットの仕様に依存します。

  1. 次に、各スプリットのディレクトリとデータセットのYAMLファイルを作成します。

    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. 最後に、画像とラベルを各スプリットのディレクトリ('train'または'val')にコピーする。

    • 注:コードのこの部分に要する時間は、データセットのサイズとシステムのハードウェアによって異なります。
    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)
    

レコードの保存(オプション)

オプションで、K-Fold分割とラベル配布のDataFrameのレコードをCSVファイルとして保存し、後で参照することができます。

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

K-Fold データ分割を使用したトレーニングYOLO

  1. まず、YOLO のモデルをロードする。

    weights_path = 'path/to/weights.pt'
    model = YOLO(weights_path, task='detect')
    
  2. 次に、データセットのYAMLファイルを反復処理してトレーニングを実行します。結果は project そして name 引数で指定する。デフォルトでは、このディレクトリは'exp/runs#'であり、#は整数インデックスである。

    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
    

結論

このガイドでは、YOLO オブジェクト検出モデルのトレーニングに K-Fold 交差検証を使用するプロセスを探った。データセットをK個のパーティションに分割し、異なるフォールド間でバランスのとれたクラス分布を確保する方法を学びました。

また、レポートDataFramesを作成し、データの分割と分割されたラベルの分布を視覚化する手順も検討した。

これは、大規模なプロジェクトや、モデルの性能をトラブルシューティングするときに特に役立つ。

最後に、各スプリットを使用した実際のモデル学習をループで実行し、さらなる分析と比較のために学習結果を保存した。

このK-Foldクロスバリデーションのテクニックは、利用可能なデータを最大限に活用するロバストな方法であり、モデルのパフォーマンスが信頼でき、異なるデータ・サブセット間で一貫していることを保証するのに役立ちます。その結果、特定のデータ・パターンに過剰適合する可能性が低く、より一般化可能で信頼性の高いモデルが出来上がります。

このガイドではYOLO を使用したが、これらのステップは他の機械学習モデルにもほとんど適用できることを忘れないでほしい。これらのステップを理解することで、あなた自身の機械学習プロジェクトでクロスバリデーションを効果的に適用することができます。ハッピー・コーディング!



作成日:2023-11-12 更新日:2023-12-03
作成者:glenn-jocher(5),Burhan-Q(1)

コメント