1. 从一次“模型翻车”说起:准确率为什么不可信
做机器学习分类任务,我见过太多新同学跑完模型、盯着准确率(Accuracy)从 0.9 一直乐呵到 0.99,结果上线一测,业务方当场黑脸的场景。问题不在于数据没处理好,也不在于特征没洗干净,而是很多人从一开始就选错了评估指标。
就拿一个信贷反欺诈项目来说,某平台真实坏账率大概只有 2%。你做一个模型,无论输入什么,全部输出“正常”,准确率照样是 98%。如果拿准确率论英雄,这个“废物模型”比绝大多数真正的模型都好看。可它一个坏人都没拦住,风控形同虚设。这就是典型的“类别不平衡”陷阱,也正是 ROC 曲线和 AUC 值需要登场的理由。
ROC 的全称是 Receiver Operating Characteristic Curve,AUC 是 Area Under the Curve 的缩写。简单说,ROC 曲线是一条描述模型在“抓得到”和“抓得错”之间权衡的曲线,AUC 是这条曲线底下围成的面积。这两个指标最核心的优势在于:它们只看模型对正负样本的排序能力,对分类阈值不敏感,也不太受类别不平衡的影响。也就是说,就算你的正样本只占 1%,AUC 依然能真实反映模型“把好人坏人分开”的本事。
这篇文章不打算摆一堆数学公式吓唬人,我会从最朴素的问题出发,把混淆矩阵、ROC 曲线的绘制逻辑、AUC 的统计含义一点点拆开,再附上完整的 Python 实操代码和几个容易踩的坑。无论你是刚入行的数据新人,还是做了两年业务模型想回头补理论的老兵,这篇内容应该都能帮你把这块地基打稳。
2. 先搞懂地基:混淆矩阵、TPR 和 FPR
2.1 四象限:预测结果和真实结果对上了没有
聊 ROC 之前,绕不开混淆矩阵(Confusion Matrix)。这东西名字听着吓人,其实就是一张 2x2 的表格,把模型预测结果和真实标签做交叉统计。
借一个大家都有共鸣的场景:你开发了一个垃圾短信识别模型,每条短信的真实标签要么是“正常短信”(负类),要么是“垃圾短信”(正类)。模型预测完,可能出现四种情况:
- 真阳性(TP,True Positive):真实是垃圾短信,模型也判成垃圾短信,抓对了。
- 假阳性(FP,False Positive):真实是正常短信,模型误判成垃圾短信,误伤了。
- 真阴性(TN,True Negative):真实是正常短信,模型也判成正常短信,放行正确。
- 假阴性(FN,False Negative):真实是垃圾短信,模型漏判成正常短信,放走了坏人。
用表格列出来就一目了然:
| 预测为正类(垃圾短信) | 预测为负类(正常短信) | |
|---|---|---|
| 真实为正类(垃圾短信) | TP | FN |
| 真实为负类(正常短信) | FP | TN |
记住一个简单的理解方式:第一个字母(T/F)代表模型判得对不对,第二个字母(P/N)代表模型给出的预测方向。所以 FP 就是“预测错了,而且错在把负类说成了正类”,也就是我们常说的误报。
2.2 灵敏度、特异度和“误伤率”
混淆矩阵本身的统计意义有限,真正有价值的是从这四个格子推导出来的几个指标。ROC 曲线用到的两个核心指标是 TPR 和 FPR。
TPR(True Positive Rate),也叫真正率或灵敏度(Sensitivity)/召回率(Recall),公式是:
TPR = TP / (TP + FN)
它回答的问题是:所有真实的正样本里,模型到底抓住了多少?垃圾短信场景里,100 条垃圾短信模型拦住了 80 条,TPR 就是 0.8。这个值越高,模型“不漏事”的能力越强。
FPR(False Positive Rate),也叫假正率,公式是:
FPR = FP / (FP + TN)
它回答的问题是:所有真实的负样本里,模型误伤了多少?1000 条正常短信里有 50 条被误判成垃圾短信,FPR 就是 0.05。这个值越低,模型“不乱来”的能力越强。
还有一个常被顺带提起的指标叫特异度(Specificity),它等于 TN / (FP + TN) = 1 - FPR。特异性描述的是模型对负样本的判断能力,本质上和 FPR 是同一个信息的两种表述。记住 TPR 和 FPR 就够了,因为 ROC 坐标系横轴就是 FPR,纵轴就是 TPR。
2.3 准确率为什么会“骗人”
前面提过准确率的短板,这里再往深处说一点。准确率 = (TP + TN) / (TP + FP + TN + FN),它把四个格的贡献一股脑揉在了一起。在正负样本比例接近 1:1 的时候,准确率还算是有意义的参考;可一旦类别失衡,准确率马上变形。
我说个极端但很现实的例子:某疾病筛查场景,发病率是 1%。如果一个模型把所有人都判为“无病”,它的准确率是 99%。但哪个医生敢用这种模型?漏掉的那 1% 是活生生的人命。这时候真正需要关注的不是整体准确率,而是模型能不能“优先把真正有病的人排到最前面”,这就引出了 ROC 和 AUC 的价值:它们不依赖某个固定的判断阈值,而是评估模型在所有阈值下对正负样本排序的综合能力。
3. ROC 曲线是怎么一步一步画出来的
3.1 模型的输出不是“0 或 1”,而是“概率”
很多人有个误解,觉得分类模型输出的就是最终类别。实际上,像逻辑回归、随机森林、XGBoost 这类分类器,底层输出的往往是一个概率值或分数值,代表样本属于正类的置信度。比如垃圾短信模型给一条短信打出 0.87,意思就是“我有 87% 的把握这条是垃圾短信”。
那模型怎么决定最后的分类标签?需要一个阈值(Threshold)。大于阈值的判为正类,小于阈值的判为负类。阈值设 0.5 是最常规的做法,但 0.5 一定是最好的吗?不一定。在高风险场景里,你可能宁可多误报一些,也不想漏掉一个,这时候阈值可以调低;在精准营销场景里,你可能得严格控制骚扰比例,阈值就要抬高。
ROC 曲线妙就妙在:它把“阈值怎么选”这个问题暂时放一边,把从 0 到 1 所有可能阈值下的表现全部画出来,让你一眼看穿模型的本质能力。
3.2 从 1.0 到 0.0,阈值扫描就像安检仪调灵敏度
画 ROC 曲线的过程,核心是对每个样本的预测分数做排序,然后从最高分往最低分移动阈值。为了方便理解,我拿机场安检举例。
安检仪有一个“灵敏度旋钮”。旋钮调到最高时,任何可疑物都触发警报,坏人肯定跑不掉,但每位乘客的皮带扣、金属牙套都可能引发误报,队伍排到百米开外。旋钮调到最低时,警报几乎不响,大家畅行无阻,但真带了违禁品的人也大摇大摆过去了。
ROC 曲线的横轴 FPR,就是“误伤普通旅客的比例”;纵轴 TPR,就是“拦住坏人的比例”。你从头到尾拧一遍旋钮,每拧一档,记下一个(FPR, TPR)坐标,把所有这些点连起来,就是 ROC 曲线。
放到具体计算里,假设模型对 5 个样本给出如下预测概率:
| 样本 | 真实标签 | 预测概率 |
|---|---|---|
| A | 正 | 0.95 |
| B | 负 | 0.82 |
| C | 正 | 0.71 |
| D | 负 | 0.45 |
| E | 负 | 0.10 |
先从阈值 = 1.0 开始,所有样本都被判为负类,TPR = 0,FPR = 0,坐标系左下角原点的位置。
阈值降到 0.95,样本 A 被分到正类,它是真正的正样本,所以 TPR = 1/2 = 0.5,FPR = 0/3 = 0,画一个点 (0, 0.5)。
阈值降到 0.82,样本 A 和 B 都被判为正类。A 是真阳性,B 是误伤,所以 TPR 还是 0.5,FPR = 1/3 ≈ 0.333,画点 (0.333, 0.5)。
阈值继续往下降,每跨过一个样本的分数,就会新增一个点。最后阈值降到 0,全部样本都是正类,TPR = 1,FPR = 1,走到右上角 (1, 1)。把所有点用线段连起来,就得到一条阶梯状的 ROC 曲线。
3.3 曲线形状背后代表什么
观察这条曲线的形状有个很有意思的规律:好的模型,曲线会像一张弓一样向左上方拱起;差的模型,曲线几乎贴着对角线。
为什么?这要从 TPR 和 FPR 的“增长率”来解释。一个好的模型,应当把正样本的分数排在前面,负样本排在后面。所以当阈值从高往低扫时,一开始进来的是高分正样本,TPR 快速增长,FPR 几乎不动,曲线陡峭上升。等阈值继续降低,混进来的开始大量是负样本时,FPR 才开始追上,曲线变缓,往右上角靠。
如果模型纯粹靠瞎猜,也就是对正负样本的排序没有任何区分能力,那阈值每降一格,进来的样本中正负比例始终和整体分布一致,TPR 和 FPR 同步增长,画出来的曲线就贴着对角线 y = x。这时的模型等同于随机猜测,没有任何参考价值。
还有一种是“反向模型”——本来正样本分数该高,结果它给正样本打的分数普遍偏低。这时候曲线会跑到对角线下方。遇到这种情况也别慌,把预测分数取反(1 - score)再评估,曲线翻到对角线上面,往往就是一个能用的模型。这个细节在实际项目里救过我不少次,后面会再提到。
4. AUC:把二维曲线浓缩成一个数字
4.1 概率视角:随机挑一对,正样本排在负样本前面的概率
ROC 曲线本身是一条连续的二维曲线,要拿它做数值对比,还是不够直观。于是就有了 AUC,它定义很简单:曲线下方与横轴围成的面积。
如果只知道“AUC 是面积”这句话,其实没多大用,更关键的是它背后的概率解释。统计学上有一个漂亮的推导结论:
AUC 等价于:随机抽取一个正样本和一个负样本,模型给正样本打的分高于给负样本打的分概率。
再翻译一下:你从真实垃圾短信里随机抽一条,再从正常短信里随机抽一条,模型给出的评分中垃圾短信高于正常短信的概率。AUC = 0.9 意味着在 90% 的情况下,模型能把垃圾短信排在正常短信前面。
这个解释非常直观,也让 AUC 的含义脱离了具体的阈值和类别比例,变成了一个纯模型排序能力的度量。
4.2 0.5、0.7、1.0,这些数字都代表什么水平
不同取值区间对应不同含义:
| AUC 区间 | 含义 | 类比 |
|---|---|---|
| 0.5 | 纯随机,毫无区分能力 | 抛硬币 |
| 0.5 ~ 0.7 | 弱区分能力 | 看面相识人,时灵时不灵 |
| 0.7 ~ 0.85 | 中等偏上,多数场景可接受 | 考试成绩排名的靠谱程度 |
| 0.85 ~ 0.95 | 强区分能力,实际业务中非常理想 | 老刑警看监控辨认嫌疑人 |
| 0.95 ~ 1.0 | 极高,但要警惕是否过拟合或数据泄漏 | 闭卷考满分,罕见程度堪比奇迹 |
0.5 以下的情况前面说过,把预测分数取反就可能得到一个不错的模型。0.95 以上的模型也不要急着欢呼,先排查训练过程和测试集之间有没有信息泄漏,或者样本量是不是太小导致得分虚高。这些问题我放在最后一章完整展开。
4.3 AUC 和业务指标的关系:它不是万能的
AUC 不是越高越好,得和业务场景结合起来看。
在推荐系统场景,你关心的是“用户会不会点击”,AUC 可以衡量排序质量,但真正评估收入时还得看转化率、GMV 这些业务指标。在风控场景,AUC 高说明坏人好人都分得开,但最终给你核算 KPI 的往往是一个固定阈值下的精确率和召回率。也就是说,AUC 负责回答“模型本身行不行”,而精确率/召回率回答的是“我们选定的判定规则合不合适”。
我见过不少团队为了把 AUC 从 0.88 磨到 0.90,花了一周时间疯狂调参,结果上线后业务指标纹丝不动。不是说不该优化,而是你要清楚:AUC 衡量的是一段曲线下的平均表现,并不会自动告诉你“业务能挣多少钱”。先看 AUC 有没有让排序能力发生质的变化,再看固定阈值那一点的业务收益,两条腿走路才稳。
5. Python 实操:从零到一画出 ROC 曲线
5.1 先造一组有区分度的样本数据
实际项目里大多直接用真实数据,但为了讲清原理,我习惯先造一组可以手动验算的小数据,方便把每一步的结果都对得上。
下面这段代码生成 200 个样本,其中 40 个是正类,160 个是负类,正类样本的分数分布整体偏高一些。为了让演示效果明显,我给特征做了两簇高斯分布,让正负样本在特征空间上有一定重叠。
import numpy as np from sklearn.datasets import make_classification # 生成模拟二分类数据 X, y = make_classification( n_samples=200, n_features=5, n_informative=3, n_redundant=0, n_clusters_per_class=1, weights=[0.8, 0.2], # 负类占 80%,正类占 20% flip_y=0.1, # 加入 10% 的标签噪声 random_state=42 ) print(X.shape, y.shape) print("正类样本数:", np.sum(y == 1)) print("负类样本数:", np.sum(y == 0))5.2 用 sklearn 一行算出 TPR、FPR 并绘制曲线
接下来用一个最简单的逻辑回归模型,对数据做训练,输出每个样本的概率预测值,再用 sklearn 内置的 roc_curve 和 auc 完成核心计算。
from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import roc_curve, auc import matplotlib.pyplot as plt # 切分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.3, random_state=42, stratify=y ) # 训练模型 model = LogisticRegression(max_iter=1000) model.fit(X_train, y_train) # 预测正类的概率 y_prob = model.predict_proba(X_test)[:, 1] # 计算 ROC 曲线和 AUC fpr, tpr, thresholds = roc_curve(y_test, y_prob) roc_auc = auc(fpr, tpr) print("AUC:", round(roc_auc, 4))这时你已经拿到了三个关键数组:fpr(横轴数据)、tpr(纵轴数据)、thresholds(每个点对应的判断阈值)。注意 roc_curve 返回的 thresholds 和样本数不一定一样长,因为它是从最大概率值到最小概率值之间“有变化的位置”取的点,存在样本分数并列的情况时点会更少。
把图画出来:
plt.figure(figsize=(7, 6)) plt.plot(fpr, tpr, color="darkorange", lw=2, label=f"ROC curve (AUC = {roc_auc:.3f})") plt.plot([0, 1], [0, 1], color="navy", lw=1.5, linestyle="--", label="Random Guess") plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel("False Positive Rate") plt.ylabel("True Positive Rate") plt.title("ROC Curve Example") plt.legend(loc="lower right") plt.grid(alpha=0.3) plt.show()画出来之后,你会看到一条明显的“弓形”曲线,右上角附近还有一个类似“台阶”的平台。这些台阶不必惊讶,这是二分类样本有限时的正常现象——每个阈值移动都会引入离散的样本点变化,曲线因此呈阶梯状。样本越多、分数粒度越细,台阶感就越不明显。
5.3 手动实现一遍“阈值扫描”,彻底打通原理
想真正把 ROC 的机制刻进脑子里,我建议不要只停留在 sklearn 的封装调用上,而是自己用纯 Python 手动实现一遍。这个过程做一次,胜过读十遍概念。
思路很简单:先把所有测试样本按预测概率从大到小排序,然后设定一个初始阈值,让它从 1.0 逐步降到 0.0,每次变化后重新统计 TP、FP、FN、TN,计算相应的 TPR 和 FPR。
def manual_roc_curve(y_true, y_score): # 按分数降序排序,并让真实标签跟着排序 order = np.argsort(y_score)[::-1] y_true_sorted = y_true[order] y_score_sorted = y_score[order] n_pos = np.sum(y_true == 1) n_neg = np.sum(y_true == 0) fpr_list = [] tpr_list = [] thresholds = [] # 加一个阈值 1.0 的正无穷起点,保证从一开始全判负 unique_scores = np.unique(y_score_sorted)[::-1] # 先记录阈值为无穷大时的点 fpr_list.append(0.0) tpr_list.append(0.0) tp = 0 fp = 0 for score in unique_scores: # 将所有分数 >= score 的样本视为正类 mask = y_score >= score # 当前阈值下的 TP 和 FP tp = np.sum((y_true == 1) & (y_score >= score)) fp = np.sum((y_true == 0) & (y_score >= score)) tpr = tp / n_pos fpr = fp / n_neg fpr_list.append(fpr) tpr_list.append(tpr) thresholds.append(score) return np.array(fpr_list), np.array(tpr_list), np.array(thresholds) fpr_manual, tpr_manual, thresh_manual = manual_roc_curve(y_test, y_prob) # 计算手动版 AUC(用梯形法) def manual_auc(fpr, tpr): # 按 fpr 排序,保证梯形计算顺序正确 order = np.argsort(fpr) fpr_sorted = fpr[order] tpr_sorted = tpr[order] area = 0.0 for i in range(1, len(fpr_sorted)): area += (fpr_sorted[i] - fpr_sorted[i-1]) * (tpr_sorted[i] + tpr_sorted[i-1]) / 2 return area print("sklearn AUC:", round(roc_auc, 4)) print("手动实现 AUC:", round(manual_auc(fpr_manual, tpr_manual), 4))你会发现两个数值几乎完全一致。原因很简单:所谓 AUC,本质上就是把相邻两个 (FPR, TPR) 点用梯形公式求面积。sklearn 内部也是这么算的,连边界情况处理方式都一样。
5.4 实操中的一个小细节:正负样本分数完全一样时怎么办
手动实现的过程中你可能会遇到一个问题:多个测试样本的预测分数完全一样。比如逻辑回归在特征比较稀疏时,很容易输出相同概率。
这时按分数降序排序后,同一分数对应的多个样本,阈值跨越这个分数时,TP 和 FP 的增量应该同时计算,而不是逐个样本跳变。上面的代码在 for 循环里用 y_score >= score 一次性统计所有大于等于该分数的样本,天然就处理了这个并列问题。
不过 sklearn 内部的处理排序规则还有一处细节:当两个样本分数相同但标签不同时,它会按“正样本优先”的方式排列,这样画出的曲线会更乐观一点点,整体影响微乎其微。了解这个小细节后,以后看到 sklearn 和手写实现有微小偏差时就不会慌。
6. 使用 AUC 评估时常见的坑与调优指南
6.1 AUC 很高但实际效果一塌糊涂?
这种情况我在真实项目里遇到过不止一次。有个朋友做流失预警模型,线下测试 AUC 高达 0.93,信心满满地上线,结果对真实流失用户的命中率不到三成。排查到最后发现两个问题:
第一,样本泄漏。他在特征工程里不小心把“用户近 7 天是否发起过投诉”这类事后变量作为特征喂给了模型。训练阶段模型拿到的是“未来的答案”,自然 AUC 高得离谱。一旦真实的预测场景里做不到时间穿越,模型就现出原形。排查方法也简单:用时间序列交叉验证替代普通随机切分,看每个时间窗上的 AUC 是否还稳定。
第二,上线后特征分布发生变化。线下训练数据的某些特征,到了线上因为埋点问题大量变成默认缺失值,模型拿着残缺输入自然表现拉胯。所以我养成了一个习惯:上线前把训练集、验证集、线上实际输入三者的特征分布都做一次对比检查,分布差异太明显的特征直接剔除。
6.2 类别极端不平衡,AUC 就完全可信吗
ROC-AUC 对类别不平衡的敏感度低,是它相比准确率的优势,但“低敏感”不等于“完全免疫”。
当负样本数量远远大于正样本时,比如 10000 个负样本和 10 个正样本,一个二分类模型很可能输出“几乎全是负类”的保守策略,此时 FPR 会一直非常低,因为负样本基数太大,误伤一部分比例上微乎其微,ROC 曲线会显得很“漂亮”,AUC 数值也很可观。但实际上它可能一个正样本都没抓住。
针对这种场景,我更推荐同时看 PR 曲线(Precision-Recall Curve)和 PR-AUC。PR 曲线以召回率为横轴、精确率为纵轴,对正样本非常敏感,能更真实地反映“正样本找得准不准”。一个重要的经验判断:当正样本比例小于 10% 时,优先关注 PR-AUC;比例接近均衡时,ROC-AUC 是稳妥的选择。
6.3 样本量太小,AUC 波动大到怀疑人生
假设测试集只有 30 个样本,其中 5 个是正类,25 个是负类。你随手改一个随机种子,AUC 可能从 0.85 掉到 0.62,另一个种子又冲回 0.9。这不是模型不稳定,是样本量太小导致统计波动。
小样本场景下的应对方案,我推荐两种:
第一种是使用置信区间辅助判断。用 Bootstrap 法对测试集重采样若干次(比如 1000 次),每次算 AUC,得到 AUC 的分布,然后给出 95% 置信区间。如果区间宽度超过 0.1,说明样本量不足以支撑可靠的结论,需要继续扩大测试集。
from sklearn.utils import resample def auc_bootstrap_ci(y_true, y_score, n_bootstrap=1000, alpha=0.95): rng = np.random.RandomState(42) aucs = [] n = len(y_true) for _ in range(n_bootstrap): idx = rng.randint(0, n, size=n) if len(np.unique(y_true[idx])) < 2: continue fpr_i, tpr_i, _ = roc_curve(y_true[idx], y_score[idx]) aucs.append(auc(fpr_i, tpr_i)) lower = np.percentile(aucs, (1 - alpha) / 2 * 100) upper = np.percentile(aucs, (1 + alpha) / 2 * 100) return np.mean(aucs), lower, upper mean_auc, ci_low, ci_high = auc_bootstrap_ci(y_test, y_prob) print(f"AUC 均值: {mean_auc:.3f}, 95% CI: [{ci_low:.3f}, {ci_high:.3f}]")第二种是使用分层 K 折交叉验证,把每个 fold 算出来的 AUC 汇总起来看均值和方差。Sklearn 里有专门的 cross_val_predict,配合 StratifiedKFold 使用即可。
6.4 多分类任务的 ROC-AUC 怎么处理
ROC-AUC 最初是为二分类设计的,遇到三分类以上就会懵。处理多分类的标准做法是“一对多”(One-vs-Rest),即把每个类别单独拎出来当作正类,其他所有类别当作负类,分别计算该类别对应的一组 ROC-AUC。
Sklearn 里直接支持:
from sklearn.multiclass import OneVsRestClassifier from sklearn.metrics import roc_auc_score # 假设 y_prob 是一个 (n_samples, n_classes) 的预测概率矩阵 auc_macro = roc_auc_score(y_test, y_prob, multi_class="ovr", average="macro") auc_weighted = roc_auc_score(y_test, y_prob, multi_class="ovr", average="weighted") print(f"Macro AUC: {auc_macro:.3f}, Weighted AUC: {auc_weighted:.3f}")macro 平均对每个类别一视同仁,不管该类别的样本量多小;weighted 平均会按类别样本量加权。实际使用中,如果类别严重不平衡,weighted 更能反映总体表现,但 macro 能暴露出“少数类到底分不分得开”这个问题。两个指标放在一起看才是上策。
6.5 一个调优和上线前的检查清单
踩了这么多坑之后,我给自己总结了一个评估模型时的心智清单,现在分享出来,每次建模都要照着过一遍:
- 拿到一个分类模型,先用 stratify 或分层 K 折切分数据,不能用默认的裸切分。
- 同时计算 accuracy、AUC、F1 和 PR-AUC,避免单一指标误导。
- 绘制 ROC 曲线时,别忘了把随机猜测线画上去,曲线越贴近左上角越好。
- 检查训练集和验证集的正负样本比例差异,异常时先查数据泄漏。
- 确认所有特征在在线预测时都能实时拿到,剔除事后变量。
- 上线前用最新一段时间的真实数据做离线回放,再算一次 AUC。
- 给 AUC 附上 Bootstrap 置信区间,业务方看到的不只是一个干巴巴的点。
7. 写在最后:把我踩过的坑浓缩成三句话
回头再看 ROC 曲线和 AUC 值,它们不过是对“模型能不能把正样本排到前面”的一次定量测量。对我个人而言,最深刻的心得体会是:评估指标永远是为了回答业务问题而存在的,别本末倒置。AUC 不能替你决定阈值,不能替你把业务目标翻译成数字,也不能自动发现数据泄漏,它能做的就是诚实地展示模型排序能力。
我自己在项目里吃过最大的亏,是拿到一个 AUC 0.92 的模型后满心欢喜地交了出去,结果因为没做特征时间一致性检查,上线第一周就被线上数据打脸。从那以后我落下一个习惯:每次评估模型不只看一个指标,也不只跑一次实验,而是把 ROC 曲线、PR 曲线、置信区间、时间切片全都要过一遍。这一套流程坚持了几年,确实把“模型翻车”的概率压低了很多。
如果你正准备拿这个东西去评估手头的模型,我建议你有空也把自己关起来,用 sklearn 和手写代码各跑通一遍 ROC 曲线的计算流程。这个动作花不了半小时,但能把理论真正焊死在脑子里。等以后再有人跟你讲“AUC 很高”,你可以从容地反问一句:你说的是哪个数据集上的 AUC?有没有置信区间?特征里夹带了未来信息吗?能把这三个问题答上来,这条路你就走稳了。