资讯动态

ROC与PR曲线选型指南:AUC、类别不均衡与阈值评估实战

发布时间:2026/10/26 14:12:59 来源:尧图企业网站定制
模型评估这件事说起来简单做起来全是坑。我带过几个新人几乎每个人第一次交上来的评估报告都是 accuracy 0.97然后兴高采烈地说模型可以上线了结果一查混淆矩阵正样本一共才占百分之三模型把所有样本都判成负类。ROC 曲线和 PR 曲线这两个东西几乎每个做机器学习的人都画过但真正能把它们讲清楚、用对场景的人并不多。这篇内容就从我实际做过的几个项目出发把模型评估的指标体系、ROC 与 AUC 的几何含义和概率含义、PR 曲线在类别极端不均衡下的不可替代性以及阈值选取、多分类平均、评估流水线搭建这些实操细节一次性讲透。不管你是刚入门还在调 sklearn 的 precision_recall_curve还是已经做了几年模型但总觉得评估报告差点意思应该都能从里面找到点能直接抄作业的东西。1. 评估体系整体设计与指标选型思路1.1 为什么准确率是最容易骗人的指标先讲一个我印象最深的例子。早些年参与一个工业质检的缺陷检测项目产线上不良率大概千分之五也就是一千个产品里只有五个有缺陷。团队里有人图省事直接拿准确率当唯一指标训练出来的模型在验证集上准确率 99.5%看着漂亮得不行。可实际部署到产线上一跑问题立刻暴露模型把所有产品都判成良品那五个缺陷品一个都没抓出来。原因很简单负样本占了 99.5%模型只要无脑输出负类就能拿到 99.5% 的准确率而这个数字跟模型有没有学到东西毫无关系。这就是准确率最大的问题它把正类和负类的错误同等对待完全无视样本分布。在类别均衡、两类错误代价相近的场景下准确率还能凑合用一旦类别失衡或者两类错误的业务代价天差地别准确率就会变成一个彻头彻尾的安慰剂。更隐蔽的是它还会掩盖模型退化成常数预测器的事实。所以我现在的习惯是拿到任何一个分类任务先看正负样本比例再看两类错误的代价最后才决定用什么指标。顺序反了后面的评估基本都是自欺欺人。1.2 ROC 与 PR 的适用边界ROC 曲线的横轴是假正率 FPR分母是负样本总数纵轴是真正率 TPR分母是正样本总数。这个定义决定了它在类别相对均衡时表现稳定因为 FPR 的分母足够大曲线不会因为个别负样本的误判而剧烈抖动。ROC 还有一个很好的性质它对正负样本的先验比例不敏感同一个模型在不同数据集上得到的 ROC 曲线形状基本一致所以适合做跨数据集的模型排序比较。但到了类别极端不均衡的场景ROC 的温柔就变成了盲区。举个例子正样本只有一百个负样本有十万个。模型多误判一千个负样本FPR 只从 0.001 涨到 0.011肉眼几乎看不出曲线变化可这一千个误判在业务上可能意味着上千次无效告警。PR 曲线的纵轴精确率 Precision 分母是“被判为正类的样本数”横轴召回率 Recall 分母是“真正的正样本数”它直接盯着正类的命中质量负样本再多也不会把指标稀释掉。所以在风控、医疗筛查、缺陷检测这类正样本稀缺、误报代价高的场景里我基本只看 PR 曲线和 APROC 只作为辅助参考。1.3 指标选型的决策路径选指标没有万能公式但可以按几个维度快速收敛。我一般按下面这张表来判断实际项目里基本能覆盖八九成的情况。场景特征正负比例更该看的指标原因类别均衡、两类错误代价接近接近 1:1ROC、AUC、准确率排序能力和整体判别力都能被公平反映正样本稀缺、误报代价高低于 1:10PR、AP、PrecisionKFPR 被大量负样本稀释ROC 过于乐观需要固定阈值上线任意F1、召回率、精确率、成本矩阵业务决策依赖单点阈值曲线只是参考多分类、类别不均衡任意宏平均 F1、加权平均、每类 AP微平均会被大类主导宏平均更能暴露小类问题需要输出概率做排序任意Brier Score、校准曲线、AUC概率校准质量直接影响下游排序这张表的用法是先看比例再看业务对误报和漏报的容忍度最后看模型输出要不要当概率用。三步走完指标基本就定了。这里有个容易被忽略的点AUC 和 AP 都是阈值无关的排序指标它们衡量的是模型把正样本排在负样本前面的能力不等于某个固定阈值下的业务效果。评估报告里如果只有 AUC没有固定阈值下的精确率、召回率那这份报告是不完整的上线时还得再补一轮阈值调优。2. 混淆矩阵与阈值所有曲线的源头2.1 四个格子的业务代价ROC 和 PR 的底层都是混淆矩阵四个格子分别是 TP、FP、FN、TN。很多人背得滚瓜烂熟但一到业务里就忘了问一句这四个格子各自的代价是多少在医疗筛查里FN 是漏诊代价可能是病情延误FP 是误诊代价是一次复查和几天焦虑。这两者的量级完全不在一个数量级FN 的代价通常远高于 FP所以阈值要往低里调宁可多报也别漏。在垃圾内容拦截里FP 是把正常内容误杀用户投诉会直接上来FN 是漏放一条后果相对可控。这时候阈值反而要往高里调。我自己踩过的一个坑是早期做评估时只看 F1觉得 F1 兼顾了精确率和召回率很平衡。后来在一个误报代价极高的场景里F1 最优的阈值上线后每天产生几千条无效告警运营团队直接找上门。回头复盘才发现F1 隐含假设 FP 和 FN 代价相同而这个假设在业务里根本不成立。从那以后我在评估阶段就会先把代价矩阵画出来再决定阈值往哪边偏。2.2 阈值滑动如何生成曲线曲线的本质是阈值从高到低滑动时混淆矩阵四个格子的连续变化。拿十个样本举例真实标签是[1,1,0,1,0,0,1,0,0,1]模型打分是[0.9,0.8,0.75,0.7,0.6,0.55,0.5,0.4,0.3,0.2]。阈值取 0.9 时只有第一个样本被判为正TP1、FP0、FN3、TN6此时 TPR0.25、FPR0。阈值降到 0.75第三个样本真实为 0也被判为正TP 还是 1FP 变成 1FPR 升到 1/6TPR 不变。阈值继续降到 0.5第七个样本真实为 1进入正类TP 变成 3TPR 升到 0.75。把这些点连起来就是 ROC 曲线。PR 曲线同理只是纵轴换成了 PrecisionTP/(TPFP)横轴换成了 RecallTP/(TPFN)。这里有个细节值得注意阈值每滑动一次四个格子只会朝一个方向变化TP 和 FP 单调不减FN 和 TN 单调不增。所以 ROC 曲线一定是单调向右上走的PR 曲线则可能出现锯齿因为 Precision 的分子 TP 增加的同时分母 TPFP 也在增加比值可能先升后降。这个差异后面还会细讲。2.3 手写一遍曲线生成过程不依赖 sklearn 手写一遍能帮你把所有细节钉死。下面这段代码把阈值从正无穷滑到负无穷每一步都重新算混淆矩阵最后返回 FPR、TPR 和阈值序列。import numpy as np def manual_roc(y_true, y_score): y_true np.asarray(y_true) y_score np.asarray(y_score) thresholds np.sort(np.unique(y_score))[::-1] thresholds np.r_[np.inf, thresholds, -np.inf] P (y_true 1).sum() N (y_true 0).sum() fprs, tprs [], [] for t in thresholds: y_pred (y_score t).astype(int) tp ((y_pred 1) (y_true 1)).sum() fp ((y_pred 1) (y_true 0)).sum() tprs.append(tp / P if P else 0.0) fprs.append(fp / N if N else 0.0) return np.array(fprs), np.array(tprs), thresholds这里有两个地方容易写错。第一阈值序列要手动补上inf和-inf否则曲线不会从 (0,0) 走到 (1,1)画出来会缺头缺尾。第二判断条件用还是要和 sklearn 对齐sklearn 的 roc_curve 默认用的是也就是分数等于阈值时判为正类。这个细节在分数有大量并列值的时候会影响曲线形状后面讲 AUC 陷阱时会再提。2.4 概率校准分数到底是不是概率很多模型输出的分数并不是严格意义上的概率。逻辑回归、朴素贝叶斯这类模型输出天然接近概率但随机森林、梯度提升树、SVM 的输出往往只是“打分”数值大小和真实概率之间有系统性偏差。如果你要把分数直接当概率用比如算期望损失、做风险定价就必须先做校准。校准的检查方法是画可靠性曲线把预测概率分箱横轴是每个箱的平均预测概率纵轴是每个箱的真实正样本比例理想情况下应该贴着对角线。偏离对角线的形态能告诉你偏差方向S 形通常是过度自信或不够自信需要 Platt 缩放单调但非线性的偏差可以用等渗回归。Brier Score 则是校准和判别力的综合指标数值越小越好计算公式是预测概率与真实标签的均方误差。注意校准必须在独立的校准集上做不能拿训练集拟合校准器再拿训练集评估否则会得到虚假的完美校准。实践中我一般从训练集里再切一小块做校准或者用交叉验证的折外预测来拟合校准器。3. ROC 曲线核心细节与 AUC 计算3.1 TPR 与 FPR 的几何意义ROC 曲线上的每一个点对应一个具体的阈值。左上角 (0,1) 是理想点FPR 为 0、TPR 为 1意味着所有正样本都被抓出来且没有误报右下角 (1,0) 是最差点模型完全反向对角线 yx 对应随机猜测AUC 等于 0.5。曲线越往左上角凸说明模型在低误报率下能拿到更高的召回率这正是业务上最想要的区间。我读 ROC 曲线时有个习惯不看整条曲线的面积先看 FPR 小于 0.05 那一段的 TPR。因为实际业务里阈值通常卡在很靠左的位置高 FPR 区域的性能再好看也用不上。有些模型整体 AUC 很高但左上角那一段塌得厉害上线后表现往往不如 AUC 稍低但左上角更饱满的模型。这个视角在模型选型时比单看 AUC 有用得多。3.2 AUC 的两种计算方式AUC 的几何定义是 ROC 曲线下的面积数值上等于随机取一个正样本和一个负样本正样本得分高于负样本的概率。这个概率解释非常直观也解释了为什么 AUC 对类别比例不敏感它只关心正负样本两两比较的相对顺序不关心各自有多少个。第一种算法是梯形法把 ROC 曲线上的相邻点用梯形面积累加。代码很简单但要注意 FPR 必须按升序排列def auc_trapezoid(fpr, tpr): order np.argsort(fpr) return np.trapz(tpr[order], fpr[order])第二种算法是秩和法也叫 Mann-Whitney U 统计量。把所有样本按分数排序正样本的秩次之和减去正样本自身秩次的最小可能值再除以正负样本对的总数from scipy.stats import rankdata def auc_rank(y_true, y_score): ranks rankdata(y_score) # 并列分数自动取平均秩 n_pos int(np.sum(y_true 1)) n_neg len(y_true) - n_pos sum_ranks_pos ranks[y_true 1].sum() return (sum_ranks_pos - n_pos * (n_pos 1) / 2) / (n_pos * n_neg)秩和法的好处是复杂度低排序一次就出结果而且在分数有大量并列时处理更稳。这里的关键是rankdata对并列分数取平均秩如果没有这步处理并列样本的先后顺序会人为影响结果AUC 会随输入顺序漂移。3.3 AUC 的四个常见陷阱第一个陷阱是类别极端不均衡时的乐观偏差。前面已经说过FPR 的分母是负样本总数负样本越多同样的 FP 数量对应的 FPR 越小曲线越靠左上。一个在十万负样本里误报一千个的模型FPR 只有 0.01看起来非常漂亮但一千个误报在业务上可能已经不可接受。第二个陷阱是 AUC 掩盖局部差异。AUC 是整个 FPR 区间上的平均表现两个模型可能 AUC 完全相同但一个在低 FPR 区间强、一个在高 FPR 区间强。业务只关心低 FPR 区间时AUC 相等不代表可以互换。第三个陷阱是跨数据集比较。虽然 AUC 对先验比例相对不敏感但不同数据集的难度分布不同同一个模型在 A 数据集 AUC 0.9、在 B 数据集 AUC 0.8不能简单说它在 A 上更好可能只是 A 的样本更容易区分。第四个陷阱是分数未校准时的误用。AUC 只依赖排序不依赖分数的绝对数值所以未校准的分数照样能算出合理的 AUC。但如果你拿这个分数去做阈值决策、算期望收益就会出问题。AUC 高和概率准是两件事评估报告里要分开写。3.4 sklearn 实操与自定义实现对照实际项目里当然直接用 sklearn但我会定期拿自定义实现和 sklearn 对一遍确保自己没记错细节。下面这段代码把两种实现放在一起比较from sklearn.metrics import roc_curve, roc_auc_score, auc import numpy as np y_true np.array([1, 1, 0, 1, 0, 0, 1, 0, 0, 1]) y_score np.array([0.9, 0.8, 0.75, 0.7, 0.6, 0.55, 0.5, 0.4, 0.3, 0.2]) fpr, tpr, thr roc_curve(y_true, y_score) print(sklearn AUC:, roc_auc_score(y_true, y_score)) print(梯形法 AUC:, auc(fpr, tpr)) print(秩和法 AUC:, auc_rank(y_true, y_score))正常情况下三个数字应该在小数点后十来位一致。如果不一致先检查是不是有并列分数再检查阈值比较用的是还是。我遇到过一次不一致查了半天发现是数据里混进了 NaNsklearn 内部会做特殊处理而自定义实现直接把 NaN 当成了普通数值参与比较排序结果全乱了。所以数据清洗这步永远要在评估之前做干净。4. PR 曲线与 AP正样本稀缺场景的正确姿势4.1 Precision 与 Recall 的权衡本质Precision 回答的是“报出来的这些有多少是真的”Recall 回答的是“真的那些报出来了多少”。这两个指标天然对立阈值调低报得更多Recall 上去Precision 下来阈值调高报得更少Precision 上去Recall 下来。PR 曲线就是把这种此消彼长的关系完整画出来。这里有个容易混淆的点PR 曲线的横轴 Recall 是递增的但 sklearn 返回的 recall 数组是递减的因为它是按阈值从低到高排列的。画图时要把数组反转一下否则曲线方向是反的。我第一次画 PR 曲线时就栽在这上面图看起来特别怪后来才发现是横轴顺序搞反了。4.2 AP 的插值差异阶梯式与插值式AP 是 PR 曲线下的面积但“面积”怎么算有讲究。最早 PR 曲线用的是 11 点插值法在 Recall 的 11 个等分点上取右侧最大的 Precision这种方式对曲线做了平滑数值偏乐观。后来学术界更推荐阶梯式也就是直接对相邻 Recall 点的 Precision 做加权求和不做任何插值。sklearn 的average_precision_score用的是阶梯式实现上等价于对 recall 做差分再乘对应 precisionfrom sklearn.metrics import precision_recall_curve, average_precision_score import numpy as np precision, recall, thresholds precision_recall_curve(y_true, y_score) ap average_precision_score(y_true, y_score) # 手动复现阶梯式 AP ap_manual -np.sum(np.diff(recall) * precision[:-1]) print(ap, ap_manual)注意precision和recall的长度比thresholds多一个因为最后一个点对应阈值无穷大、所有样本都判为负类的情况。做阈值寻优时下标要对齐否则会错位。这个坑我在好几个项目里见人踩过表现是选出来的阈值对应的 F1 和报告里对不上。4.3 PR 曲线的基线问题PR 曲线不能直接比高度因为它的随机基线不是 0.5而是正样本比例。正样本占 10% 时一个随机模型的 PR 曲线大致在 Precision 0.1 附近波动AP 也接近 0.1。正样本占 1% 时随机基线就降到 0.01。所以两个正样本比例不同的数据集AP 0.3 和 AP 0.2 不一定后者更差得先看各自基线。我一般会在报告里同时写三个数AP、基线正样本比例、提升倍数AP 除以基线。提升倍数在跨项目比较时特别有用能快速判断模型到底比随机猜强多少。另一个实用指标是 RecallK也就是取打分最高的 K 个样本时的召回率K 通常对应业务每天能处理的告警量这个指标比 AP 更贴近运营实际。4.4 实操代码与 ROC 对比下面这段把 ROC 和 PR 放在一起算用的是同一个模型输出方便直观对比两种视角的差异from sklearn.metrics import ( roc_auc_score, average_precision_score, precision_recall_curve, roc_curve ) auc_val roc_auc_score(y_true, y_score) ap_val average_precision_score(y_true, y_score) baseline np.mean(y_true) print(fAUC: {auc_val:.4f}) print(fAP: {ap_val:.4f}) print(f随机基线: {baseline:.4f}) print(f提升倍数: {ap_val / baseline:.2f}x)在正样本比例 5% 的数据集上我见过 AUC 0.92、AP 0.45 的模型也见过 AUC 0.88、AP 0.52 的模型。前者 ROC 好看但 PR 一般后者反过来。业务只关心正类命中时我会选后者。这种取舍单看 AUC 是做不出来的必须把两个视角都摆出来。5. 完整评估流水线的实操搭建5.1 数据切分策略决定评估可信度评估结果可不可信一半取决于切分。随机切分在时序数据上会泄露未来信息模型在验证集上表现虚高上线后迅速打回原形。我现在的做法是有时序关系的用时间切分训练集取早期数据验证集取后期数据有分组结构的用分组切分比如同一个用户、同一台设备的数据不能同时出现在训练和验证里完全没有结构的才用分层随机切分。分层切分的作用是保证训练集和验证集的正负比例一致避免小样本场景下验证集里正样本太少导致指标剧烈波动。sklearn 的train_test_split带上stratifyy就能实现交叉验证用StratifiedKFold。这里有个细节如果正样本极少比如只有几十个普通交叉验证的每折正样本可能只有个位数指标方差会非常大。这种情况下我会用重复分层交叉验证多跑几轮取均值和标准差报告里写“均值±标准差”比单次结果可靠得多。5.2 多分类的宏平均、微平均与加权平均多分类评估最容易糊弄过去的就是平均方式。宏平均是每类单独算指标再取算术平均小类和大类权重相同能暴露小类性能问题微平均是把所有类的混淆矩阵合并后统一算大类会主导结果加权平均是按每类样本数加权介于两者之间。假设三分类类别样本数分别是 1000、100、10小类 F1 只有 0.3其他两类都是 0.9。宏平均 F1 约 0.7微平均接近 0.89加权平均约 0.88。如果报告只写微平均小类崩了也看不出来。我的习惯是宏平均和加权平均都写差距大就说明小类有问题需要单独排查。多分类 ROC 也一样multi_classovr是每类对其他类算 AUC 再平均multi_classovo是两两配对算再平均。样本不均衡时优先看宏平均配合每类的 AP 一起看比单一数字有信息量。5.3 阈值寻优与业务落地曲线是给模型选型用的上线要的是具体阈值。常用的寻优目标有三种约登指数最大化TPR 减 FPR、F1 最大化、业务成本最小化。前两种纯统计第三种最贴近实际。成本最小化的做法是给 FP 和 FN 各赋一个代价遍历候选阈值算总成本取最小的那个。cost_fp, cost_fn 5, 50 # 误报和漏报的业务代价 best_thr, best_cost None, float(inf) for t in np.unique(y_score): y_pred (y_score t).astype(int) fp ((y_pred 1) (y_true 0)).sum() fn ((y_pred 0) (y_true 1)).sum() cost fp * cost_fp fn * cost_fn if cost best_cost: best_cost, best_thr cost, t代价数值从哪来找业务方聊问清楚一次误报平均消耗多少人工、一次漏报可能造成多大损失哪怕只能给出数量级也比拍脑袋强。阈值定下来后不要一劳永逸线上数据分布会漂移我一般两周复盘一次阈值附近的混淆矩阵发现精确率或召回率偏离设定值超过一定幅度就重新调。5.4 评估报告模板与可视化要点一份能让人看懂的评估报告至少包含这几块数据集描述样本量、正负比例、切分方式、核心指标表AUC、AP、基线、固定阈值下的精确率召回率 F1、ROC 和 PR 双图、混淆矩阵、分群表现按关键维度切片看指标是否稳定、以及结论和风险提示。图不要只画曲线把随机基线和对角线也画上读者一眼就能看出模型比瞎猜强多少。分群表现这块经常被忽略但它是发现模型偏见的主要手段。比如一个风控模型整体 AP 0.6按用户活跃度分群后新用户群体 AP 只有 0.2说明模型对新用户几乎没判别力上线后这批人会被大量误伤。这种问题在整体指标里完全看不出来只有切片才能暴露。6. 常见问题与排查实录6.1 曲线形状反常的排查清单ROC 曲线出现明显凹陷、PR 曲线剧烈锯齿、AUC 低于 0.5这些都是信号。AUC 低于 0.5 通常不是模型太差而是标签和分数的方向搞反了检查一下正类编码是不是 1。曲线锯齿一般是样本量太小或者分数并列太多可以增大验证集或者对分数做轻微扰动再看。ROC 出现凹陷说明数据或代码有问题正常的 ROC 曲线一定是单调的凹陷意味着某个阈值下 TP 和 FP 的变化不符合预期大概率是排序或去重环节出了错。还有一种情况是曲线画出来只有几个点那是分数取值太少比如模型只输出 0 和 1 两个值这种情况下曲线退化成折线AUC 的信息量很低应该改用对数损失或者 Brier Score 评估。6.2 AUC 很高但线上效果差这个问题我被问过很多次。原因通常有三类一是训练验证集和线上数据分布不一致离线评估再高也没用要做线上影子测试二是评估时用了未来信息比如特征里混进了标签泄露的字段离线虚高上线就崩三是 AUC 高但阈值选错了模型排序能力没问题卡点卡在了不合适的位置。排查顺序我一般是先查特征有没有穿越再查训练和线上数据的特征分布差异最后复盘阈值。第一类问题最隐蔽我见过特征里包含“最终处理结果”字段的离线 AUC 0.99上线直接废掉。评估前把特征逐个过一遍问一句“这个字段在预测时刻真的能拿到吗”能挡掉大部分泄露。6.3 PR 曲线抖动与 AP 不稳定PR 曲线在小样本上抖动是常态因为 Precision 的分母是预测为正的样本数阈值附近样本一进一出比值就会跳。缓解办法是增大评估集、对分数做分箱平滑、或者直接看固定召回率下的精确率比如 Recall0.8 时的 Precision这个数比整条曲线稳定得多。AP 不稳定还有一个原因是正样本太少几十个正样本算出来的 AP 方差极大不同随机种子能差出 0.1。这种情况下我会用重复交叉验证取均值并且报告里一定带上标准差。单次 AP 数字在没有方差信息的情况下参考价值有限尤其是模型之间差距很小的时候。6.4 常见问题速查表现象可能原因排查动作AUC 低于 0.5标签方向反了、分数取负了检查正类编码和分数符号ROC 曲线凹陷排序错误、重复样本、代码 bug用秩和法复算 AUC 对照PR 曲线锯齿严重样本量小、分数并列多扩大评估集、分数分箱AP 远低于 AUC 表现正样本稀缺、误报多看固定召回率下的精确率离线高线上低特征穿越、分布漂移逐特征检查时序合法性多分类指标虚高只看微平均、小类被掩盖补宏平均和每类 AP阈值上线后效果漂移数据分布变化定期复盘混淆矩阵、重调阈值这张表我一般贴在项目文档首页新同学上手先看一遍能少走不少弯路。我个人在实际操作中的体会是评估这件事最难的从来不是调包算指标而是想清楚业务到底在为什么买单。AUC 和 AP 再漂亮如果阈值卡在了业务用不上的位置或者评估集和线上根本不是一个分布那这些数字就只是自我安慰。每次写完评估报告我都会强迫自己回答一个问题如果明天就上线我最担心哪批样本被误判这个问题的答案往往比报告里任何一行指标都更接近真相。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑