如实解读检测指标
运行验证,将精确率、召回率、mAP50-95、各类别 AP 和混淆矩阵结合起来阅读,并根据目标选择交付阈值。
一个数字让人安心:mAP 0.83,搞定。但相同的 mAP 可能掩盖某个类别有一半时间检测失败、边界框总是过松,或者某个阈值导致每分钟都触发一次告警。你将运行一次真实验证,逐行阅读结果,直到明确需要修复什么以及阈值该设在哪里。
实践操作
如何评估每个预测#
验证过程会将每个预测框与真实标注进行匹配。如果预测类别正确,且它与尚未匹配的真实标注框之间的 IoU 超过阈值,该预测就是真正例(TP);否则就是假正例(FP)。每个未匹配的真实标注对象都是一个假负例(FN)。
| 指标 | 公式 | 回答的问题 |
|---|---|---|
| 精确率 | TP / (TP + FP) | 模型绘制边界框时,有多大概率是正确的? |
| 召回率 | TP / (TP + FN) | 实际物体中有多少被模型找到了? |
| F1 | 2 × P × R / (P + R) | 一个兼顾两者的数值 |
降低置信度阈值会增加预测框数量:召回率上升,精确率下降。精确率-召回率曲线描绘了所有阈值下的这种权衡,其曲线下面积就是单个类别的平均精度(AP)。mAP50是在 IoU 0.5 时对各类别 AP 求平均,此时明显偏松的边界框仍会被计为正确。mAP50-95对 0.5 到 0.95 之间的十个 IoU 阈值求平均,因此会奖励更贴合目标的边界框;它是 Ultralytics 模型表中的核心指标。
运行验证#
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
metrics = model.val(data="coco8.yaml")
print(f"precision={metrics.box.mp:.3f} recall={metrics.box.mr:.3f}")
print(f"mAP50={metrics.box.map50:.3f} mAP50-95={metrics.box.map:.3f}") Class Images Instances Box(P R mAP50 mAP50-95)
all 4 17 0.551 0.962 0.943 0.669
person 3 10 0.551 0.8 0.848 0.387
dog 1 1 0.36 1 0.995 0.597
horse 1 2 0.36 1 0.995 0.696
elephant 1 2 0.656 0.969 0.828 0.545
umbrella 1 1 0.543 1 0.995 0.895
potted plant 1 1 0.833 1 0.995 0.895从上到下阅读:
- Instances 17, Images 4。 COCO8 是一个冒烟测试数据集。4 张图像不足以支持交付决策;请把所有数值都视为示例。
- mAP50 0.943,mAP50-95 0.669。 大多数物体都能找到,但许多边界框过松。两者差距很大,说明短板在定位,而不是检测。
person的 AP50-95 为 0.387。 最常见的类别反而是最弱的类别。这就是你的瓶颈。- P 为 0.551,R 为 0.962。 这两列是在跨类别 F1 最大化时对应的单一置信度下读取的——这里的阈值较低——而不是你进行预测时使用的 0.25。不要把它们称为“模型的精确率”。
各类别 AP 和交付阈值#
同一次运行会保留各类别 AP,以及每个置信度阈值下的精确率和召回率,因此你可以根据目标选择阈值,而不是凭猜测:
from ultralytics import YOLO
model = YOLO("yolo26n.pt")
metrics = model.val(data="coco8.yaml", plots=False, verbose=False)
box = metrics.box
# Per-class AP from the same run: the average hides the weakest class
for i, c in enumerate(box.ap_class_index):
print(f"{model.names[int(c)]:13} AP50={box.ap50[i]:.3f} AP50-95={box.ap[i]:.3f}")
# Precision and recall at every confidence threshold for one class
row = list(box.ap_class_index).index(0) # class 0 = person
for conf in (0.1, 0.25, 0.5, 0.75):
j = int(conf * (len(box.px) - 1))
print(f"person @ conf {conf:.2f}: precision {box.p_curve[row, j]:.2f}, recall {box.r_curve[row, j]:.2f}") all 4 17 0.551 0.962 0.943 0.669
person AP50=0.848 AP50-95=0.387
dog AP50=0.995 AP50-95=0.597
horse AP50=0.995 AP50-95=0.696
elephant AP50=0.828 AP50-95=0.545
umbrella AP50=0.995 AP50-95=0.895
potted plant AP50=0.995 AP50-95=0.895
person @ conf 0.10: precision 0.63, recall 0.80
person @ conf 0.25: precision 1.00, recall 0.54
person @ conf 0.50: precision 1.00, recall 0.40
person @ conf 0.75: precision 1.00, recall 0.14这样,阈值就是一条决策规则,而不是一种感觉:
| 根据决策标准确定目标 | 选择 | 本次运行的结果 |
|---|---|---|
| 误报最多占告警的 5%(精确率 ≥ 0.95) | 满足要求的最低阈值,以尽可能保持较高召回率 | 0.25:精确率 1.00,召回率 0.54 |
| 最多漏掉五分之一的人(召回率 ≥ 0.80) | 满足要求的最高阈值,以尽可能保持较高精确率 | 0.10:精确率 0.63,召回率 0.80 |
在这里两种方案都不够好:高精确率会漏掉一半的人,高召回率则会让三分之一的告警成为误报。这就是选择“迭代”的依据,而不是阈值本身的问题。验证还会将 BoxPR_curve.png、BoxP_curve.png、BoxR_curve.png、BoxF1_curve.png 和 confusion_matrix.png 保存到运行目录中,因此你可以从图表中看到同样的权衡;性能指标指南会逐一介绍这些指标。
接下来修复什么#
混淆矩阵会告诉你弱势类别被误认为什么。如果 forklift 的 AP 偏低,而且叉车被预测为 pallet_jack,这属于类别混淆,而不是定位问题:添加能体现视觉差异的示例;如果下游代码不需要区分这两个类别,也可以将它们合并。模型评估分析指南会介绍整个工作流程。

大多数修复都落在第一和第三个方面:为瓶颈类别提供更好的数据和更好的标签。只有通过前面三节的数据检查后,才更改模型或训练设置。
| 在冻结的测试集上获取证据 | 决策 |
|---|---|
| 每个对业务至关重要的类别都达到其精确率和召回率目标 | 交付,并进行监控 |
| 某个类别或条件未达到目标 | 迭代:针对这一差距补充数据并改进标签 |
| 所有类别都远未达标,或各类别在视觉上彼此重叠 | 重新思考任务规范、类别或摄像头配置 |
常见问题#
0.83 的 mAP 可能掩盖某个类别只有 0.40 的事实。如果这个类别承载着业务价值,那你的模型就不是 0.83,而是一个被漂亮平均值掩盖的问题模型。
症状:模型“得分 0.94”,但画出的边界框过于松散,无法用于测量或裁剪。解决方法:报告 mAP50-95,并将它与 mAP50 的差距视为定位能力的信号。
症状:只有几十张图像时就得到看似完美的数值,或者验证集包含训练集的近似副本。解决方法:将验证集扩充到每个重要类别数百个实例,并在查看任何指标之前,按照上一课的方法运行近重复项检查。
显示解答
评估结果:yolo26n.pt 上的人数计数器,COCO8 验证集
瓶颈是 person,AP50-95 为 0.387,而总体值为 0.669。它也是实例数最多的类别(17 个中有 10 个),因此它既是表现最弱的类别,也是对人数计数器最重要的类别。它的 AP50 为 0.848,说明模型能找到大多数人,但框画得比较松——与任何测量位置或尺寸的任务相比,这对计数的影响较小。
若要达到精确率 ≥ 0.95,最低合格阈值是 conf 0.25,此时精确率为 1.00,但召回率只有 0.54:计数器会漏掉将近一半的人。若要达到召回率 ≥ 0.80,我们需要将 conf 设为 0.10,此时三分之一的计数会是误报。
建议:迭代。 从部署摄像头采集并标注人员数据——尤其是小尺寸、被遮挡和位于画面边缘的人员——然后在包含数百人的固定测试集上重新验证。仅凭 4 张验证图像,无论如何都不足以支持部署决策。