Meet YOLO26: next-gen vision AI.

Link to this section使用 Ultralytics 进行 K 折交叉验证#

Link to this section简介#

本综合指南阐述了如何在 Ultralytics 生态系统中为目标检测数据集实现 K 折交叉验证。我们将利用 YOLO 检测格式以及 sklearn、pandas 和 PyYAML 等关键 Python 库,引导你完成必要的设置、特征向量生成过程以及 K 折数据集拆分的执行。

K-fold cross validation data splitting

无论你的项目涉及水果检测数据集还是自定义数据源,本教程旨在帮助你理解并应用 K 折交叉验证,以增强机器学习模型的可靠性和鲁棒性。虽然本教程中我们应用了 k=5 折,但请记住,最佳折数可能因你的数据集和项目具体情况而异。

让我们开始吧。

Link to this section设置#

  • 你的标注应采用 YOLO 检测格式

  • 本指南假设标注文件在本地可用。

  • 为了演示,我们使用了 水果检测 数据集。

    • 该数据集总共包含 8479 张图像。
    • 它包含 6 个类别标签,每个标签的实例总数如下所列。
类标签实例计数
苹果7049
葡萄7202
菠萝1613
橙子15549
香蕉3536
西瓜1976
  • 必要的 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 目录中。

Link to this section为目标检测数据集生成特征向量#

  1. 首先创建一个新的 example.py Python 文件,用于执行以下步骤。

  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 折交叉验证

Link to this sectionK 折数据集拆分#

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

    • 重要:
      • 设置 shuffle=True 可确保拆分中类别的随机分布。
      • 通过设置 random_state=M(其中 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))
  2. 数据集现在已被拆分为 k 折,每一折都有一个 trainval 索引列表。我们将构建一个 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)

Link to this section保存记录(可选)#

你可以选择将 K 折拆分记录和标签分布 DataFrames 保存为 CSV 文件,以供将来参考。

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

Link to this section使用 K 折数据拆分训练 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 文件以运行训练。结果将保存到由 projectname 参数指定的目录中。默认情况下,此目录为 '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.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)

Link to this section结论#

在本指南中,我们探讨了使用 K 折交叉验证来训练 YOLO 目标检测模型的过程。我们学习了如何将数据集拆分为 K 个分区,从而确保在不同折之间实现平衡的类分布。

我们还探讨了创建报告 DataFrame 以可视化数据拆分和这些拆分中标签分布的过程,这让我们清楚地了解了训练集和验证集的结构。

我们还可以选择保存记录以供将来参考,这在大型项目中或排除模型性能故障时特别有用。

最后,我们在循环中使用每个拆分实现了实际的模型训练,并保存了训练结果以供进一步分析和比较。

这种 K 折交叉验证技术是充分利用现有数据的一种稳健方法,它有助于确保模型性能在不同数据子集之间是可靠且一致的。这使得模型更具通用性和可靠性,不太可能对特定的数据模式产生过拟合

请记住,虽然我们在本指南中使用了 YOLO,但这些步骤在很大程度上可迁移到其他机器学习模型中。了解这些步骤可以让你在自己的机器学习项目中有效地应用交叉验证。

Link to this section常见问题解答#

Link to this section什么是 K 折交叉验证?为什么它在目标检测中很有用?#

K 折交叉验证是一种将数据集划分为 'k' 个子集(折)以更可靠地评估模型性能的技术。每一折都既作为训练数据,又作为验证数据。在目标检测背景下,使用 K 折交叉验证有助于确保你的 Ultralytics YOLO 模型在不同数据拆分中的性能稳健且具有通用性,从而增强其可靠性。有关使用 Ultralytics YOLO 设置 K 折交叉验证的详细说明,请参考 使用 Ultralytics 进行 K 折交叉验证

Link to this section如何使用 Ultralytics YOLO 实现 K 折交叉验证?#

要使用 Ultralytics YOLO 实现 K 折交叉验证,你需要按照以下步骤操作:

  1. 验证标注是否采用 YOLO 检测格式
  2. 使用诸如 sklearnpandaspyyaml 等 Python 库。
  3. 从你的数据集中创建特征向量。
  4. Split your dataset using KFold from sklearn.model_selection.
  5. 在每个拆分上训练 YOLO 模型。

如需综合指南,请参阅我们文档中的 K 折数据集拆分 部分。

Link to this section为什么我应该使用 Ultralytics YOLO 进行目标检测?#

Ultralytics YOLO 提供具有高准确率和效率的最先进的实时目标检测。它功能多样,支持多种计算机视觉任务,例如检测实例分割语义分割分类。此外,它还与 Ultralytics Platform 等工具无缝集成,用于无代码模型训练和部署。有关更多详细信息,请浏览我们 Ultralytics YOLO 页面上的优势和功能。

Link to this section如何确保我的标注格式正确以适用于 Ultralytics YOLO?#

你的标注应遵循 YOLO 检测格式。每个标注文件必须列出对象类别,以及它在图像中的边界框坐标。YOLO 格式确保了训练目标检测模型时数据处理的精简和标准化。有关正确标注格式的更多信息,请访问 YOLO 检测格式指南

Link to this section我可以使用 K 折交叉验证处理水果检测以外的自定义数据集吗?#

可以,只要标注采用 YOLO 检测格式,你就可以将 K 折交叉验证用于任何自定义数据集。将数据集路径和类标签替换为你自定义数据集特定的那些。这种灵活性确保了任何目标检测项目都能从使用 K 折交叉验证的稳健模型评估中受益。如需实际示例,请查看我们的 生成特征向量 部分。

评论