Ultralytics YOLO27: 加入候补名单。
Get Started
不止于检测:分割、姿态、OBB 和分类·分类、组合、出航·课程 8/10
课程中级

训练图像分类器

整理文件夹数据集,在 MNIST160 上训练 yolo26n-cls.pt,读取概率和 top-1 准确率,并将低置信度预测转交人工审核。

图像分类针对每张图像回答一个问题:这是什么?它所需的标注成本是所有任务中最低的——只需一个文件夹名称——而且运行速度最快,因此它是检测器之后默认的第二阶段,也是每帧只看到一个部件的检测工位的理想首选模型。它的数据集没有 YAML 文件,因此文件夹结构就是约定;分类器总会给出答案,即使它从未见过类似的图像。判断何时不该相信分类器,也是工作的一半。

学习成果

在 MNIST160 上训练 yolo26n-cls.pt,报告 top-1 准确率,从 result.probs 读取类别概率,并选择一个置信度阈值,将不确定的图像送去审核。

快速通道
如果你已经熟悉相关内容,以下是简要版。
  1. 将图像放入 train/<class>/ 和 val/<class>/;文件夹名称会成为类别名称,并按字母顺序排序。

  2. yolo classify train data=mnist160 model=yolo26n-cls.pt epochs=1 imgsz=64.

  3. 以 top-1 准确率(metrics.top1)作为准入指标;类别数不超过五个时,top-5 始终为 1.0。

  4. 读取 result.probs.top1 和 .top1conf,并将低于置信度阈值的预测交给人工处理。

实践操作

文件夹数据集的结构#

Ultralytics YOLO 图像分类示例:三片叶子分别标记为健康、锈病和白粉病,并显示各自的置信度

分类数据集是目录,而不是 YAML 文件。将 data= 指向根文件夹:

parts/
├── train/
│   ├── good/      img_0001.jpg ...
│   ├── scratch/   img_0412.jpg ...
│   └── dent/      img_0733.jpg ...
└── val/
    ├── good/
    ├── scratch/
    └── dent/

根据已安装的源码和分类数据集指南,加载器对数据集的处理方式如下:

情况行为
类别索引train/ 下的文件夹名称按字母顺序排序 → dent=0,good=1,scratch=2
验证文件夹先是 val/,然后是 validation/,再是 valid/;如果都没有,则在发出警告后回退到 test/
没有 train/ 文件夹,只有类别文件夹自动按 80/20 的比例拆分到 train/ 和 val/

在 Ultralytics Platform 上,分类数据集为每张图像提供一个图像级标签,而不是形状标注:

Ultralytics Platform 分类数据集:传送带上有多个白色盒子,每个盒子只有一个图像级标签 class2,该标签从类别面板中选取

新文件夹会重新排列类别编号

症状:添加类别后,旧集成会把 good 部件报告为 dent。原因:添加名为 bent 的文件夹后,它会排在最前面,导致其后所有索引都发生偏移。解决方法:始终使用 model.names(或 result.names)将索引映射到名称;不要硬编码索引。

在 MNIST160 上训练#

MNIST160 包含 160 张手写数字图像,train/ 和 test/ 中各有 80 张,每个部分又分为十个数字文件夹——非常适合 CPU 冒烟测试。它没有 val/ 文件夹,因此请留意加载器发出警告并回退到 test/:

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")
model.train(data="mnist160", epochs=1, imgsz=64, device="cpu")
metrics = model.val()
print(f"top-1: {metrics.top1:.3f}  top-5: {metrics.top5:.3f}")
WARNING ⚠️ Dataset 'split=val' not found, using 'split=test' instead.
val: .../datasets/mnist160/test... found 80 images in 10 classes ✅
                   all      0.162      0.463
top-1: 0.162  top-5: 0.463

一个 epoch 几乎不会改变新建的十分类头。在我们的运行中,epochs=10 在笔记本电脑 CPU 上约 10 秒内达到了 top-1 0.55 和 top-5 0.95——流程运行正常,只是还需要更长的训练计划。分类任务指南列出了预训练模型的尺寸。

Top-1 表示最高概率类别正确的图像占比。Top-5 在正确类别出现在概率最高的五个类别中时计为命中。对于包含 1,000 个类别的 ImageNet,top-5 很有参考价值,因为其类别从宽泛类别到非常细粒度的类别都有;类别数不超过五个时,top-5 始终为 1.0,类别数为十个时,它也很宽松。应结合 top-1 准确率和逐类别混淆矩阵来设定准入标准。

ImageNet 示例图像按从宽泛到细粒度的类别排列,例如从哺乳动物到哈士奇,以及从车辆到三体帆船

在代码中读取概率#

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")  # ImageNet, 1,000 classes
result = model("https://ultralytics.com/images/bus.jpg")[0]

probs = result.probs
print(f"top-1: {result.names[probs.top1]} ({float(probs.top1conf):.2f})")
for i, conf in zip(probs.top5, probs.top5conf.tolist()):
    print(f"  {result.names[i]:20s} {conf:.3f}")
top-1: minibus (0.52)
  minibus              0.516
  police_van           0.369
  trolleybus           0.096
  minivan              0.006
  ambulance            0.003

probs.top1 是一个索引,probs.top5 是索引列表,而 probs.data 是完整的概率向量。注意 top-1 置信度:0.52 仅略高于在 minibus 和 police_van 之间抛硬币的概率。你可以根据这个数值决定如何分流。

将不确定的预测交给人工处理#

生产环境中的分类器很少需要对每张图像都判断正确;它需要对自己决定处理的图像判断正确。这个脚本会训练 10 个 epoch 的模型,然后测量不同置信度阈值在 80 张留出数字图像上的效果:

from pathlib import Path

from ultralytics import YOLO

model = YOLO("yolo26n-cls.pt")
model.train(data="mnist160", epochs=10, imgsz=64, device="cpu", plots=False)  # model now holds best.pt
print(model.names)  # folder names, sorted, became class indices

test = Path(model.trainer.data["test"])  # mnist160/test, the split the loader validated on
images = sorted(test.rglob("*.png"))
results = model(images, imgsz=64, verbose=False)
truth = [p.parent.name for p in images]

for threshold in (0.0, 0.3, 0.6):
    kept = [(r, t) for r, t in zip(results, truth) if r.probs.top1conf >= threshold]
    correct = sum(r.names[r.probs.top1] == t for r, t in kept)
    print(f"threshold {threshold:.1f}: auto-accept {len(kept):2d}/{len(images)}, accuracy on accepted {correct / max(len(kept), 1):.0%}")
{0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9'}
threshold 0.0: auto-accept 80/80, accuracy on accepted 55%
threshold 0.3: auto-accept 41/80, accuracy on accepted 68%
threshold 0.6: auto-accept 10/80, accuracy on accepted 80%

每一行都代表一种权衡:提高阈值会让被接受的预测更准确,同时会把更多图像交给人工处理。在 0.6 阈值下,该模型仅对 80 个数字中的 10 个自行作出判断——它还无法自动处理任何任务,解决方法是增加训练,而不是设计更巧妙的阈值。每轮训练后重新运行同一张表:它会同时告诉你能够达到的准确率,以及各阈值会产生多少审核工作。

判断规则

在留出数据上测量已接受图像的准确率,选择满足要求的最低阈值。然后检查由此产生的审核量是否在团队可处理的范围内;如果超出范围,就改进模型。

常见问题#

没有“以上皆非”类别

症状:空工位、错误部件或遮住镜头的手都被赋予了置信度很高的类别标签。原因:softmax 概率之和始终为 1,因此分类器总会从已有类别中选出一个。解决方法:添加一个装有这些图像的 other/ 文件夹,并将低 top1conf 预测结果送审。

在新文件夹上验证预训练分类头

症状:预训练模型在尚未训练前,在你的数据集上得分接近零。原因:yolo26n-cls.pt 会预测 1,000 个 ImageNet 索引,而你的文件夹复用了索引 0–9,但含义不同:在 MNIST160 上,文件夹 0 是索引 0,预训练分类头将其称为 tench。我们测得这种情况下 top-1 为 0.000。解决方法:先微调,再验证。

数据集文件夹泄漏

症状:验证时 top-1 接近 1.0,生产环境中的表现却低得多。原因:train/ 和 val/ 中存在同一部件的近重复照片。解决方法:按部件或采集批次划分数据集,参见正确划分数据集。

分类器是在 imgsz=224 尺寸上预训练的,而不是 640。对于大图像中的小目标,与其使用更大的 imgsz,不如采用先检测再分类的方法(下一课会介绍)。

试试看

在 mnist160 上训练 yolo26n-cls.pt:先以 1 个 epoch 训练,再以 imgsz=64 训练 10 个 epoch,并记录两次运行的 top-1 和 top-5。打印 model.names,确认文件夹名称如何变成类别索引。然后运行 route.py,为自动接受图像准确率至少达到 75% 的要求选定阈值。

完成条件
满足以下所有条件,即表示你已完成本课程。
  • 你已经获得 1 个 epoch 和 10 个 epoch 运行的 top-1 和 top-5。

  • 你打印了 model.names,并能解释按字母顺序分配索引的规则,以及为什么 top-5 对五分类问题毫无用处。

  • 你给出了一个阈值、被接受图像上的准确率,以及 80 张图像中有多少张会被送去审核。

显示解答
from ultralytics import YOLO

for epochs in (1, 10):
    model = YOLO("yolo26n-cls.pt")
    model.train(data="mnist160", epochs=epochs, imgsz=64, device="cpu")
    metrics = model.val()
    print(f"{epochs:2d} epochs  top-1: {metrics.top1:.3f}  top-5: {metrics.top5:.3f}")
print(model.names)  # {0: '0', 1: '1', ..., 9: '9'}

我们的运行结果:1 个 epoch → top-1 0.162,top-5 0.463;10 个 epoch → top-1 0.550,top-5 0.950。对于五分类问题,正确答案总会出现在前五名中,因此 top-5 按定义就是 1.0,无法提供任何信息。

若要让被接受图像的准确率达到 75%,route.py 给出的阈值是 0.6(接受的 10 张图像中准确率为 80%),这会将 80 个数字中的 70 个送去审核。这才是实情:该阈值满足准确率要求,但模型目前还没有实用价值。继续训练,并在为实际应用选择阈值之前重新运行这张表。

接下来做什么

接下来,我们将串联多个任务:由检测器为分类器提供输入,再由姿态模型为跟踪器提供输入。

登录以保存学习进度