Ultralyticsを使用したK-Fold交差検証#
はじめに#
この包括的なガイドでは、Ultralyticsエコシステム内での物体検出データセットに対するK分割交差検証(K-Fold Cross Validation)の実装について解説します。YOLO検出フォーマットや、sklearn、pandas、PyYAMLなどの主要なPythonライブラリを活用して、必要なセットアップ、特徴量ベクトルの生成プロセス、およびK分割データセットの分割の実行手順を順に説明します。
プロジェクトでFruit Detectionデータセットを使用する場合でも、カスタムデータソースを使用する場合でも、このチュートリアルは機械学習モデルの信頼性と頑健性を高めるためのK分割交差検証の理解と適用を支援することを目的としています。このチュートリアルではk=5分割を使用していますが、最適な分割数はデータセットやプロジェクトの仕様によって異なる場合があることに留意してください。
それでは始めましょう。
セットアップ#
-
アノテーションはYOLO検出フォーマットである必要があります。
-
このガイドでは、アノテーションファイルがローカルで利用可能であることを前提としています。
-
このデモンストレーションでは、Fruit Detectionデータセットを使用します。
- このデータセットには合計8479枚の画像が含まれています。
- 6つのクラスラベルが含まれており、各インスタンス数は以下の通りです。
| クラスラベル | インスタンス数 |
|---|---|
| Apple | 7049 |
| Grapes | 7202 |
| Pineapple | 1613 |
| Orange | 15549 |
| Banana | 3536 |
| Watermelon | 1976 |
-
必要なPythonパッケージは以下の通りです:
ultralyticssklearnpandaspyyaml
-
このチュートリアルでは
k=5分割を使用します。ただし、特定のデータセットに最適な分割数を決定する必要があります。
-
プロジェクト用に新しいPython仮想環境(
venv)を開始し、有効化します。pip(または好みのパッケージマネージャー)を使用して以下をインストールします:- Ultralyticsライブラリ:
pip install -U ultralytics。または、公式リポジトリをクローンすることもできます。 - Scikit-learn、pandas、およびPyYAML:
pip install -U scikit-learn pandas pyyaml。
- Ultralyticsライブラリ:
-
アノテーションがYOLO検出フォーマットであることを確認してください。
- このチュートリアルでは、すべてのアノテーションファイルが
Fruit-Detection/labelsディレクトリにあります。
- このチュートリアルでは、すべてのアノテーションファイルが
物体検出データセット用の特徴ベクトル生成#
-
以下の手順を実行するために、新しい
example.pyPythonファイルを作成することから始めます。 -
データセットのすべてのラベルファイルを取得します。
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' -
次に、データセットの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()) -
空の
pandasDataFrameを初期化します。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) -
アノテーションファイルに存在する各クラスラベルのインスタンス数をカウントします。
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` -
以下は、データを格納した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分割交差検証の適用が可能になります。
K-Foldデータセット分割#
-
次に、
sklearn.model_selectionからKFoldクラスを使用して、データセットのk分割を生成します。- 重要:
shuffle=Trueを設定することで、分割におけるクラスのランダムな分布が保証されます。Mが選択した整数である場合にrandom_state=Mを設定することで、再現可能な結果を得ることができます。
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)) - 重要:
-
データセットは現在
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" -
次に、
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理想的には、すべてのクラス比率が各分割間およびクラス間で妥当な程度に類似していることが望ましいですが、これはデータセットの特性に依存します。
-
続いて、各分割用のディレクトリとデータセットYAMLファイルを作成します。
from datetime import datetime, timezone 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 date = datetime.now(timezone.utc).date().isoformat() save_path = Path(dataset_path / f"{date}_{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, ) -
最後に、各分割の画像とラベルを対応するディレクトリ('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の学習#
-
まず、YOLOモデルを読み込みます。
from ultralytics import YOLO weights_path = "path/to/weights.pt" # use yolo26n.pt for a small model model = YOLO(weights_path, task="detect") -
次に、データセットの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 -
自動データセット分割には、Ultralytics data.split.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分割交差検証のテクニックは、利用可能なデータを最大限に活用するための堅牢な方法であり、さまざまなデータサブセット間でモデルのパフォーマンスが信頼性高く一貫していることを保証するのに役立ちます。これにより、特定のデータパターンに過学習しにくい、より汎用性が高く信頼性の高いモデルが実現します。
このガイドではYOLOを使用しましたが、これらの手順は他の機械学習モデルにもほとんど適用可能です。これらの手順を理解することで、自身の機械学習プロジェクトで効果的に交差検証を適用できるようになります。
よくある質問 (FAQ)#
K分割交差検証は、モデルのパフォーマンスをより信頼性高く評価するために、データセットを「k」個のサブセット(分割)に分割するテクニックです。各分割は、トレーニングデータと検証データの両方として機能します。物体検出の文脈において、K分割交差検証を使用することは、異なるデータ分割にわたってUltralytics YOLOモデルのパフォーマンスが頑健で汎用性があることを保証し、その信頼性を高めるのに役立ちます。Ultralytics YOLOでのK分割交差検証のセットアップに関する詳細な手順については、K分割交差検証とUltralyticsを参照してください。
Ultralytics YOLOでK-Fold交差検証を実装するには、以下の手順に従う必要があります:
- アノテーションがYOLO検出フォーマットであることを確認してください。
sklearn、pandas、pyyamlなどのPythonライブラリを使用します。- データセットから特徴ベクトルを作成する。
sklearn.model_selectionのKFoldを使用してデータセットを分割します。- 各分割でYOLOモデルを学習する。
詳細なガイドについては、ドキュメントのK分割データセット分割セクションを参照してください。
Ultralytics YOLO は、高い accuracy と効率性を備えた最先端のリアルタイム物体検出を提供します。汎用性が高く、detection、instance segmentation、semantic segmentation、classification などの複数の computer vision タスクをサポートしています。さらに、コード不要のモデルトレーニングとデプロイメントのために Ultralytics Platform などのツールとシームレスに統合されます。詳細については、Ultralytics YOLO page で利点と機能をご確認ください。
アノテーションはYOLO検出フォーマットに従う必要があります。各アノテーションファイルには、画像内のバウンディングボックス座標とともに、オブジェクトクラスをリストする必要があります。YOLOフォーマットにより、物体検出モデルのトレーニングに向けた効率的で標準化されたデータ処理が保証されます。適切なアノテーションフォーマットの詳細については、YOLO検出フォーマットガイドをご覧ください。
はい、アノテーションがYOLO検出フォーマットである限り、任意のカスタムデータセットでK分割交差検証を使用できます。データセットのパスとクラスラベルを、カスタムデータセット固有のものに置き換えてください。この柔軟性により、あらゆる物体検出プロジェクトがK分割交差検証による堅牢なモデル評価の恩恵を受けることができます。実践的な例については、特徴量ベクトルの生成セクションを確認してください。