验证与指标
验证通过 val() 把一个模型跑在一个数据集划分(split)上,返回一个由指标键和 float 值组成的扁平字典。这些键都是字面字符串,你拿到哪些取决于任务,而不是家族。
运行一次验证
val() 接收数据集,返回指标。
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt")metrics = model.val(data="coco8.yaml") print(metrics["metrics/mAP50-95"])print(metrics["metrics/mAP50"])print(metrics["speed/total_ms"])libreyolo val model=LibreYOLO9s.pt data=coco8.yamlfrom libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt")metrics = model.val(data="coco8.yaml", split="train", batch=4) print(metrics)返回值是一个由指标键到数值组成的扁平 dict。每个键都是字面量,所以按名字读,而不是按
位置读。对于检测、分割和分类,它是一个 dict 子类,还带有作为属性的逐类别结果,见下文。
主要参数是 data、split、batch、imgsz、conf、iou、workers、device、
augment、save_json 和 verbose。conf 默认是 0.001,iou 默认是 0.6,
两个都比预测的默认值宽松得多,因为一次 mAP 扫描需要低置信度的那条长尾。imgsz
默认取模型自身的输入尺寸,而不是一个固定数字。split 只接受 val、test 或
train,别的都不行。不传 data 时,训练过的检查点会在它训练所用的数据集上验证;
已发布的权重不携带数据集,需要传 data=。
验证配置里的其他字段都会作为关键字参数透传,包括 save_dir、max_det、
eval_max_det、half、amp_dtype、cache、save_plots、single_cls 和 agnostic_nms。在 CUDA 上设 half=True 时,验证在 amp_dtype 的 autocast 下运行,bfloat16 的输出会在后处理之前转成 float32;用 amp_dtype="bfloat16" 训练时的验证也是这样做的。
agnostic_nms=True 会在重叠超过 iou 的检测框之间,无论类别如何,只保留得分最高的那一个。它作用在家族已经完成的检测结果上,在 classes= 之后,所以 mAP、混淆矩阵、曲线和逐图像计数打分的都是同一批检测框。它覆盖检测和分割,任何其他任务都会抛出 ValueError。预测一侧见阈值与过滤。
single_cls=True 把检测当作单一类别来打分。真值被合并为类别 0,模型的检测按模型自身标签空间里的 classes= 保留,然后重新标为 0,所以一个在多个类别上训练的检查点会被当作单类别检测器来打分。
每种任务的指标键
目标检测返回 COCO 那一族数字:
metrics/mAP50-95 metrics/mAP50 metrics/mAP75metrics/mAP_small metrics/mAP_medium metrics/mAP_largemetrics/AR1 metrics/AR10 metrics/AR100 metrics/AR_max_detmetrics/AR_small metrics/AR_medium metrics/AR_largemetrics/precision metrics/recallmetrics/precision(B) metrics/recall(B) metrics/mAP50(B) metrics/mAP50-95(B)其中有两个是坑。metrics/precision 和 metrics/recall 是为向后兼容保留的别名:
它们装的是 mAP 50-95 和 AR@100 的值,而不是一对查准率和查全率。用带名字的键。
实例分割把上面那些 mAP 和 AR 数字当作掩码(mask)数字放在不带后缀的键下,检测框
版本放在 (B) 后缀下,掩码版本在 (M) 下再重复一遍。这个任务里的查准率和查全率
只有带后缀的形式,也就是 metrics/precision(B)/metrics/recall(B) 和
metrics/precision(M)/metrics/recall(M),两对装的都是和 detect 一样的别名值:
(B) 那对是检测框 mAP50-95 和检测框 AR@100,(M) 那对是掩码 mAP50-95 和掩码
AR@100。
| 任务 | 键 |
|---|---|
| detect | metrics/mAP50-95、metrics/mAP50、metrics/mAP75,以及上面按尺寸和查全率的细分 |
| segment | 上面 detect 那些键的掩码版本(不带后缀的键就是掩码);precision/recall 只以 (B)/(M) 的形式存在,两者的别名方式相同 |
| pose | metrics/keypoints_mAP50-95、metrics/keypoints_mAP50、metrics/keypoints_mAP75、metrics/keypoints_mAP_M、metrics/keypoints_mAP_L,以及对应的 keypoints_AR 各键 |
| obb | metrics/mAP50-95、metrics/mAP50、metrics/mAP75、metrics/precision、metrics/recall,外加带 (OBB) 后缀的副本 |
| classify | metrics/accuracy_top1、metrics/accuracy_top5 |
| semantic | metrics/mIoU、metrics/pixel_accuracy |
| panoptic | metrics/PQ、metrics/SQ、metrics/RQ、metrics/PQ_things、metrics/PQ_stuff、metrics/categories |
| depth | metrics/abs_rel、metrics/rmse、metrics/delta1、metrics/delta2、metrics/delta3 |
| normal | metrics/mean_angular_error、metrics/median_angular_error、metrics/within_11_25、metrics/within_22_5、metrics/within_30 |
| edge | metrics/ODS、metrics/OIS、metrics/best_threshold |
| restore | metrics/PSNR、metrics/SSIM |
| matte | metrics/MAE、metrics/Smeasure |
| ocr | metrics/det_precision、metrics/det_recall、metrics/det_hmean、metrics/e2e_precision、metrics/e2e_recall、metrics/e2e_f1、metrics/rec_1-NED |
| point | metrics/precision、metrics/recall、metrics/f1、metrics/MLE、metrics/MAE、metrics/RMSE,外加一个 mAP 扫描键 |
OBB 的 metrics/precision 和 metrics/recall 不是别名:它们是 IoU 0.50 下真实的
查准率和查全率,取在最宽松的工作点上(每一条挺过 conf 的预测,默认 0.001)。
带 (OBB) 后缀的副本以任务专属的名字把同样这四个值重复一遍,和上面 (B) 与
(M) 的约定一样。
accuracy_top5 其实是 top-min(5, num_classes),所以在一个三类的数据集上它就是
top-3,每个样本都满足,因此读数是 1.0。分类结果还把这两个值暴露为属性 top1 和
top5,与 confusion_matrix 放在一起。
point 任务的扫描键是按距离阈值拼出来的,所以用默认值时它是
metrics/mAP@[0.01:0.10],单阈值的那个键是 metrics/mAP@0.01。传入
dist_thresholds 会把这两个字符串都改掉。
大多数任务还会返回一个 fitness 键,也就是最佳检查点(checkpoint)选择默认用的
那个单一数字。目标检测、分割和 OBB 不带这个键;它们的家族按 metrics/mAP50-95
来选,而这个键它们的字典确实会返回。姿态既不返回 fitness 也不返回
metrics/mAP50-95;它的训练器改把 best_metric_key 设成
metrics/keypoints_mAP50-95。
ImageFolder 分类新增宏平均 metrics/precision、metrics/recall 和 metrics/f1,对验证目标中出现的类别取平均。top-1 仍是默认适应度。检测还返回 metrics/best_conf 和 metrics/best_conf_f1,在 IoU 0.50 下选择 micro-F1 最优阈值,并把以类别名称为键的阈值放在 metrics.box.best_conf_per_class 上。同分检测保持分组;并列时选择更高的阈值。没有正 F1 时返回 0.0。分割不提供这些阈值键。
混淆矩阵
检测、分割和分类的结果带有 results.confusion_matrix。每次验证都会计算它,不管有没有写出图表,它的 matrix 是一个整数数组,索引为 matrix[predicted, true]。
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt")metrics = model.val(data="coco8.yaml") cm = metrics.confusion_matrixprint(cm.matrix.shape) # (81, 81):80 个类别加背景print(cm.class_accuracy()) # 类别名:找到的目标中被判为该类别的占比tp, fp = cm.tp_fp()对于检测和分割,置信度达到 0.25(或更高的 conf)的预测,会与任意类别的真值检测框在 IoU 0.5 下一对一配对,IoU 高的优先。一个配对按它预测的类别和真实的类别计数,所以位置准确但类别错误的框会落在对角线之外。矩阵多出一个 background 行和列:没有配上的真值计入 background 行,没有配上的预测计入 background 列。分割配对的是检测框,不是掩码。设了 classes= 时,其他类别的真值会被排除。
COCO 的 crowd 区域(iscrowd=1)遵循 mAP 所用的规则。一个没有被其类别的任何真值匹配到的预测,如果至少有一半落在同类别的 crowd 区域内(交集除以预测自身的面积),就会被忽略而不是被计数。box.image_metrics 和 visualize=True 的图像使用同样的阈值和同样的 crowd 规则。YOLO 格式的标注没有 crowd 区域。
对于分类,每张图像只计一次,按它的 top-1 类别对照它的标签,没有 background。
| 方法 | 返回 |
|---|---|
summary(normalize=False) | 每个预测类别一个字典,{"Predicted": name, <true class>: value};设 normalize 时是占每个真实类别的比例 |
to_csv()、to_json() | 以 CSV 或 JSON 文本形式给出的汇总 |
to_df() | 以 Polars DataFrame 形式给出的汇总;需要 pip install polars |
tp_fp() | 逐类别的真正例(对角线)和假正例(预测行的其余部分) |
nonzero() | 已填充的单元格,以对齐的 (predicted, true, count) 数组给出 |
class_accuracy() | 逐类别,其实例被判为该类别的占比;对于检测,只统计找到的目标 |
plot(normalize=True, save_dir="") | 写出 confusion_matrix_normalized.png,用 normalize=False 则写出 confusion_matrix.png,并返回路径 |
超过 2,000 个类别时,不会构建稠密的分类矩阵:matrix、summary()、to_*() 导出和 plot() 会抛错,而 tp_fp()、nonzero() 和 class_accuracy() 仍然可用。
这个类是 libreyolo.validation.ConfusionMatrix,构造方式为 ConfusionMatrix(nc, iou_thres=0.5, conf_thres=0.25, *, names=None, task="detect"),其中 task 是 "detect" 或 "classify"。
逐类别的查准率、查全率和 F1
检测和分割的结果带有每个类别在各个置信度阈值上的查准率、查全率和 F1,在 IoU 0.5 下匹配。
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt")metrics = model.val(data="coco8.yaml") box = metrics.boxprint(box.px.shape, box.f1_curve.shape) # (1000,) 和 (classes, 1000)for index, f1 in zip(box.ap_class_index, box.f1): print(model.names[int(index)], round(float(f1), 3))box.px 保存 1000 个置信度,在 [0, 1] 上均匀分布。box.p_curve、box.r_curve 和 box.f1_curve 是 (classes, 1000) 的数组,每一行对应 box.ap_class_index 的一个条目,即在这个划分里有真值的类别索引,按升序排列。box.p、box.r 和 box.f1 是每个类别在使这些类别平均 F1 最大的置信度上的值。分割时它们全部由检测框算出。运行没有产生曲线时,它们是空数组。
逐图像检测框指标
检测和分割的结果保持字典兼容,同时提供 results.box.image_metrics。每个文件名对应 precision、recall、f1、tp、fp 和 fn,即使关闭可视化,也使用可视化的匹配规则。这里分割统计的是检测框。文件基名重复时,第一次出现后改用完整路径。分母为零时返回 0.0。这些记录不会在分布式 rank 之间汇总。
速度键
每个验证器都会加上计时:
speed/preprocess_ms speed/inference_ms speed/postprocess_msspeed/total_ms speed/total_s speed/images_seen这些是整轮运行平均下来的每张图像毫秒数。它们描述的是你运行时的机器和设置,所以从 里面取出来的数字,只有连同硬件、批大小和数值精度一起报告才有意义。
评估后端
目标检测和分割的指标是通过一个 COCO 评估器算出来的,默认的
faster_coco_eval=True 会在装了 faster-coco-eval 包时选用 C++ 后端。没装的
时候,这次运行会回退到 pycocotools,每个进程警告一次:
faster_coco_eval requested but not installed; falling back to pycocotools.Install with: pip install faster-coco-eval实际跑的是哪个后端会以 last_eval_backend 记录在模型上,CLI 对检测类任务会在输出
里报出来。设置 LIBREYOLO_FASTER_COCO_EVAL 可以从环境覆盖配置里的值。
iou_thresholds 只在 OBB 这条路径上生效。COCO 那条路径按它自己固定的 0.50 到
0.95 扫描来评估,忽略这个值。
验证损失
默认情况下验证只报告精度。val_loss=True 会在验证批次上同时计算这个家族的训练
目标。
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt")model.train(data="coco8.yaml", epochs=10, val_loss=True)它会输出 metrics/loss,外加每一项对应的一个 metrics/loss/<component>,加权
方式和训练时完全一致,所以各分量加起来正好是总和。经过 logger 时它们显示为
val/loss 和 val/loss/<component>,而 libreyolo monitor 会把 metrics/loss
和 train/loss 叠在一起画。
各分量是每个家族自己的:
| 任务 | 家族 | 分量 |
|---|---|---|
| detect | yolo9、yolo9_p2、yolo9_e2e | box、cls、dfl |
| detect | yolonas | cls、iou、dfl |
| detect | rfdetr | ce、bbox、giou |
| detect | rtdetr、rtdetrv2 | vfl、bbox、giou |
| detect | dfine | vfl、bbox、giou、fgl、ddf |
| detect | domedetr | vfl、bbox、giou、fgl、ddf、defe_density、defe_reg |
| detect | deim、deimv2、rtdetrv4、ec | mal、bbox、giou、fgl、ddf |
| detect | rtmdet | cls、bbox |
| detect | picodet | cls、bbox、dfl |
| detect | yolox | iou、obj、cls、l1 |
| detect | yolo7 | iou、obj、cls |
| point | fomo | ce |
| classify | resnet、convnext、mobilenetv4、efficientnetv2 | ce |
| semantic | segformer、lingbotvision、dinov2 | sem |
| restore | nafnet | restore |
它默认关闭,因为标签分配(target assignment)会给验证增加时间和内存开销。验证器
复用已经为精度指标产生的模型输出,而不是再跑一次前向,它在评估模型或 EMA 模型上以
no_grad 运行,多卡训练下则在 rank 0 上本地计算,不做任何集合通信。最佳检查点的
选择仍然按精度指标来。
有三件事它是故意不做的。它从不包含对比去噪(contrastive denoising)项,因为那些项 在前向时需要真值(ground truth),而验证的前向拿不到真值。它报告的是评估模式下的 模型,所以当一个家族的训练前向和评估前向确实不同时,比如在 BatchNorm 统计量或者 随机深度(stochastic depth)上,数字反映的是评估模式;这正是想要的那种对比。还有, 家族没有为某个任务实现它时,会在准备阶段抛出一个配置错误,而不是悄悄跳过:
val_loss=True currently supports RF-DETR detection only; segment, pose, OBB,classify, and semantic tasks are not supportedFOMO 是那个什么都不改变的例外:它的验证器一直都在算这个损失,val_loss=True 只
影响它发布在哪些键下。
带数据增强的验证和验证损失不能一起用,两个都要就会抛错。
一次验证会写出的文件
val() 总会把 config.yaml 写进它的保存目录,没给 save_dir 时这个目录默认是
runs/val/<model>_<size>_<timestamp>。project、name 和 exist_ok 按训练的方式选择这个
目录:project/name,除非 exist_ok=True,否则加上递增后缀。
from libreyolo import LibreYOLO model = LibreYOLO("LibreYOLO9s.pt")model.val(data="coco8.yaml", save_json=True, save_dir="runs/val/exp")save_json=True 对目标检测写出 predictions.json,对分割写出
predictions_bbox.json 和 predictions_masks.json。OBB 不支持它,而且会明说。
save_plots=True,或者它的别名 plots=True,会写进一个 plots/ 子目录,并且需要
matplotlib,pip install "libreyolo[plots]" 会装上它。目标检测会拿到 box_metrics.png、
各类别的 AP 和查全率图表、查准率-查全率曲线和置信度曲线、f1_conf_box.png 里的逐类别
F1 曲线、confusion_matrix.png 里以计数表示的混淆矩阵、confusion_matrix_normalized.png
里以各真实类别占比表示的混淆矩阵,装了 OpenCV 时还有标注过的样本图像。分割会加上指标图表、
逐类别图表以及查准率-查全率曲线和置信度曲线在掩码那侧的副本;F1 曲线和混淆矩阵仍然是检测框层面的。
姿态有自己的一套指标和曲线。分类会写出这两个混淆矩阵文件,超过 2,000 个类别时除外。其他验证器没有实现图表;语义分割、全景分割、深度估计、法向估计、
边缘检测、图像复原、抠图、OCR、OBB 和点检测在那里什么都不写。画图失败只会警告,
绝不会中止这次运行。
visualize=True 为检测和分割写出考虑类别的检测框 TP/FP/FN 图像,为 ImageFolder 分类写出标签与 top-1 的对照图像,分别放入 visualize/errors/ 和 visualize/correct/。匹配使用 IoU 0.5 和置信度 max(0.25, conf)。默认值为 visualize=False、show_labels=True 和 show_conf=True。不支持的任务以及 V-JEPA 2 视频片段验证会拒绝可视化。
plot_samples=8 限制单独的样例图数量;0 禁用,-1 保留全部图像。这不会改变指标或可视化输出。
训练过程中的验证
训练每隔 eval_interval 轮,并且总在最后一轮之后,在数据集的 val 划分上验证一次,
它产生的指标就是驱动 best.pt 选择、patience 提前停止以及每个 logger 里 val/
各键的东西。EMA 打开时,验证跑在 EMA 权重上。它的文件写入运行目录里的 val/ 目录。
val=False 会关闭训练中的验证,最后一轮也包括在内。
eval_interval、patience 和 save_plots 见 超参数,
数字最后去了哪里见 实验 logger。
相关
- 数据集,验证器读取的划分键和格式都在那里。