ROC曲线与PR曲线详解:从AUC/AP到不平衡分类评估实战

发布时间:2026/10/20 5:31:56
ROC曲线与PR曲线详解:从AUC/AP到不平衡分类评估实战 做分类模型的朋友应该都有过这种体验模型跑完准确率往那儿一摆99.2%看着相当唬人。结果业务方一问“那它到底能找出多少真正的坏人”你瞬间就发现准确率这玩意根本回答不了问题。于是你开始翻出各种评估曲线ROC和PR就是这时候最常用到的两条。这篇是Day 16的学习笔记核心就干一件事把ROC曲线和PR曲线彻底聊明白。不光是告诉你怎么画更重要的是搞懂它们分别在解决什么问题、在什么场景下必须用哪条、以及用的时候有哪些反直觉的坑。适合正在学机器学习的同学看也适合那些已经跑过模型但在指标选择上还凭感觉的朋友。1. 两条曲线的来历和定位1.1 混淆矩阵是理解一切曲线的起点先回到最基本的混淆矩阵。二分类模型输出的是每个样本属于正类的概率比如“是垃圾邮件”的可能性。我们设定一个阈值高于阈值预测为正低于阈值就预测为负于是会得到四个数字TP真正例实际为正预测为正TN真负例实际为负预测为负FP假正例实际为负预测为正也叫误报FN假负例实际为正预测为负也叫漏报以垃圾邮件为例假设邮箱里一共1000封邮件其中真实垃圾邮件100封正常邮件900封。模型预测出120封垃圾邮件其中真垃圾80封、误伤40封正常邮件。那么TP80FP40FN20TN860。光看准确率也就是(80860)/100094%看起来挺高但如果你关心的是“垃圾邮件到底找回来多少”那得看召回率TPRTP/(TPFN)80/10080%说明还有20封漏了过去。这就是单一指标容易骗人的地方。ROC曲线和PR曲线本质上是在回答“阈值应该怎么选”“模型排序能力到底行不行”这两个问题。它们不是只看某一个固定阈值的效果而是把所有可能的阈值都扫一遍把结果连成一条曲线让你一眼看到模型在不同严苛程度下的表现。1.2 ROC曲线和PR曲线到底在画什么ROC曲线的全称是受试者工作特征曲线。横轴是假正例率FPR纵轴是真正例率TPR也就是召回率TPR TP / (TP FN)表示正样本被找出来的比例FPR FP / (FP TN)表示负样本被误判成正类的比例PR曲线的横轴是召回率Recall纵轴是精确率PrecisionPrecision TP / (TP FP)表示预测为正的样本里真正是正类的比例Recall TPR和上面一样直观理解的话ROC关注的是“找出正例的能力”和“别误伤负例的克制程度”之间的平衡PR曲线关注的是“找回来的正例里有多少是真货”像手术前的二次确认必须尽可能精准。ROC曲线和PR曲线经常被放在一起讲因为它们都是把预测分数从高到低排序后以不同阈值切分得到的曲线而且都能用一个面积值来概括性能分别是AUC和AP。很多论文习惯两张图一起放背后其实是希望读者从两个维度同时审视模型。1.3 为什么两条曲线经常被同时摆出来因为只给一条会出问题。ROC曲线在面对类别不平衡时很容易显得“过度乐观”。举个例子如果负样本有10000个正样本只有100个即使误伤了一堆负样本FPR的分母是(FPTN)其中TN基数极其庞大FPR的变化幅度会非常小曲线看起来还是贴左上角。PR曲线不一样它的横轴根本不看TN负样本的基数再多也不影响走势。正类占比越低PR曲线的压力越大。所以严谨的做法是两条曲线一起画互相当参照物谁也别想偷懒。2. 不打开计算器ROC曲线逐点手算实例2.1 准备一份小数据集光看公式容易虚实际手算一遍就通了。造一个极小的例子10个样本5个正类(P)5个负类(N)模型已经给每个样本打出了预测得分按得分从高到低排好样本编号真实标签预测得分1P0.982P0.853N0.764P0.645N0.456N0.327P0.288N0.199P0.1110N0.03注意这个排序是模型能力的核心真正例的得分总体要比假正例靠前ROC曲线才会好看。2.2 阈值扫描的完整过程预测得分可以嵌入选好阈值后得分高于阈值判为正否则判为负。我们从最高分0.98开始把这个阈值慢慢往下调每调一次就把TPR和FPR算一遍。阈值取0.98只有样本1被预测为正。TP1FN4所以TPR1/50.2FP0TN5所以FPR0。坐标点(0, 0.2)。阈值取0.85样本1、2被预测为正。TP2FN3TPR0.4样本3是负样本但得分排第三此刻还没被预测正所以FP0FPR0。坐标点(0, 0.4)。阈值取0.76样本1、2、3被预测为正。此时TP2FN3TPR0.4样本3被误判为正FP1TN4FPR0.2。坐标点(0.2, 0.4)。阈值取0.64样本1、2、3、4被预测为正。TP3FN2TPR0.6FP1FPR0.2。坐标点(0.2, 0.6)。阈值取0.45样本1、2、3、4、5被预测为正。TP3TPR0.6FP2FPR0.4。坐标点(0.4, 0.6)。阈值取0.32样本1到6。TP3TPR0.6FP3FPR0.6。坐标点(0.6, 0.6)。阈值取0.28样本1到7。TP4TPR0.8FP3FPR0.6。坐标点(0.6, 0.8)。阈值取0.19样本1到8。TP4TPR0.8FP4FPR0.8。坐标点(0.8, 0.8)。阈值取0.11样本1到9。TP5TPR1.0FP4FPR0.8。坐标点(0.8, 1.0)。阈值取0.03全部判为正。TP5TPR1.0FP5FPR1.0。坐标点(1.0, 1.0)。再补两个边界阈值取无穷大时所有样本都判负点在(0, 0)阈值取负无穷时全都判正点在(1, 1)。把这些点连起来就形成ROC曲线。可以看到它明显凸向左上角说明排序质量不错。2.3 如何科学地解读AUC值AUC就是ROC曲线下的面积。它的概率含义很漂亮随机抽一个正样本和一个负样本模型给正样本打出的分数高于负样本的概率。换句话说AUC衡量的是模型把正负样本排对顺序的能力跟具体阈值没关系。用上面这个10样本小例子可以把AUC当成一个排序正确率的度量0.5等于瞎猜因为50%的概率排对1.0等于完美排序所有正样本都排在负样本前面。实际业务里怎么参考AUC在0.7~0.8算能用0.8~0.9算良好0.9以上要小心先确认是不是数据泄漏或者评估集太小导致的结果失真。AUC不是越高越好高得离谱反而值得怀疑。3. PR曲线不平衡数据下的“照妖镜”3.1 精确率与召回率的互补关系PR曲线的核心就是精确率和召回率之间的矛盾关系。阈值降低时越来越多的样本被预测为正召回率自然上升但这意味着连一些得分不高的负样本也被捞进来精确率通常会随之下降。可以这么类比你在果园里摘苹果召回率是“别漏掉任何一颗熟苹果”精确率是“摘下来的果子里有多少是真熟透的”。想一条不漏就得把看起来差不多的青果子也摘下来筐里烂果子自然变多想每颗都摘得准就得摘得小心翼翼难免漏掉一些藏在叶子底下的。这两条曲线就是这种取舍的直接反映。如果模型能把正样本的得分排得很靠前你会发现阈值降了之后先进来的还是正样本精确率掉得慢曲线就比较靠右上。如果模型排序不行阈值一放宽就混进一堆负样本曲线会立刻朝左下角坠落。3.2 同一份数据两条曲线差多少还用上面那10个样本来看。PR曲线需要每个阈值下精确率和召回率的数据阈值PrecisionRecall0.981.00.20.851.00.40.760.6670.40.640.750.60.450.60.60.320.50.60.280.5710.80.190.50.80.110.5561.00.030.51.0对照上一节的ROC坐标点能看出一个大区别ROC的纵轴TPR只取决于正样本检出的比例横轴FPR要看TN的数量PR曲线的纵轴要看FP的数量横轴只跟正样本有关完全不涉及TN。当负样本极多时FPR的变化会被巨大的TN基数稀释ROC曲线依然可以很漂亮。但PR曲线每一个FP都会直接压低精确率负样本的影响完全压不住。所以类别分布越偏这两条曲线的差距越明显。正类占比掉到个位数甚至更低的时候AUC还漂漂亮亮、AP却惨不忍睹这种情况我见过太多次。3.3 AP分数为什么比面积更实用AP是Average Precision的缩写在PR曲线上有几种计算方式。常见的一种是在每个被召回的正样本位置计算当前的精确率然后对所有位置的精确率求平均等价于PR曲线下的面积。sklearn里的average_precision_score就是这种插值平均的思路。AP的最大价值是对少数类极其敏感。它不把负样本的大基数摊进来而是实打实看模型在正样本上找回得准不准。做反欺诈、故障检测、罕见病筛查这类正样本稀少的场景时AP比AUC更容易反映出模型排名的真实变化。个人的习惯是调参时把AP当主指标AUC当次指标。AP波动大、反馈直接AUC相对稳适合做整体核验。4. 二分类实战用Python完整绘制两条曲线4.1 生成一份带倾向性的数据集空谈无用直接上手跑一遍。用机器学习里常用来做演示的合成数据这里生成2000个样本2个特征正样本占比设成5%模拟真实业务中常见的稀疏正例场景。这样画出来更容易看出ROC和PR的差异。import numpy as np import matplotlib.pyplot as plt from sklearn.datasets import make_classification from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import roc_curve, roc_auc_score from sklearn.metrics import precision_recall_curve, average_precision_score X, y make_classification( n_samples2000, n_features2, n_redundant0, n_informative2, n_clusters_per_class1, weights[0.95, 0.05], random_state42 ) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, random_state42 ) model LogisticRegression() model.fit(X_train, y_train) y_score model.predict_proba(X_test)[:, 1]要注意一点这里用正类的概率分数而不是predict返回的0/1标签。因为曲线需要对所有可能的阈值扫描必须提供连续得分。4.2 计算并绘制ROC曲线fpr, tpr, thresholds roc_curve(y_test, y_score) auc_value roc_auc_score(y_test, y_score) plt.figure(figsize(6, 5)) plt.plot(fpr, tpr, labelfROC (AUC {auc_value:.3f})) plt.plot([0, 1], [0, 1], k--, labelRandom (AUC 0.5)) plt.xlabel(False Positive Rate) plt.ylabel(True Positive Rate) plt.title(ROC Curve) plt.legend() plt.grid(alpha0.3) plt.show()跑完之后你会发现AUC大概率在0.95以上。原因就是ROC曲线在极端不平衡数据下天然容易好看。别高兴得太早把PR曲线也画出来对比一下才知道模型到底几斤几两。4.3 计算并绘制PR曲线precision, recall, thresholds precision_recall_curve(y_test, y_score) ap_value average_precision_score(y_test, y_score) plt.figure(figsize(6, 5)) plt.plot(recall, precision, labelfPR (AP {ap_value:.3f})) base_rate y_test.mean() plt.axhline(base_rate, colorgray, linestyle--, labelfBaseline ({base_rate:.3f})) plt.xlabel(Recall) plt.ylabel(Precision) plt.title(Precision-Recall Curve) plt.legend() plt.grid(alpha0.3) plt.show()画PR曲线时一定要把基线画出来。基线就是正样本占比比如这里是约5%。如果AP只是0.2几或者0.3哪怕ROC的AUC超过0.95也说明模型排名优异的正样本在整体正例池子里并没有想象中那么突出离“精准找到稀缺正样本”还有不小的距离。4.4 两条曲线合在一起看结论才完整典型效果是ROC曲线骄傲地贴在左上角AUC0.97PR曲线却明显往右下角塌AP只有0.5左右。同样的模型两套指标给出的体感完全不同。不是因为哪条曲线错了而是它们各自在回答不同的问题。ROC回答的是“负样本被误判的代价是否可控”PR回答的是“找到的正样本是否足够纯”。所以我的建议很直接上线报告里两条曲线都画AP配上正样本占比AUC配上负样本占比让人一眼能判断当前场景是平衡数据还是稀疏正例。5. 曲线能用不代表模型能上线高频踩坑记录5.1 极端不平衡时的“美丽谎言”正样本占比千分之一时AUC冲到0.99都有可能因为负样本实在太多FPR稍微动一点绝对值就已经很大了但被淹没在庞大的TN基数里。PR曲线则会立刻揭穿真相AP可能只有0.1。这种时候真正值得相信的排序是先看正样本数量再看AP最后才看AUC。某次做异常交易识别时AUC漂亮得能上墙结果一查AP只有0.04原因就是模型把所有样本都打低分排序里正样本并没有真正靠前只是负样本基数大到让FPR变化可以忽略。5.2 样本量太小曲线锯齿状怎么办当正样本只有几十个时PR曲线会像心电图一样抖每个阈值跳动都可能画出剧烈的折线。这不是模型不行是评估集容量不够统计波动太大。处理办法一是扩大测试集别只留几百个样本二是采用多次随机采样的方式画出一组曲线的置信带状区域看整体走向而不是单条线三是可以记录多个时间窗口的AP观察均值和中位数是否稳定而不是盯着某一次实验值。5.3 阈值选错模型上线即翻车曲线看的是排序能力但线上不可能动态调阈值。如果你只是看到AUC很高就顺手用0.5当阈值很可能上线后精确率和召回率都不符合业务要求。正确做法是明确业务目标例如必须召回90%的正样本那就回到PR曲线上找到召回率0.9对应的精确率和阈值或者按业务成本给“漏报”和“误报”分别定损失找出一条加权损失最小的阈值。还有一个细节roc_curve返回thresholds是按降序排列的precision_recall_curve返回的thresholds和recall、precision数组是同一个索引对应。拿阈值时千万别搞错对齐关系我踩过这个坑。5.4 多分类与在线学习场景下怎么处理多分类任务不能直接画一条总曲线。常规做法是一对多对每个类别分别算一条ROC或PR曲线也可以把正例合并做微平均或宏平均的曲线。微平均偏向样本量大的类别宏平均平等看待每个类别选哪个取决于业务更关注谁。在线学习或模型持续迭代的场景里数据分布会漂移。上周画好的ROC曲线这周可能就作废了。稳妥的做法是用滑动窗口定期重算曲线并把不同时间窗口的AP和AUC画到同一张趋势图上。曲线只反映它被计算时那批数据的状态别把它当永久标签。6. 我个人实践中的几条实用经验做分类评估这三年多我自己总结了一套固定动作拿到一个新模型先查正样本数量再看测试集里的正类占比然后算AP和AUC最后一定手动看几个高分段样本的排序情况而不是只盯汇总曲线。高分段里如果混进太多负样本AUC再高也扛不住线上业务的精确率要求。还有一点画曲线时别用默认的密集网格。数据量大时几千个阈值点画在一起线条反而糊成一团。可以先隔几个点采样或者只画出关键阈值区域比如召回率在0.6到0.95之间放大看这样更容易找到可用的阈值。最后分享一个方法论层面的心得任何评估曲线都只是对历史数据的总结真正上线后分布一漂移再漂亮的曲线都可能失灵。与其纠结ROC和PR哪个更高端不如先想清楚业务最怕哪种错误——怕误伤就盯着FPR和precision怕漏报就盯着TPR和recall。把这两条曲线的定位和误读方式彻底搞明白比多跑几十个模型都管用。