数据集的 train/val/test 划分:解读 Ultralytics `data/split.py` 数据集切分工具
数据准备是训练一切 YOLO 模型的第一步,而把一份原始图片合理划分成 train / val / test 三个子集则是数据准备中最常被低估的环节。本文以 Ultralytics 仓库中的 数据切分模块 为对象,深入讲解它提供的两大数据集切分工具——面向图像分类任务的 split_classify_dataset 与面向检测 / 分割 / 姿态等任务、按路径清单切分的 autosplit,覆盖其设计动机、目录结构约定、函数参数、底层实现与真实数据集(如 xView)中的落地用法。读完本文,你将掌握 Ultralytics 官方数据切分的全部机制,并能结合自己的数据规模正确选择切分方案。
模块全景:一个文件解决两类切分需求
ultralytics/data/split.py 是 Ultralytics 工具链中专门负责"把原始数据划分成训练可用子集"的模块,其公开 API 由两个函数组成(对应 API 参考文档 中自动生成的 ultralytics.data.split.split_classify_dataset 与 ultralytics.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):
- 目录创建:以
Path(f"{source_path}_split")构造输出根目录,并依次创建train、val子目录(exist_ok=True保证可重复执行不会报错)。 - 统计与日志:枚举
source_dir下所有子目录作为类别目录,统计类别数与图片总数,并通过LOGGER.info输出形如Splitting caltech (2 classes, 100 images) into 80% train, 20% val...的进度信息。 - 逐类别切分:对每个类别目录,仅收集后缀匹配图片格式白名单的文件(见下文),调用
random.shuffle打乱后,按int(len(image_files) * train_ratio)计算切分点,前段复制到 train、后段复制到 val。 - 复制而非移动:使用
shutil.copy2,会保留文件的修改时间等元数据,且原始数据不受任何影响,可放心重复实验。 - 返回结果:日志输出
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.py 的 check_cls_dataset 中被自动触发,构成一个很实用的兜底逻辑:
- 当校验分类数据集时发现
data_dir / "train"目录不存在; - 若递归扫描
data_dir仍能找到合法的图片文件(image_files := [f for f in data_dir.rglob("*.*") if ...]),则日志输出Dataset 'split=train' not found...Attempting to split...; - 自动以
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):
- 递归收集图片:
files = sorted(x for x in path.rglob("*.*") if x.suffix[1:].lower() in IMG_FORMATS),即对path做递归展开并同样以IMG_FORMATS白名单过滤,保证子目录嵌套也能被覆盖。 - 固定随机种子:
random.seed(0)被显式调用,意味着多次运行同一数据集会得到完全一致的划分结果,这对可复现实验至关重要。 - 按权重抽样分桶:
indices = random.choices([0, 1, 2], weights=weights, k=n),对每一张图片独立地按权重从 0(train)/1(val)/2(test) 中抽取一个归属,即每个子集的图片数只是期望比例而非精确比例。 - 清理旧产物:若
path.parent下已存在同名清单文件,先unlink删除再追加写入,避免重复运行产生脏数据。 - 逐图片落盘:遍历(带 TQDM 进度条)时,若
annotated_only=True则通过img2label_paths检查标签是否存在,只有通过检查的图片才会被追加写入对应的txt[i]。 - 写入相对路径:清单内每行是相对
path.parent的 POSIX 风格相对路径并带./前缀,例如./images/img1.jpg(split.py)。写./前缀是因为 data/base.py 在解析数据集 YAML 的 train/val 条目时,会把./开头的相对路径再拼接到数据集根目录上,形成"本地 → 全局"的路径转换。
标签路径换算:img2label_paths
autosplit 的 annotated_only 过滤依赖 data/utils.py 的 img2label_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作为自动切分手段,并与基于YOLODataset的kfold_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 训练管线的前提。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0630
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00