Ultralyticsを使用したK-Fold交差検証

はじめに

この包括的なガイドでは、Ultralyticsエコシステム内での物体検出データセットに対するK-Fold交差検証の実装方法を説明します。YOLO検出フォーマットと、sklearn、pandas、PyYAMLといった主要なPythonライブラリを活用し、必要なセットアップ、特徴ベクトルの生成プロセス、そしてK-Foldデータセット分割の実行手順をガイドします。

K-fold cross validation data splitting

プロジェクトがFruit Detectionデータセットであれ、カスタムデータソースであれ、このチュートリアルはK-Fold交差検証を理解し適用することで、機械学習モデルの信頼性と堅牢性を高めることを目的としています。このチュートリアルではk=5分割を使用しますが、最適な分割数はデータセットやプロジェクトの具体的な内容によって異なることに留意してください。

それでは始めましょう。

セットアップ

  • アノテーションはYOLO検出フォーマットである必要があります。

  • このガイドは、アノテーションファイルがローカルで利用可能であることを前提としています。

  • 実演にはFruit Detectionデータセットを使用します。

    • このデータセットには合計8479枚の画像が含まれています。
    • 6つのクラスラベルが含まれており、各クラスの合計インスタンス数は以下の通りです。
クラスラベルインスタンス数
Apple7049
Grapes7202
Pineapple1613
Orange15549
Banana3536
Watermelon1976
  • 必要なPythonパッケージは以下の通りです。

    • ultralytics
    • sklearn
    • pandas
    • pyyaml
  • このチュートリアルではk=5分割で運用します。ただし、特定のデータセットにとって最適な分割数を決定してください。

  1. プロジェクト用に新しいPython仮想環境(venv)を作成し、有効化してください。pip(または使用するパッケージマネージャー)を使用して以下をインストールします。

    • Ultralyticsライブラリ: pip install -U ultralytics。または、公式リポジトリをクローンすることもできます。
    • Scikit-learn、pandas、PyYAML: pip install -U scikit-learn pandas pyyaml
  2. アノテーションがYOLO検出フォーマットであることを確認してください。

    • このチュートリアルでは、すべてのアノテーションファイルがFruit-Detection/labelsディレクトリにあるものとします。

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

  1. まず、以下の手順のために新しいexample.pyPythonファイルを作成します。

  2. データセットのすべてのラベルファイルを取得します。

    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. 次に、データセットYAMLファイルの内容を読み取り、クラスラベルのインデックスを抽出します。

    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. 空の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. アノテーションファイル内に存在する各クラスラベルのインスタンス数をカウントします。

    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. 以下は、作成された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-Foldデータセット分割

  1. Now we will use the KFold class from sklearn.model_selection to generate k splits of the dataset.

    • 重要:
      • shuffle=Trueを設定することで、分割内にクラスがランダムに分布するようになります。
      • random_state=MMは任意の整数)を設定することで、再現可能な結果を得ることができます。
    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. これでデータセットがk個のフォールドに分割され、それぞれがtrainインデックスとvalインデックスのリストを持つようになりました。結果をより明確に表示するためにDataFrameを構築します。

    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. 次に、各フォールドについて、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

    理想的なシナリオは、すべてのクラス比率が各分割において、そしてクラス間で妥当に類似していることです。ただし、これはデータセットの特性に依存します。

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

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

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

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

オプションとして、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モデルを読み込みます。

    from ultralytics import YOLO
    
    weights_path = "path/to/weights.pt"  # use yolo26n.pt for a small model
    model = YOLO(weights_path, task="detect")
  2. 次に、データセットYAMLファイルを順次読み込んで学習を実行します。結果はprojectおよびname引数で指定されたディレクトリに保存されます。デフォルトでは、このディレクトリは'runs/detect/train#'(#は整数インデックス)になります。

    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. 自動データセット分割にはUltralytics data.utils.autosplit関数も使用できます。

    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)

結論

このガイドでは、YOLO物体検出モデルを学習させるためのK-Fold交差検証の使用プロセスについて解説しました。データセットをK個のパーティションに分割し、異なるフォールド間でクラス分布がバランス良くなるようにする方法を学びました。

また、データ分割や各分割におけるラベル分布を可視化するためのレポートDataFrameを作成する手順も確認し、学習セットと検証セットの構造を明確に把握することができました。

オプションとして、将来の参照用に記録を保存しました。これは大規模プロジェクトやモデルのパフォーマンスをトラブルシューティングする際に特に役立ちます。

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

このK-Fold交差検証の手法は、利用可能なデータを最大限に活用するための堅牢な方法であり、モデルのパフォーマンスが異なるデータサブセット全体で信頼性が高く一貫していることを確認するのに役立ちます。これにより、特定のデータパターンに過学習しにくく、より汎用性が高く信頼性の高いモデルが実現します。

本ガイドではYOLOを使用しましたが、これらの手順は他の機械学習モデルにもほぼ適用可能です。これらの手順を理解することで、自身の機械学習プロジェクトにおいて交差検証を効果的に適用できるようになります。

FAQ

K-Fold交差検証とは何ですか?また、なぜ物体検出において役立つのですか?

K-Fold交差検証は、モデルのパフォーマンスをより確実に評価するために、データセットを'k'個のサブセット(フォールド)に分割する手法です。各フォールドは、学習データと検証データの両方として機能します。物体検出の文脈において、K-Fold交差検証を使用することは、Ultralytics YOLOモデルのパフォーマンスが異なるデータ分割全体で堅牢かつ汎用的であることを確認し、信頼性を高めるのに役立ちます。Ultralytics YOLOでK-Fold交差検証を設定する詳細な手順については、Ultralyticsを使用したK-Fold交差検証を参照してください。

Ultralytics YOLOを使用してK-Fold交差検証を実装するにはどうすればよいですか?

Ultralytics YOLOでK-Fold交差検証を実装するには、以下の手順に従う必要があります。

  1. アノテーションがYOLO検出フォーマットであることを確認する。
  2. sklearnpandaspyyamlのようなPythonライブラリを使用する。
  3. データセットから特徴ベクトルを作成する。
  4. Split your dataset using KFold from sklearn.model_selection.
  5. 各分割に対してYOLOモデルを学習させる。

包括的なガイドについては、ドキュメントのK-Foldデータセット分割セクションを参照してください。

なぜ物体検出にUltralytics YOLOを使用すべきなのですか?

Ultralytics YOLOは、高い精度と効率性を備えた最先端のリアルタイム物体検出を提供します。検出、セグメンテーション、分類など、複数のコンピュータビジョンタスクをサポートする多用途なツールです。さらに、コード不要のモデル学習とデプロイのためのUltralytics Platformといったツールともシームレスに統合されます。詳細については、Ultralytics YOLOページで利点や機能をご確認ください。

Ultralytics YOLOのために、アノテーションが正しいフォーマットであることを確認するにはどうすればよいですか?

アノテーションはYOLO検出フォーマットに従う必要があります。各アノテーションファイルには、画像内のバウンディングボックス座標とともに、オブジェクトのクラスを記載する必要があります。YOLOフォーマットは、物体検出モデルの学習に向けた効率的で標準化されたデータ処理を保証します。適切なアノテーションフォーマットの詳細については、YOLO検出フォーマットガイドをご覧ください。

Fruit Detection以外のカスタムデータセットでK-Fold交差検証を使用できますか?

はい、アノテーションがYOLO検出フォーマットであれば、任意のカスタムデータセットでK-Fold交差検証を使用できます。データセットのパスとクラスラベルをカスタムデータセット固有のものに置き換えてください。この柔軟性により、どのような物体検出プロジェクトでも、K-Fold交差検証を使用して堅牢なモデル評価を行うメリットを享受できます。実践的な例については、特徴ベクトルの生成セクションを確認してください。

コメント