上个月参加一场模型评审,算法侧报上来一个离线 AUC 0.93 的评分模型,讲得挺完整,特征工程、样本采样、交叉验证都交代了。业务侧紧接着甩出一句:“这模型上线一周,预测准确率只有三成多。”会议室安静了两秒,然后两边开始各自翻自己的报表,越翻越对不上。其实他们说的根本不是同一个东西——一个在说模型把正负样本排开的能力,一个在说按 0.5 这条线切下去之后有多少分对了。
AUC、ROC、ACC 这三个词在机器学习里出现频率极高,但它们回答的是三个不同的问题。ACC 回答“我按某个阈值切完之后,对的比例是多少”;ROC 回答“我把阈值从高到低滑一遍,正样本召回率和负样本误伤率是怎么此消彼长的”;AUC 则把整条 ROC 曲线压成一个数字,回答“模型整体的排序能力有多强”。把这三个混着用,轻则报告被追问到答不上来,重则在类别极不平衡的场景里选出一个“看起来很美”实际没用的模型。
下面我把这三个指标从定义、计算、适用边界到实操踩坑,一层层拆开讲。如果你正在做分类模型、要写评估报告、或者被业务方问过“你这个 0.9 到底是什么意思”,这篇内容可以当成一份对照手册看。
1. 三个指标在算同一件事的不同侧面
1.1 混淆矩阵:三个指标共用的那块地基
讲 AUC、ROC、ACC 之前绕不开混淆矩阵,因为这三个指标全部是从它派生出来的,只是派生的路径不一样。二分类问题里,模型对每个样本输出一个分数,你选一个阈值把它切成“正”和“负”,和真实标签两两对照,就得到四个格子:真正例 TP、假正例 FP、真负例 TN、假负例 FN。
这四个数字看起来简单,但它们撑起了后面所有的讨论。ACC 是拿对角线上的两个数除以总数;召回率 TPR 是 TP 除以“真实为正”的那一整行;误伤率 FPR 是 FP 除以“真实为负”的那一整行。注意这里有个关键差别:ACC 的分母是全部样本,TPR 和 FPR 的分母各自是真实正样本数和真实负样本数,两者互不干涉。
这个分母的差别,就是后面所有“ACC 和 AUC 结论打架”问题的根源。我见过不少人把 ACC 高理解成“模型好”,把 TPR 高也理解成“模型好”,但在类别不平衡的场景里,这两个“好”可能指向完全相反的模型。
还有一点容易被忽略:混淆矩阵依赖阈值。同一批预测分数,阈值取 0.3、0.5、0.7 会得到三张完全不同的混淆矩阵,进而算出三个不同的 ACC。所以在报告里只写一个 ACC 却不写阈值,等于什么都没说。
1.2 ACC:一个分子除以分母,陷阱全在分母里
ACC 的定义简单到不需要解释:预测正确的样本数除以总样本数,也就是 (TP+TN)/(TP+FP+FN+TN)。正因为简单,它成了最容易被误用的指标。
它的问题不在于算法,而在于它给每个样本的权重是一样的。一个正样本判对和一个负样本判对,在 ACC 眼里价值相同。可现实里这两类错误的代价往往差得很远——把一笔欺诈交易漏过去,和把一个正常用户误拦下来,损失量级可能差十倍百倍。ACC 对这种不对称完全无感。
更麻烦的是类别比例。假设一个场景里负样本占 95%,正样本只占 5%,模型什么都不学,无脑全预测为负,ACC 直接就是 0.95。这个数字放在报告里非常漂亮,但模型的实际价值是零,因为它一个正样本都没抓到。
我一般会把 ACC 理解成“在默认阈值下的一次抽样体检”,它能告诉你整体判对的比例,但告诉不了你错在哪、错得贵不贵、换个阈值会不会更好。它能用,但只能作为众多指标里的一个配角,不能当主角。
1.3 ROC 与 AUC:换了个坐标系看模型
ROC 曲线的思路和 ACC 完全不同。它不去固定一个阈值,而是把阈值从高到低滑一遍,每滑到一个位置就记录一对数字:纵轴是 TPR(真实正样本中被抓到的比例),横轴是 FPR(真实负样本中被误伤的比例)。阈值高的时候两个值都接近 0,阈值低的时候两个值都接近 1,把这些点连起来就是一条从左下角往右上角走的曲线。
这条曲线的形状直接反映模型的排序质量。一个完美的模型,所有正样本的分数都高于所有负样本,曲线会贴着左上角走;一个随机瞎猜的模型,曲线就是那条从 (0,0) 到 (1,1) 的对角线。
AUC 是这条曲线下的面积,取值在 0 到 1 之间。它有一个非常好用的等价解释:随机抽一个正样本和一个负样本,模型给正样本的分数高于负样本的概率。这个解释比“曲线下面积”直观得多,也解释了为什么 AUC 对类别比例不敏感——它每次只比较一对样本,正负样本各自内部的多少不影响这个比较。
理解了这一点,就能明白 AUC 衡量的是“排序”,不是“分类”。模型只要能把正样本排在负样本前面,AUC 就高,至于具体在哪个阈值切、切完之后精确率是多少,AUC 一概不管。
2. 为什么同一个模型,ACC 和 AUC 会给出相反的结论
2.1 一个能算出来的反例
空口讲道理不如直接算。构造一个 1000 个样本的场景,正样本 50 个,负样本 950 个,正样本占比 5%。
先看模型 A:它什么都没学会,把所有样本都预测为负。这时候 TP=0、FN=50、FP=0、TN=950,ACC = (0+950)/1000 = 0.95。但它的 TPR=0,一个正样本都没抓到,ROC 曲线就是对角线上的一条退化线,AUC = 0.5,等于抛硬币。
再看模型 B:它抓到了 40 个正样本,漏了 10 个,同时误伤了 100 个负样本。TP=40、FN=10、FP=100、TN=850,ACC = (40+850)/1000 = 0.89。TPR = 40/50 = 0.8,FPR = 100/950 ≈ 0.105。把这两点放到 ROC 空间里,AUC 大概在 0.9 上下。
结果很清楚:模型 A 的 ACC 是 0.95,比模型 B 的 0.89 高,但模型 A 的 AUC 是 0.5,模型 B 是 0.9。如果报告里只写 ACC,你会选模型 A,而模型 A 在生产环境里一点用都没有。
这个反例我在不同场合讲过很多次,每次讲完都有人问“那是不是 ACC 就没用了”。不是没用,是它在这个场景里被类别比例绑架了。分母里 950 个负样本的权重太大,把 50 个正样本的表现彻底稀释掉了。
2.2 AUC 对类别比例不敏感,这是优点也是盲区
AUC 之所以不受类别比例影响,是因为它在计算时把正负样本分开处理了。无论正样本是 50 个还是 5000 个,它都是在“正样本内部”和“负样本内部”各算各的,最后比较两个分布的相对位置。
这个性质在类别不平衡的场景里非常有用,风控、医疗筛查、故障检测这类正样本稀少的任务,AUC 几乎是标配指标。但它同时带来一个盲区:当负样本极度稀少时,AUC 的估计会变得非常不稳定。
原因在 FPR 这一侧。FPR 的分母是真实负样本数,如果负样本只有几十个,那么每多误伤一个,FPR 就跳一大截,ROC 曲线会变得非常粗糙,AUC 的方差也随之放大。这种情况下两个模型 AUC 差 0.05,可能纯粹是采样噪声,不能直接下结论说谁更好。
我的经验是,正样本或负样本任一侧少于 100 个时,AUC 一定要配置信区间看,用 bootstrap 重采样算个几百次,看分布有没有重叠。重叠了就老老实实说“两个模型在这个样本量下分不出高下”,别硬给结论。
2.3 阈值是 ACC 的隐形参数,是 AUC 的天然免疫对象
这是三个指标里最实用的一条区别。ACC 必须绑定一个阈值才能算出来,而 AUC 把所有阈值都积分掉了,不需要你选。
这个差别在实际工作里的影响比想象中大。业务方上线模型的时候,往往不是按 0.5 切的。内容审核场景可能要把阈值调到很高,宁可漏也不误伤;反欺诈场景可能要把阈值调低,宁可误伤也要抓全。同一个模型,换个阈值,ACC 可能从 0.7 掉到 0.5,但 AUC 一动不动。
所以我现在写报告,ACC 旁边一定标注阈值,而且尽量报多个阈值下的结果,比如 0.3、0.5、0.7 各给一列。业务方看到这个表格,才能对应到自己实际会用的那个点。
反过来说,AUC 的“免选阈值”也有代价:它把高分段和低分段的表现一视同仁地平均了。可业务上真正在意的是高分段——比如只审核风险分最高的 5%——那这 5% 里的排序质量,AUC 反映得并不充分。这时候我会补一条 PR 曲线或者直接算 top-K 的精确率。
3. ROC 曲线怎么画出来,AUC 怎么算出来
3.1 手动推一遍 ROC 的生成过程
光看定义容易飘,拿一组小数手动走一遍最实在。假设模型对 7 个样本输出了如下分数,真实标签是前三个为正:
import numpy as np y_true = np.array([1, 1, 1, 0, 0, 0, 0]) y_score = np.array([0.9, 0.6, 0.4, 0.8, 0.5, 0.3, 0.1])阈值取 0.5,大于等于 0.5 判为正。逐个看:0.9 判正、真实正,TP;0.6 判正、真实正,TP;0.4 判负、真实正,FN;0.8 判正、真实负,FP;0.5 判正、真实负,FP;0.3 判负、真实负,TN;0.1 判负、真实负,TN。
汇总一下:TP=2、FN=1、FP=2、TN=2。TPR = 2/3 ≈ 0.667,FPR = 2/4 = 0.5,ACC = 4/7 ≈ 0.571。
现在把阈值从 1.0 开始往下滑,每遇到一个分数就更新一次 TP 和 FP,记录一组 (FPR, TPR):阈值 1.0 时两个都是 0,得到点 (0,0);滑到 0.9 时抓到一个 TP,得到 (0, 0.333);滑到 0.8 时误伤一个负样本,得到 (0.25, 0.333);滑到 0.6 时又抓一个 TP,得到 (0.25, 0.667);滑到 0.5 时再误伤一个,得到 (0.5, 0.667);滑到 0.4 时抓第三个 TP,得到 (0.5, 1.0);滑到最后负样本全进,得到 (1,1)。
把这些点按横轴排序连起来,就是这条样本集上的 ROC 曲线。真正的工程实现不会一个一个阈值去扫,sklearn 的做法是先按分数排序,然后一次性累加,复杂度 O(n log n),结果完全等价。
3.2 AUC 的两种算法:梯形积分与排序统计
AUC 的第一种算法是几何法:把 ROC 曲线下的区域切成若干个小梯形,逐个求面积再累加。上面的例子里,相邻点之间的梯形面积加起来,就是 AUC 的几何近似。样本量大的时候,这种离散累加和真实的曲线下面积差别可以忽略。
第二种算法是统计法,也就是前面提到的“正样本分数高于负样本的概率”:
pos = y_score[y_true == 1] # [0.9, 0.6, 0.4] neg = y_score[y_true == 0] # [0.8, 0.5, 0.3, 0.1] pair_num = len(pos) * len(neg) # 3 * 4 = 12 win = 0.0 for p in pos: for n in neg: if p > n: win += 1.0 elif p == n: win += 0.5 # 同分算半个,这是标准做法 print(win / pair_num) # 9 / 12 = 0.75手算一下:0.9 赢了全部 4 个负样本;0.6 赢了 0.5、0.3、0.1 这 3 个;0.4 赢了 0.3、0.1 这 2 个。总共 9 个胜场,除以 12 个配对数,AUC = 0.75。
这个结果和几何法算出来的应该一致,实现上有细微差别时通常是同分处理方式不同导致的。顺带说一下,这个统计量在统计学里有名字,叫 Mann-Whitney U 统计量,AUC 和它是同一个东西的两种表述。
工程上还有一个细节:同分必须算 0.5 分。如果直接写p > n而不处理相等的情况,在分数是离散值(比如只有 0 到 100 的整数分)的场景里,AUC 会被系统性低估。这个坑我在早期一个规则打分模型上踩过,分数全是整数,同分极多,算出来的 AUC 比实际低了一截,排查了半天才发现是这里的问题。
3.3 曲线上的几个关键点到底意味着什么
ROC 曲线上有几个特殊位置,值得单独说一下,因为它们和业务决策直接对应。
左上角的 (0, 1) 是理想点,意味着一个正样本没漏、一个负样本没误伤,现实中基本不存在。对角线是随机基准,AUC 等于 0.5,任何 AUC 明显低于 0.5 的模型,通常意味着标签接反了或者特征方向搞错了,这时候把预测分数取个负号,AUC 就变成 1 减去原来的值。
曲线上的每一个点都对应一个具体阈值,也就对应一组具体的 TPR 和 FPR。业务上选阈值,本质上是在这条曲线上挑一个点。比如反欺诈场景,你可能希望 TPR 不低于 0.8,在这个约束下找 FPR 最小的那个点;内容审核场景,你可能希望 FPR 不超过 0.05,在这个约束下找 TPR 最大的点。
这里有个容易被忽略的事实:AUC 只反映曲线整体的“隆起程度”,不反映曲线在某个局部的好坏。两条 ROC 曲线可能 AUC 相同,但一条在高 TPR 区域表现好,另一条在低 FPR 区域表现好。如果你的业务只关心低 FPR 区域,光看 AUC 会丢掉这个信息,必须把曲线本身画出来看局部形状,或者直接算部分 AUC。
4. 三个指标的主场和死穴
4.1 ACC 什么时候还能放心用
ACC 不是不能用,它有明确的主场。当类别基本平衡、两类错误代价接近、阈值就是业务实际会用的那个值时,ACC 是一个直观且够用的指标。比如一个二分类的缺陷检测任务,合格品和不合格品各占一半,漏检和误检的返工成本差不多,那 ACC 完全可以作为主指标汇报。
还有一种情况是作为辅助指标。AUC 高不高是一回事,落地到具体阈值上 ACC 是多少是另一回事,两个一起报,读者才能建立完整印象。我现在写报告的习惯是 AUC 打头,后面跟一个“阈值 X 下的 ACC / 精确率 / 召回率”表格,这样既有全局视角又有落地视角。
需要警惕的是把 ACC 当成唯一指标,尤其是在采样之后的数据上算 ACC。做过负采样或者正样本过采样的人应该都有体会:采样后的数据分布和线上真实分布不一样,在采样数据上算出来的 ACC 和线上跑的准确率能差出十几个点。这种情况要么把预测结果还原到真实分布再算,要么干脆别用 ACC。
4.2 AUC 的适用边界,以及几个典型的反例
AUC 好用,但它有明确的边界,越过边界继续用就会出问题。
第一个边界是极度不平衡且负样本稀少。前面说过,FPR 的分母太小会让 AUC 方差爆炸。这种情况更适合看 PR 曲线,因为精确率的分母是“预测为正的样本数”,在正样本稀少的场景里更稳定。
第二个边界是只关心头部。比如推荐系统只展示前 20 个结果,风控只人工审核风险分最高的 1%,那模型在低分段排得准不准其实无所谓。AUC 会把整条曲线都算进去,低分段的表现也会影响最终数值,这时候用 top-K 精确率或者 NDCG 之类的指标更贴切。
第三个边界是概率需要校准。AUC 只关心排序,不关心分数的绝对大小。一个 AUC 0.9 的模型,输出的“0.9 分”可能对应的真实概率只有 0.4。如果下游要用这个分数做期望损失计算、或者要和业务规则做阈值拼接,必须先做概率校准(Platt scaling 或 isotonic regression),否则分数没法当概率用。
第四个边界是多分类口径。多分类场景下 AUC 有 macro、micro、weighted 三种算法,结果能差好几个点。macro 是每类各算一个再平均,micro 是把多分类展平成二分类算,weighted 是按类样本数加权平均。报告里不写清楚用的哪种,读者会误读。类别不平衡时我一般用 macro 或 weighted,micro 会被大类主导。
4.3 该配哪些补充指标
单靠一个指标下结论总归不踏实,我通常会配一组指标一起看,各有分工:
| 指标 | 回答的问题 | 适合场景 |
|---|---|---|
| ACC | 按当前阈值切,对的比例多少 | 类别平衡、代价对称 |
| AUC | 整体排序能力如何 | 类别不平衡、阈值未定 |
| PR 曲线 / AP | 正类抓得准不准、全不全 | 正样本稀少 |
| KS | 正负样本分布的分离度 | 风控评分卡常用 |
| Lift | 头部人群比大盘好多少倍 | 营销、拉新 |
| Brier / 校准曲线 | 输出的分数能不能当概率用 | 需要算期望损失 |
| 召回率 @ 固定精确率 | 在可接受的误伤下能抓多少 | 审核、筛查 |
这张表不是让你全用上,而是让你在报告里说清楚“我为什么选这几个”。选了 AUC 不选 ACC,就要说明是因为阈值未定;选了 PR 不选 AUC,就要说明是因为正样本太少。有理由的选型,评审的时候才站得住。
5. 真实项目里的指标选择与报告写法
5.1 一张决策表把选型逻辑定下来
被问到“到底该看哪个指标”的时候,我一般会走一遍下面这个判断链,基本能覆盖八成场景:
| 判断条件 | 建议主指标 | 理由 |
|---|---|---|
| 正负样本比例在 4:6 到 6:4 之间 | ACC + AUC | 平衡场景下 ACC 不失真,AUC 补充全局视角 |
| 正样本占比低于 10% | AUC 或 PR-AUC | ACC 会被负样本主导 |
| 正样本少于 100 个 | PR-AUC + 置信区间 | AUC 方差大,PR 更稳 |
| 只关心 top 5% 人群 | top-K 精确率 / Lift | 全局指标掩盖头部表现 |
| 下游要用分数算钱 | AUC + 校准后概率 + Brier | 排序对不够,概率得准 |
| 阈值已由业务固定 | 该阈值下的混淆矩阵全套 | 这个点上的表现才是真实表现 |
走完这张表,基本能确定主指标是什么、辅助指标配哪几个。我现在的习惯是主指标最多两个,辅助指标不超过四个,报太多反而没人看,重点也被稀释了。
5.2 我踩过的几个坑
说几个实打实踩过的,都是常规文档里不会写的。
第一个是训练集 AUC 当交付指标。早期做的一个分类模型,训练集 AUC 0.98,我兴冲冲报上去,上线后掉到 0.6 出头。后来复盘发现是特征里有几个字段是事后回填的,训练时已经带了未来信息,属于典型的特征穿越。从那以后我的规矩是:任何指标只能报验证集或时间外样本上的结果,训练集指标仅用于判断是否欠拟合,不进报告。
第二个是线下线上不一致但没查分布。有一批模型的线下 AUC 稳定在 0.85,上线后只有 0.7 左右。排查了很久,最后发现是线上请求里有一批样本的特征缺失率远高于离线,缺失值填充逻辑两边不一致。这个坑的教训是:线下评估要尽量用和线上同一套特征处理链路,或者至少把缺失率分布对齐看一眼。
第三个是样本量小硬下结论。有个小场景的正样本只有 60 来个,两个版本模型 AUC 差 0.04,我当时直接说新版更好。后来做 bootstrap 一看,两个版本的置信区间大面积重叠,这个 0.04 完全在噪声范围内。之后凡是正样本低于 200 的场景,我都会跑一遍重采样,把区间一起报出去,宁可说“分不出”,也不给错的结论。
第四个是多分类 AUC 口径没写清。一次跨团队对齐,两边报的 AUC 差了 0.06,互相怀疑对方算错了。查了半天发现一个用 macro 一个用 micro,都是对的,只是口径不同。现在我的报告模板里固定写明“AUC 采用 macro 平均,各类等权”。
5.3 给不同的人看不同的数字
最后说一个汇报层面的经验。同一份评估结果,给三类人看要给三套说法。
给算法同学看,报 AUC、PR 曲线、KS、混淆矩阵、校准曲线,越细越好,他们要拿这些调模型。给业务同学看,把指标翻译成他们的语言:固定通过率 10% 的情况下能召回多少、误伤多少、每万笔里大概多少人会被误拦。给决策层看,直接换算成成本和收益,比如“这个阈值下每月多拦 200 笔风险单,同时多误伤 80 个正常用户,按单均成本折算净收益是多少”。
这套做法的好处是,没人会追着你问“AUC 0.9 是什么意思”。指标只是中间产物,最终要落到“所以要不要上、上哪个阈值”这个问题上。
我个人在实际操作中的体会是,AUC、ROC、ACC 这三个东西真正难的地方不在公式,而在于你要清楚每个数字背后的分母是谁、有没有被类别比例稀释、是不是绑定了某个隐含阈值。把这三个问题在报告里主动回答掉,评审时基本不会被问倒。另外有个小技巧:任何一次汇报前,先自己用一组极端数据把指标算一遍——比如全预测为负、全预测为正——看看报出来的数字是不是离谱到能被一眼看穿。这个自检花不了五分钟,但能挡住大部分口径错误。