首页
/ 数据集的 train/val/test 划分:解读 Ultralytics `data/split.py` 数据集切分工具

数据集的 train/val/test 划分:解读 Ultralytics `data/split.py` 数据集切分工具

2026-09-07 21:33:58作者:贡沫苏Truman

数据准备是训练一切 YOLO 模型的第一步,而把一份原始图片合理划分成 train / val / test 三个子集则是数据准备中最常被低估的环节。本文以 Ultralytics 仓库中的 数据切分模块 为对象,深入讲解它提供的两大数据集切分工具——面向图像分类任务的 split_classify_dataset 与面向检测 / 分割 / 姿态等任务、按路径清单切分的 autosplit,覆盖其设计动机、目录结构约定、函数参数、底层实现与真实数据集(如 xView)中的落地用法。读完本文,你将掌握 Ultralytics 官方数据切分的全部机制,并能结合自己的数据规模正确选择切分方案。

模块全景:一个文件解决两类切分需求

ultralytics/data/split.py 是 Ultralytics 工具链中专门负责"把原始数据划分成训练可用子集"的模块,其公开 API 由两个函数组成(对应 API 参考文档 中自动生成的 ultralytics.data.split.split_classify_datasetultralytics.data.split.autosplit 两节):

函数 适用任务 切分产物 是否复制文件
split_classify_dataset(source_dir, train_ratio=0.8) 图像分类 新建 {source}_split/train{source}_split/val 目录并复制图片 是(shutil.copy2
autosplit(path, weights=(0.9, 0.1, 0.0), annotated_only=False) 检测 / 实例分割 / 姿态等(YOLO 标签体系) path.parent 生成 autosplit_train.txt / autosplit_val.txt / autosplit_test.txt 路径清单 否(仅写文本清单)

之所以需要两套方案,是因为两类任务的数据组织方式不同:图像分类数据集按 class_dir/图片 的目录结构组织,类别由目录名隐含,必须物理拆分目录才能得到独立子集;而 YOLO 检测类数据集通过一个数据集 YAML 指向 train / val 图片路径(可为目录或清单文件),标签则通过"把 images 换成 labels"的路径约定与图片一一对应,因此切分只要**生成图片路径清单(.txt)**即可,无需复制任何文件。

分类数据集切分:split_classify_dataset

split_classify_dataset 专门服务于以"子目录 = 类别"组织的图像分类数据集(如 Caltech101、ImageNet 的简化目录结构),它会在原目录旁新建一个 {source_dir}_split 目录,在其中按比例重建 train / val 两级子目录,并将图片按类别复制进去。

函数签名与参数

split.py 源码 可确认完整签名:

def split_classify_dataset(source_dir: str | Path, train_ratio: float = 0.8) -> Path:
参数 类型 默认值 说明
source_dir str | Path 必填 分类数据集根目录路径,其下每个子目录代表一个类别,子目录内为图片文件
train_ratio float 0.8 分配给 train 集的比例,取值区间 (0, 1);val 集比例为 1 - train_ratio
返回值 Path 新建的切分目录 {source_dir}_split 的路径

目录结构约定

源码 docstring 中给出了切分前后的标准结构对照(split.py):

切分前:                         切分后:
caltech/                        caltech_split/
├── class1/                     ├── train/
│   ├── img1.jpg                │   ├── class1/
│   ├── img2.jpg                │   │   ├── img1.jpg
│   └── ...                     │   │   └── ...
├── class2/                     │   ├── class2/
│   ├── img1.jpg                │   │   └── ...
│   └── ...                     └── val/
└── ...                             ├── class1/
                                    │   ├── img2.jpg
                                    │   └── ...
                                    ├── class2/
                                    └── ...

底层行为与实现细节

对照源码可以梳理出该函数的完整执行流程(split.py):

  1. 目录创建:以 Path(f"{source_path}_split") 构造输出根目录,并依次创建 trainval 子目录(exist_ok=True 保证可重复执行不会报错)。
  2. 统计与日志:枚举 source_dir 下所有子目录作为类别目录,统计类别数与图片总数,并通过 LOGGER.info 输出形如 Splitting caltech (2 classes, 100 images) into 80% train, 20% val... 的进度信息。
  3. 逐类别切分:对每个类别目录,仅收集后缀匹配图片格式白名单的文件(见下文),调用 random.shuffle 打乱后,按 int(len(image_files) * train_ratio) 计算切分点,前段复制到 train、后段复制到 val。
  4. 复制而非移动:使用 shutil.copy2,会保留文件的修改时间等元数据,且原始数据不受任何影响,可放心重复实验。
  5. 返回结果:日志输出 Split complete in {split_path} ✅ 并返回切分目录路径。

需要注意的一个实现细节:这里使用了 Python 全局 random 模块且没有固定随机种子,因此每次调用划分结果是随机的;若需要可复现的划分,可在调用前自行 random.seed(...)

只挑选合法图片:IMG_FORMATS 白名单

函数只处理扩展名(小写后)位于 IMG_FORMATS 集合内的文件。该集合定义于 data/utils.py,共 13 种常见图像格式:

avif, bmp, dng, heic, heif, jp2, jpeg, jpg, mpo, png, tif, tiff, webp

因此即使类别目录中混有 README.txt、缩略图缓存等无关文件,也不会被复制进 train / val 子集,天然实现了数据清洗。

实际调用示例

from ultralytics.data.split import split_classify_dataset

# 默认 80/20 划分,返回 Path("path/to/caltech_split")
split_path = split_classify_dataset("path/to/caltech")

# 自定义 75/25 划分
split_classify_dataset("path/to/caltech", 0.75)

一个自动触发场景:缺失 train 目录时的隐式切分

split_classify_dataset 并非只能手动调用——它在 data/utils.pycheck_cls_dataset 中被自动触发,构成一个很实用的兜底逻辑:

  1. 当校验分类数据集时发现 data_dir / "train" 目录不存在;
  2. 若递归扫描 data_dir 仍能找到合法的图片文件(image_files := [f for f in data_dir.rglob("*.*") if ...]),则日志输出 Dataset 'split=train' not found...Attempting to split...
  3. 自动以 train_ratio=0.8 调用 split_classify_dataset,并重新指向新生成的 train 子目录。

这意味着:当你手头只有一个"全部类别平铺在一起"的原始分类图片目录、直接把它作为 data 传给分类训练入口时,Ultralytics 会主动帮你完成 80/20 的 train/val 划分,无需手工准备目录。这一自动行为也从侧面印证了上述切分函数在设计上"对原始目录零破坏、只新增 _split 目录"的保守策略是刻意为之的。

检测类数据集的自动切分:autosplit

autosplit 面向 YOLO 家族最常见的标签体系(images/labels/ 同级、同名 txt 标签),核心思路是:不复制图片,而是在图片目录的父目录下生成 3 个持久化的路径清单文件 autosplit_train.txt / autosplit_val.txt / autosplit_test.txt,之后在数据集 YAML 中用这些清单作为 train / val 的来源。

函数签名与参数

split.py 源码 可确认完整签名:

def autosplit(
    path: Path = DATASETS_DIR / "coco8/images",
    weights: tuple[float, float, float] = (0.9, 0.1, 0.0),
    annotated_only: bool = False,
) -> None:
参数 类型 默认值 说明
path Path DATASETS_DIR / "coco8/images" 待切分图片所在目录,递归收集其下所有合法格式图片
weights tuple[float, float, float] (0.9, 0.1, 0.0) 依次对应 train / val / test 三个子集被抽中的权重,即 (训练, 验证, 测试) 的期望比例
annotated_only bool False True 时仅收录存在对应 txt 标签文件的图片
返回值 None 结果以 3 个 autosplit_*.txt 文件落盘

底层实现要点

对照源码可以梳理其执行逻辑(split.py):

  1. 递归收集图片files = sorted(x for x in path.rglob("*.*") if x.suffix[1:].lower() in IMG_FORMATS),即对 path 做递归展开并同样以 IMG_FORMATS 白名单过滤,保证子目录嵌套也能被覆盖。
  2. 固定随机种子random.seed(0) 被显式调用,意味着多次运行同一数据集会得到完全一致的划分结果,这对可复现实验至关重要。
  3. 按权重抽样分桶indices = random.choices([0, 1, 2], weights=weights, k=n),对每一张图片独立地按权重从 0(train)/1(val)/2(test) 中抽取一个归属,即每个子集的图片数只是期望比例而非精确比例。
  4. 清理旧产物:若 path.parent 下已存在同名清单文件,先 unlink 删除再追加写入,避免重复运行产生脏数据。
  5. 逐图片落盘:遍历(带 TQDM 进度条)时,若 annotated_only=True 则通过 img2label_paths 检查标签是否存在,只有通过检查的图片才会被追加写入对应的 txt[i]
  6. 写入相对路径:清单内每行是相对 path.parent 的 POSIX 风格相对路径并带 ./ 前缀,例如 ./images/img1.jpgsplit.py)。写 ./ 前缀是因为 data/base.py 在解析数据集 YAML 的 train/val 条目时,会把 ./ 开头的相对路径再拼接到数据集根目录上,形成"本地 → 全局"的路径转换。

标签路径换算:img2label_paths

autosplitannotated_only 过滤依赖 data/utils.pyimg2label_paths,它实现 YOLO 标准的路径映射规则:把路径中的 {sep}images{sep} 段替换为 {sep}labels{sep},同时把扩展名替换为 .txt。例如:

  • 输入 .../coco8/images/train/0001.jpg
  • 输出 .../coco8/labels/train/0001.txt

因此在使用 autosplit(..., annotated_only=True) 前,请务必保证标签目录采用 images ↔ labels 同级替换的默认布局;若自定义了标签目录名,可通过 img2label_paths(..., label_dir=...) 换算确认后再决定是否启用该开关。

典型用法示例

from ultralytics.data.split import autosplit

# 默认 90/10/0 划分(无测试集),作用于默认 coco8 图片目录
autosplit()

# 常用:80/15/5,且仅收录带标签的图片
autosplit(path="path/to/images", weights=(0.8, 0.15, 0.05), annotated_only=True)

运行完成后,会在 path父目录(例如 datasets/coco8/)中生成三个清单文件,可直接供数据集 YAML 引用:

# coco8 风格的 autosplit 引用写法
path: ../datasets/coco8
train: images/autosplit_train.txt
val: images/autosplit_val.txt

真实落地:xView 数据集的官方下载流程即用 autosplit

autosplit 不是孤立的工具函数——Ultralytics 官方维护的 xView 遥感检测数据集配置 就把它内嵌进 download: 脚本作为数据准备流水线的一环(xView.yaml):

from ultralytics.data.split import autosplit
...
convert_labels(dir / "xView_train.geojson")      # 1. GeoJSON → YOLO 标签
Path(dir / "train_images").rename(dir / "images" / "train")  # 2. 整理图片目录
Path(dir / "val_images").rename(dir / "images" / "val")
autosplit(dir / "images" / "train")              # 3. 对训练图片做 90/10 自动切分

对应地,该数据集的 YAML 中 train / val 直接指向生成的清单(xView.yaml):

train: images/autosplit_train.txt  # 约 90% 的 847 张标注图片
val: images/autosplit_val.txt      # 约 10% 的 847 张标注图片

从该数据集的官方文档说明可以看出这套流程的最终效果:autosplit_train.txt 列出约 90% 的带标注训练图,autosplit_val.txt 列出约 10% 用于验证。值得注意的是 xView 的 847 张训练图片本身就有官方标注,而官方单独的 val 图片无标签,所以这里切分的是"标注过的训练图"这一子集——这正是 autosplit 在真实数据集流水线中的典型定位。

fraction 训练参数的区别

很多开发者会把 autosplit 与训练时的 fraction 参数混为一谈,Ultralytics 官方在 simple-utilities 文档 中明确区分了两者:

  • autosplit(本模块):一次性生成持久化autosplit_*.txt 清单文件,划分结果固化在磁盘上,多次训练跨进程共享同一划分;
  • fraction 训练参数:在单次训练启动时按比例随机抽取子集用于本次 run,不落盘、每次运行可变化,适合快速试跑与消融实验。

两者可以组合使用:先用 autosplit 得到稳定的 train/val 清单,再配合 fraction 对训练子集做进一步的临时采样。此外,split_classify_dataset 是"物理复制文件"型划分,而 autosplit 是"生成路径清单"型划分,前者适用于目录即类别的分类任务,后者适用于 images/labels 布局的检测类任务——请根据任务类型选择,不要混用

进阶阅读与延伸

  • 若想进行更严谨的、按类别均衡的 K 折交叉验证而非简单随机切分,仓库提供了完整指南 kfold-cross-validation.md,其中也明确推荐了 autosplit 作为自动切分手段,并与基于 YOLODatasetkfold_split 方案做了对比。
  • 更多实用的数据集与标签工具(如 check_cls_dataset 的自动兜底切分、图片压缩、路径换算)集中在 simple-utilities.md
  • 两个函数的完整 API 文档见 split.md 参考页

小结

Ultralytics 通过 data/split.py 一个模块优雅地覆盖了两种主流数据组织方式下的切分需求:split_classify_dataset 负责"按类别目录物理拆分、复制出 train/val 两套目录",并在分类数据缺失 train 目录时被 check_cls_dataset 自动触发;autosplit 负责"在 images/labels 布局下按权重随机生成持久化的路径清单",以固定种子保证可复现,其可靠性经过了 xView 等官方数据集下载流程的验证。理解这两个函数的分工与内部约定,是把自己的原始数据快速、正确地送入 Ultralytics 训练管线的前提。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
897
5.8 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
594
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
916
1.83 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.58 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.01 K
516
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
547
388