尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

准确率、精度、召回率、mAP一文讲透:原理、应用与PyTorch实践

准确率、精度、召回率、mAP一文讲透:原理、应用与PyTorch实践 别看这几个名词在简历上出现率极高真正到了面试或者实际调模型的时候能把准确率、精度、召回率讲清楚的人并不多。包括我自己带过的实习生初看资料都懂一上手处理一个类别不均衡的样本集就乱了拿着 Accuracy 跟我说模型效果很好结果跑上线全露馅。这个事儿其实不难难的是把这些指标放到真实场景里去理解。比如做视频动作分类、训练目标检测模型、跑量化压缩在不同阶段你需要盯的指标完全不一样。这篇就用最直白的大白话把准确率Accuracy、精度Precision、召回率Recall、mAP 这几个概念彻底剥开。不仅讲公式更讲它们各自在什么时候用、为什么用、怎么算才不出错。很多同学会问“精度和准确率有什么不一样不是一个意思吗”这就是典型的没建立混淆矩阵的直觉。别急我们从最基础的说起后面我会直接放一段能跑的 Python 代码手把手带你在 PyTorch 训练流程里把这些指标算出来。1. 四个指标先搞清楚它们各自回答什么问题1.1 为什么总是把准确率和精度搞混中文翻译把 Accuracy 叫准确率把 Precision 叫精度这两个词在日常对话里太像了导致很多人默认它们是一回事。但在机器学习里它们回答的是完全不同的两个问题。Accuracy 回答的是你预测的所有样本里有多少个是对的Precision 回答的是你预测为正类的那些样本里有多少个真的是正类一个是“整体对不对”一个是“说对了多少”的纯度。同一个模型这两个数值可能差距非常大。我在跑 UCF101 视频动作分类的时候见过训练集准确率 92% 但精度只有 47% 的情况因为背景帧和相似动作的类别太多模型经常把 A 动作误判成 B虽然整个验证集上错误的数量不多但一旦判成某个具体动作错的概率很大。为了理清这个概念必须先引入混淆矩阵。在二分类问题里假设正类是我们要找的东西负类是其余所有东西那么会得到四个数字TPTrue Positive实际是正类预测也是正类。TNTrue Negative实际是负类预测也是负类。FPFalse Positive实际是负类但预测成了正类。FNFalse Negative实际是正类但预测成了负类。这四个数字构成了所有分类问题的地基。接下来所有指标都是从这四个数字里算出来的。1.2 一个小例子快速建立直觉我给你一个生活场景假设你做了一个垃圾短信过滤器手机里一共收到 100 条短信其中 20 条是垃圾短信80 条是正常短信。你的过滤器把其中 15 条判定为垃圾短信而且这 15 条确实都是垃圾短信另外的 5 条垃圾短信被漏掉了。在这个例子里Accuracy (15 75) / 100 90%没错吧100 条里判对了 90 条。Precision 15 / 15 100%凡是标记为垃圾短信的全部是对的非常精准。Recall 15 / 20 75%实际 20 条垃圾短信只拦住了 15 条漏掉了 5 条。你看一个模型可以同时做到高精度、中召回而准确率看起来也很高。但反过来这个过滤器如果直接把所有短信都标记为正常Accuracy 立刻变成 80%但 Recall 变成 0%。如果只看准确率你会以为性能只是从 90% 降到了 80%实际上模型已经完全不能用。这个例子就是理解所有分类指标的最佳起点。2. 准确率 Accuracy最直观的指标但也是最容易骗人的2.1 公式与混淆矩阵准确率的公式没有任何悬念Accuracy (TP TN) / (TP TN FP FN)用大白话说就是所有样本里预测正确的比例。它简单、直观、容易向非技术同事解释。在 sklearn 里一行代码就能算出来在 PyTorch 里也就是一个(pred label).sum() / total的事。正因为简单所以容易被误用。我在实际项目中见过有人用 Accuracy 监控一个“用户是否会点击广告”的模型样本里点击率只有 3%。也就是说模型哪怕什么都不学永远输出“不点击”就能拿到 97% 的准确率。然后这个模型上线两周业务方困惑为什么点击率毫无提升。原因就是类别严重不均衡的情况下Accuracy 完全失去参考价值。所以Accuracy 本身没问题是使用场景选错了。2.2 案例当准确率很高模型却可能在“摆烂”在深度学习里有一个常见的陷阱训练集和验证集都显示 Accuracy 在稳步上升看起来模型在正常收敛。但实际上你检查每一类的召回率会发现模型把所有难分类的样本全部推给了“背景类”或者“其他类”。尤其在视频动作分类这种多分类场景下有些动作类别出现频率低模型干脆不学它们的特征输出结果永远是高频类别最后整体 Accuracy 依然很高因为高频类别主导了整个指标。这个现象叫“类别不平衡下的捷径学习”。模型发现只要预测成大多数类损失函数的值就不会太大于是走了一条捷径。要避免这个问题唯一的办法是不要单独盯着 Accuracy 看必须同时观察每个类别的 Precision 和 Recall。举个例子在我跑 UCF101 的过程中“打篮球”和“投篮”这两个类别的部分帧非常像。初期模型为了冲刺 Accuracy把很多“投篮”的动作直接归为“打篮球”因为样本量更大。整体 Accuracy 确实挺高但单独看“投篮”类别的 Recall 只有不到 20%。后来我做了两件事一是把相似类别的损失权重调高二是针对这些类别单独计算 F1 分数来监控情况才好转。2.3 什么时候用准确率合适Accuracy 也并不是没有用武之地。如果你的数据集类别相对均衡并且每一个类别的重要程度差不多Accuracy 完全可以作为主要监控指标。比如手写数字识别 MNIST10 个类别样本量差不多78% 和 92% 的准确率对比就有明确的意义。再比如二分类的“信用卡交易是否欺诈”虽然欺诈样本极少但如果你把欺诈判成正常交易和把正常交易判成欺诈代价都非常高这时候也可以综合使用 Accuracy、Precision、Recall 一起看而不是只看某个单一指标。一句话总结Accuracy 适合作总体参考但不适合作为唯一追求的目标。尤其是当你处理的数据类别有偏斜时务必把它和其他指标放到一起观察。3. 精确率 Precision 与召回率 Recall一对互相对抗的指标3.1 精确率的含义与计算精确率的公式是Precision TP / (TP FP)它衡量的是你预测出来的所有正类样本里到底有多少是准确的。换句大白话就是“你说他是对的那到底对不对”。精确率高说明你的模型“很少误报”。做垃圾邮件过滤时如果把正常邮件误杀用户体验极差这时候你希望精度高一些宁可漏掉一部分垃圾邮件也不能把重要邮件扔进垃圾箱。做电商平台的风控系统也是一样系统判定一个账号是机器注册如果误封了真人账号客户投诉会瞬间淹没客服所以风控模型对 Precision 的要求极高。但这里有一个很容易被忽略的细节Precision 受到“判定阈值”的直接影响。在二分类中模型一般输出一个概率值比如 0.7你设定阈值是 0.5 还是 0.9得到的 Precision 完全不同。阈值越高预测为正类的样本越少但留下的往往都是模型非常有把握的Precision 自然就高。3.2 召回率的含义与计算召回率的公式是Recall TP / (TP FN)它衡量的是所有真正的正类样本里你成功找出来了多少。换句大白话就是“该找的到底找回来没有”。召回率高说明你的模型“很少漏报”。医疗领域里癌症筛查宁可误判一部分正常人也最好不要漏掉任何一个真正的病人所以这类场景对 Recall 的要求极高。视频动作分类也一样比如“摔倒”这个动作哪怕只有一次没有检出来都可能造成严重后果这时候 Recall 一定是第一优先级。但高召回率通常意味着模型会疯狂把边界样本判成正类导致误报率上升。你为了抓到所有真正的目标把很多不是目标的也圈进来了。这就像出海捕鱼你把网眼做小确实能捞到更多鱼但水草和垃圾也一起捞上来了。3.3 P-R 的拉锯战与 F1 分数Precision 和 Recall 就像坐跷跷板你很难同时让两个都高。要提高 Precision你就会损失 Recall要提高 RecallPrecision 往往会掉。这是因为它俩一个惩罚误报FP一个惩罚漏报FN而一个模型在给定能力下TP 的总量是有上限的。为了平衡两者最常用的就是 F1 分数F1 2 * Precision * Recall / (Precision Recall)F1 是 Precision 和 Recall 的调和平均数。注意是调和平均数不是算术平均数。调和平均数对低值更敏感意味着只有两个指标都高的时候F1 才会高。这个特性很适合用来做模型的综合调优目标。在实际项目中我一般会同时看三个值Precision、Recall、F1。如果 F1 在提升通常说明模型在“准”和“全”之间找到了更好的平衡。如果 F1 长期上不去那可能要回到数据本身看看是不是标注有误、类别定义模糊、或者正负样本比例差距过大这些问题靠调参是解决不了的。4. mAP目标检测与实例分割的“总冠军”指标4.1 从 AP 到 mAP这个指标到底在算什么前面说的几个指标都是针对“分类”任务的也就是给一个图片、一段视频、一行数据输出一个类别标签。但在目标检测任务里模型不仅要说出“画面里有什么类别”还要用边界框标出“物体在哪个位置”。这时候光看分类指标就不够了因为边界框预测得准不准也是评估模型能力的重要部分。mAP 的全称是 mean Average Precision翻译过来是“各类别平均精度的再平均”。它不是一个单一自然公式而是一套计算流程。核心思想是对每个类别画出它的 Precision-Recall 曲线然后计算曲线下的面积也就是 Average PrecisionAP最后把所有类别的 AP 取平均得到 mAP。这个指标在目标检测领域几乎成了通用货币。你训练 YOLO、Faster R-CNN、SSD 或者更前沿的 DETR论文里报告的 mAP 就是这套流程算出来的。比如 COCO 数据集上经常看到 mAP50、mAP75、mAP[.5:.95]这些数字都是基于这套逻辑。4.2 IoU 在 mAP 计算里扮演的角色在检测任务里判断一个框“预测对了”是有标准的光说“你画了一个框我也画了一个框”不行两个框不可能完全重合。实际上我们用的是 IoUIntersection over Union也叫交并比计算的是真实边界框与预测边界框的交集面积和并集面积的比例。IoU 交集面积 / 并集面积如果预测框和真实框完全重叠IoU 1。如果完全不重叠IoU 0。计算 mAP 时通常设定一个阈值比如 IoU 0.5。只要预测框与真实框的 IoU 大于 0.5就认为这个预测是正类TP如果同一个真实框被多个预测框命中只取置信度最高的那一个算正类其他算负类。这就是非极大值抑制NMS在评估环节里的作用。这里有个很多初学者踩过的坑预测框和真实框都画对了类别但位置跑偏一大截IoU 只有 0.3结果被算成 FP。模型分类能力很强但定位能力差mAP 一样上不去。反过来如果边界框很准但类别经常搞错mAP 同样会很难看。mAP 是把分类和定位两个能力综合评估的指标。4.3 COCO 和 PASCAL VOC 的 mAP 差异不同数据集评估协议计算 mAP 的方式有一定差异很多人在复现论文时没注意这一点导致自己算出来的数字和论文对不上。PASCAL VOC 的经典做法是将所有检测结果按置信度从高到低排序然后调整阈值时用固定 IoU 阈值 0.5 计算 Precision-Recall 曲线再对曲线做平滑处理单调递减最后计算面积。这就是 mAP50。COCO 的做法更严格它计算从 IoU 0.5 到 0.95 每间隔 0.05 共 10 个阈值下的 AP再取平均记为 mAP[.5:.95]。这个指标对边界框的定位精度要求非常高模型哪怕框偏了一点在高 IoU 阈值下也被判为错。所以 COCO 的 mAP 数值普遍比 VOC 算法低一大截两个数字不能直接横向对比。还有一个差异是COCO 对面积很小、中等、很大的物体分别计算 AP还有一个专门的 ARAverage Recall指标。做小目标检测时一定要关注AP_small这一项。我在实际任务里就遇到过整体 mAP 看起来还行但AP_small几乎为 0说明模型对远处的、小尺寸的目标完全没有感知能力这在自动驾驶、无人机视角任务里是致命的。5. 用 Python 实操手把手计算这些指标5.1 二分类场景的 Accuracy/Precision/Recall 计算光讲公式不够直接写代码。下面的代码模拟了一个二分类任务。假设我们有 5 个样本的真实标签y_true模型输出概率值y_scores我选了 0.5 作为判定阈值。import numpy as np y_true np.array([1, 0, 1, 1, 0]) y_scores np.array([0.9, 0.4, 0.75, 0.65, 0.2]) threshold 0.5 y_pred (y_scores threshold).astype(int) print(预测结果:, y_pred) TP np.sum((y_pred 1) (y_true 1)) TN np.sum((y_pred 0) (y_true 0)) FP np.sum((y_pred 1) (y_true 0)) FN np.sum((y_pred 0) (y_true 1)) accuracy (TP TN) / (TP TN FP FN) precision TP / (TP FP) recall TP / (TP FN) f1 2 * precision * recall / (precision recall) print(fAccuracy : {accuracy:.4f}) print(fPrecision: {precision:.4f}) print(fRecall : {recall:.4f}) print(fF1 : {f1:.4f})输出结果如下预测结果: [1 0 1 1 0] Accuracy : 1.0000 Precision: 1.0000 Recall : 1.0000 F1 : 1.0000这个例子太理想了。你可以把threshold改成 0.7 试试第二个样本的预测结果会变成 0但第四个样本真实标签是 1预测也是 1所以结果还不变。可以把第四个样本的分数改成 0.55阈值 0.7 时它就会被判为 0这时 Recall 就会掉到 0.667Precision 保持不变。这种手动折腾能帮你快速建立阈值如何影响指标的感觉。5.2 在 PyTorch 训练中实时追踪这些指标实际训练模型时不能等整个 epoch 跑完了再用 sklearn 算那样没法实时监控。更常用的做法是在每个 batch 内累积 TP、TN、FP、FNepoch 结束后统一计算。下面是我在分类训练里经常用的一个简单实现。假设模型输出是 logits真实标签是labelsclass MetricsTracker: def __init__(self): self.TP 0 self.TN 0 self.FP 0 self.FN 0 def update(self, logits, labels): preds logits.argmax(dim1) for pred, label in zip(preds, labels): if label 1: if pred 1: self.TP 1 else: self.FN 1 else: if pred 1: self.FP 1 else: self.TN 1 def compute(self): eps 1e-6 accuracy (self.TP self.TN) / (self.TP self.TN self.FP self.FN eps) precision self.TP / (self.TP self.FP eps) recall self.TP / (self.TP self.FN eps) f1 2 * precision * recall / (precision recall eps) return { accuracy: accuracy, precision: precision, recall: recall, f1: f1, }这里加了eps是为了防止除零。如果模型一整个 epoch 都没有预测出正类TP FP 0Precision 会出现除零错误。加一个小 epsilon 后结果会变成 0不影响我们判断模型是否已经“放弃了学习”。在训练循环里调用的方式很简单tracker MetricsTracker() for batch in dataloader: inputs, labels batch outputs model(inputs) tracker.update(outputs, labels) metrics tracker.compute() print(metrics)这个实现没有考虑 GPU 加速和三分类以上问题但对监控训练过程已经完全够用。多分类的时候你需要把标签转换成 one-hot 或用sklearn.metrics.classification_report来生成更完整的报告那里每个类别都有独立的 Precision、Recall、F1。5.3 用 scikit-learn 快速验证计算逻辑很多时候我们手写的指标计算容易出细节错误比如把 axes 弄混了或者对每个类别的计算逻辑不一致。为了保险起见我通常会再用 scikit-learn 算一遍对一下结果。from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score y_true [1, 0, 1, 1, 0] y_pred [1, 0, 1, 1, 0] print(Accuracy :, accuracy_score(y_true, y_pred)) print(Precision:, precision_score(y_true, y_pred)) print(Recall :, recall_score(y_true, y_pred)) print(F1 :, f1_score(y_true, y_pred))注意二分类时precision_score默认pos_label1如果你的正类不是 1记得指定这个参数。多分类时默认做的是 macro 平均即每个类别算完指标后取平均这与 micro 平均得到的数值有非常大的差异。micro 平均在类别不平衡时基本等于 Accuracy而 macro 平均把每一个类别都看得同等重要更接近“模型在所有类别上的平均表现”。6. 常见问题与避坑指南现实项目里的真实经历6.1 为什么我的召回率低得离谱这是我被身边人问过最多的问题之一。模型整体 Accuracy 95%但某个关键类别 Recall 只有 35%这通常不是模型结构的问题而是数据的问题。排查顺序一般是先看这个类别的样本量是不是太少。如果这个类别只有 50 个样本而其他类别有 5000 个模型大概率学不到这个类别的有效特征。再看类别之间是否有重叠或者歧义。在动作分类里“挥拳”和“挥手”如果标注标准不统一模型就会混乱。标注人员认为“挥拳”的样本模型学到的特征却更接近“挥手”。最后看验证集划分是否随机。如果验证集里这个类别的样本和训练集来自同一段视频时空临近性会导致盲目的高指标如果来自于完全不同的场景指标又可能低到离谱。针对第一点最简单有效的方法是重采样或者调整损失函数的类别权重。PyTorch 里可以直接给CrossEntropyLoss传weight参数把少样本类别的权重调高让模型误分类少数类时损失更大。但注意这个操作不是万能的权重太大容易过拟合我一般从 1.5 倍开始调配合验证集观察效果。6.2 类别不平衡时只看 Accuracy 会怎样直接给一个反面教训。我曾经做一个工业质检项目产品缺陷率只有 2%训练数据里 98% 都是良品。刚开始训练时为了省事直接拿 Accuracy 当监控指标模型收敛到 98% 之后我怎么调结构都上不去。后来意识到模型根本什么都没学到只是把所有样本都预测成了良品。这时候你有两条路第一条路是换指标。使用 Precision、Recall、F1 甚至 PR 曲线下方的面积来监控曲线下方的面积对类别分布的敏感度更高能够反映模型在正类上的真实表现。第二条路是收集更多正类样本或者对正类做数据增强。在工业场景里可以通过旋转、平移、加噪声、调亮度等方式把缺陷样本扩增。但我必须提醒一句数据增强要控制幅度如果增强出来的样本和真实缺陷差异太大模型学到的可能是增强方式本身而不是缺陷的本质特征。6.3 mAP 计算中容易出错的地方mAP 的计算比分类指标复杂得多最常犯的错有三个。第一个是在评估阶段忘了做 NMS。模型在推理时往往会输出大量重叠的预测框如果你直接用这些框计算 mAP同一个物体会被重复计成多个 TP导致指标虚高。正确的做法是在评估阶段也执行 NMS保留置信度最高的框抑制掉重叠度高的其他框。第二个是处理“一个真实框被多个预测框命中”时的方式。正确逻辑是按置信度从高到低排序对于某一个真实框只有第一个超过 IoU 阈值的预测框算作 TP其余的预测框即使 IoU 超过阈值也算作 FP。很多人忽略了这一点直接把所有超过阈值的预测框都算成 TPmAP 直接爆表。第三个是把类别的 AP 和整体 mAP 搞混。mAP 是所有类别的 AP 的平均值但在类别极不平衡的情况下平均会被高频类别主导。比如 UCF101 有 101 个动作类别如果模型只把高频的“走路”学得很好其他 100 类的 AP 都很低mAP 依然难看得不行。看指标时一定要把每个类别的 AP 拉出来逐个检查别只看一个综合值。6.4 阈值怎么选Precision-Recall 曲线与 F1-beta很多同学把模型训练完就直接对验证集用 0.5 的阈值这其实很粗暴。0.5 只是一个默认值不是最优点。每个业务场景对误报和漏报的敏感度不一样阈值应该根据场景需求去选。比如在用 PyTorch 跑二分类时我先拿到验证集的所有概率输出probs torch.sigmoid(model_outputs).cpu().numpy() labels val_labels.cpu().numpy() thresholds np.arange(0.1, 0.9, 0.05) best_thresh 0.5 best_score 0.0 for thresh in thresholds: preds (probs thresh).astype(int) precision precision_score(labels, preds) recall recall_score(labels, preds) # 这里根据业务需求调整平衡权重比如更看重召回 score 2 * precision * recall / (precision recall) if score best_score: best_score score best_thresh thresh print(Best threshold:, best_thresh)这个例子用的是 F1 分数作为选择阈值的依据。如果你的业务对漏报更敏感比如异常检测可以改用 F2 分数它给召回率更高的权重。同理如果你对误报更敏感就调成加权方案让精度占主要地位。阈值本质上不是一个超参数而是“业务在召回和精确之间做的取舍”。至于 Precision-Recall 曲线它是把从最高置信度到最低置信度的所有分类结果都画出来展示在不同判定标准下两个指标的变化关系。可视化这个曲线非常直观。曲线右上方越凸说明模型效果越好。如果一个模型在每个阈值下的 Precision 和另一个模型一样高但 Recall 都更高这个模型就严格优于另一个。6.5 量化模型后的指标变化另一个真实场景是模型量化。在做 RKNN 这类端侧推理时模型往往需要从 FP32 转成 INT8量化后特征表达能力受限模型的指标通常都会下降。有人发现量化后 Accuracy 只是略微下降但某个类别的 Recall 掉了 20% 以上这种情况非常常见因为少数类的特征在 INT8 量化后更容易被压缩掉。我的建议是量化前不要只看整体指标把每个类别的 Precision、Recall 存档。量化后再做一次逐类对比确定哪些类别的退化最严重。如果退化集中在小物体或者低纹理类别上通常只能选择混合量化把这些层保留为 FP16 或者 FP32避免一刀切量化带来的精度崩盘。这类问题光调评估阈值解决不了必须在模型结构层面想办法。写了这么多最后分享一点我的实际感受指标这个东西不是用来在报告里“好看”的而是用来帮你发现问题、指导调优方向的。我见过太多人花两个星期调结构只为了把 Accuracy 提高 0.5 个百分点却不看一眼混淆矩阵和 PR 曲线。如果真正按照这个思路去排查也许半天就能发现是少数类样本被严重覆盖跟模型结构一点关系都没有。我自己现在的习惯是训练初期只看 loss 和 Accuracy确保模型在正常收敛中期开始看每个类别的 Precision、Recall 和 F1特别关注样本量少的类别到了检测类任务还会保存每个类别的 AP 曲线逐类比较。每次实验前把指标计算代码固化下来放到一个统一的工具模块里不要每次复制粘贴到新的 notebook 里。这样省下的时间远比写这个工具模块花的时间多得多。如果现在有人再问我准确率、精度、召回率、mAP 的区别我会建议他别急着背公式。先拿一个垃圾短信过滤器的例子亲手算一遍混淆矩阵里的 TP、FP、FN、TN然后找一份不平衡数据集训练一个简单的二分类模型观察不同阈值下指标怎么变化。做完这套流程这些概念就会变成你自己的直觉了。
返回列表