跳至内容

K-Fold 交叉验证Ultralytics

导言

本综合指南说明了如何在Ultralytics 生态系统中对对象检测数据集实施 K 折交叉验证。我们将利用YOLO 检测格式和关键的Python 库(如 sklearn、pandas 和 PyYaml),指导您完成必要的设置、生成特征向量的过程以及 K-Fold 数据集拆分的执行。

K 折交叉验证概述

无论您的项目涉及水果检测数据集还是自定义数据源,本教程都将帮助您理解和应用 K 折交叉验证,以增强机器学习模型的可靠性和稳健性。当我们应用 k=5 请记住,最佳折叠次数可能因数据集和项目的具体情况而异。

话不多说,让我们开始吧!

设置

  • 您的注释应采用YOLO 检测格式

  • 本指南假设注释文件可从本地获取。

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

    • 该数据集共包含 8479 幅图像。
    • 它包括 6 个类别标签,每个标签的实例总数如下。
类别标签 实例计数
苹果 7049
葡萄 7202
菠萝 1613
橙色 15549
香蕉 3536
西瓜 1976
  • 必要的Python 软件包包括

    • ultralytics
    • sklearn
    • pandas
    • pyyaml
  • 本教程使用 k=5 折叠次数。不过,您应该根据具体数据集确定最佳折叠次数。

  • 启动新的Python 虚拟环境 (venv)并激活它。使用 pip (软件包管理器)进行安装:

    • Ultralytics 图书馆: pip install -U ultralytics.或者,您也可以克隆官方的 repo.
    • Scikit-learn、pandas 和 PyYAML: pip install -U scikit-learn pandas pyyaml.
  • 验证您的注释是否采用YOLO 检测格式

    • 在本教程中,所有注释文件都在 Fruit-Detection/labels 目录。

生成物体检测数据集的特征向量

  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 文件的内容并提取类标签的索引。

    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 DataFrame.

    import pandas as pd
    
    indx = [l.stem for l in labels]  # uses base filename as ID (no extension)
    labels_df = pd.DataFrame([], columns=cls_idx, index=indx)
    
  5. 计算注释文件中每个类别标签的实例数。

    from collections import Counter
    
    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 折交叉验证应用于对象检测数据集。

K 倍数据集拆分

  1. 现在,我们将使用 KFoldsklearn.model_selection 以产生 k 对数据集进行分割。

    • 重要:
      • 设置 shuffle=True 确保了分班中班级的随机分布。
      • 通过设置 random_state=M 其中 M 是一个选定的整数,这样就可以得到可重复的结果。
    from sklearn.model_selection import KFold
    
    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=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
    

    最理想的情况是,每次分割和不同类别的所有类别比率都相当相似。不过,这取决于数据集的具体情况。

  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 (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,
            )
    
  5. 最后,将图像和标签复制到每个分割的相应目录("train "或 "val")中。

    • 注意:这部分代码所需的时间将根据数据集的大小和系统硬件而有所不同。
    import shutil
    
    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 折分割和标签分布数据框的记录保存为 CSV 文件,以备将来参考。

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

使用 K 折数据分割训练YOLO

  1. 首先,加载YOLO 模型。

    from ultralytics import YOLO
    
    weights_path = "path/to/weights.pt"
    model = YOLO(weights_path, task="detect")
    
  2. 接下来,遍历数据集 YAML 文件以运行训练。结果将保存到由 projectname 参数。默认情况下,该目录为 "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
    

结论

在本指南中,我们探索了使用 K 折交叉验证来训练YOLO 物体检测模型的过程。我们学习了如何将数据集分成 K 个分区,确保不同折叠中的类分布均衡。

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

此外,我们还保存了我们的记录,以备将来参考,这在大型项目或排除模型性能故障时尤为有用。

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

这种 K 折交叉验证技术是充分利用可用数据的一种稳健方法,有助于确保模型在不同数据子集中的性能是可靠和一致的。这将产生一个更具通用性和可靠性的模型,从而减少对特定数据模式的过度拟合。

请记住,虽然我们在本指南中使用的是YOLO ,但这些步骤大多可用于其他机器学习模型。了解了这些步骤,你就能在自己的机器学习项目中有效地应用交叉验证。祝你编码愉快

常见问题

什么是 K 折交叉验证,为什么它在物体检测中很有用?

K 折交叉验证是一种将数据集划分为 "k "个子集(折叠)的技术,用于更可靠地评估模型性能。每个折叠既是训练数据,也是验证数据。在物体检测方面,使用 K 折交叉验证有助于确保Ultralytics YOLO 模型的性能在不同数据集之间具有稳健性和通用性,从而提高其可靠性。有关使用Ultralytics YOLO 设置 K-Fold 交叉验证的详细说明,请参阅 Ultralytics 的 K-Fold 交叉验证

如何使用Ultralytics YOLO 实现 K-Fold 交叉验证?

要通过Ultralytics YOLO 实现 K 折交叉验证,需要遵循以下步骤:

  1. 验证注释是否符合YOLO 检测格式
  2. 使用Python 库,如 sklearn, pandaspyyaml.
  3. 根据数据集创建特征向量
  4. 使用 KFoldsklearn.model_selection.
  5. 在每个分割点上训练YOLO 模型。

有关全面指南,请参阅我们文档中的K-Fold 数据集拆分部分。

为什么要使用Ultralytics YOLO 进行物体检测?

Ultralytics YOLO 提供最先进的实时物体检测功能,具有高精确度和高效率。它用途广泛,支持多种计算机视觉任务,如检测、分割和分类。此外,它还能与Ultralytics HUB 等工具无缝集成,实现无代码模型训练和部署。欲了解更多详情,请访问Ultralytics YOLO 页面了解其优势和功能。

如何确保我的注释格式正确,可用于Ultralytics YOLO ?

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

除了水果检测之外,我还能对自定义数据集使用 K-Fold 交叉验证吗?

是的,只要注释是YOLO 检测格式,您就可以对任何自定义数据集使用 K-Fold 交叉验证。用您自定义数据集的特定路径和类标签代替数据集路径和类标签。这种灵活性确保任何对象检测项目都能受益于使用 K 折交叉验证进行的强大模型评估。有关实用示例,请查看我们的生成特征矢量部分。



创建于 2023-11-12,更新于 2024-07-05
作者:glenn-jocher(10)、IvorZhu331(1)、Burhan-Q(1)

评论