模型评估
这一课你将能做到
- 看懂准确率、精确率、召回率、F1、AUC 各自衡量什么,遇到具体业务知道选哪个
- 用混淆矩阵和分类报告定位模型「错在哪一类」
- 通过调整决策阈值,在精确率和召回率之间做出符合业务代价的取舍
- 用交叉验证稳定评估模型,并识别过拟合与欠拟合
为什么要学
模型训练出来只是个数字对象,能不能上线取决于评估。评估做错,后面全错:一个把所有用户都判为「未掌握」的模型,在 92% 的人确实未掌握时准确率高达 92%,但它毫无用处——这正是本站做题场景会遇到的真数据形态(多数用户大部分题未掌握)。
很多初学者第一次翻车就翻在「用准确率评估不平衡数据」。本课把常用指标全部过一遍,并建立关键认知:指标不是背出来的,是按业务代价权衡出来的。
先建立图像
混淆矩阵(二分类,以「是否掌握」为例):
预测: 掌握 预测: 未掌握
真实: 掌握 TP(说对了) FN(漏报:该推的题没推)
真实: 未掌握 FP(误报:乱推难题) TN(说对了)
精确率 = TP / (TP+FP) 「说掌握的里面有多少真掌握」→ 关乎推题体验
召回率 = TP / (TP+FN) 「真掌握的里面找出了多少」 → 关乎别漏掉用户
跟着做
步骤 1:制造不平衡数据,先踩一次坑
import numpy as np
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.dummy import DummyClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score
# 模拟做题记录:仅 8% 的样本是「已掌握」
X, y = make_classification(n_samples=1000, n_features=6,
weights=[0.92, 0.08], flip_y=0.02, random_state=42)
X_tr, X_te, y_tr, y_te = train_test_split(
X, y, test_size=0.25, stratify=y, random_state=42) # 不平衡必须 stratify
dummy = DummyClassifier(strategy="most_frequent").fit(X_tr, y_tr)
print("全猜多数类的准确率:", accuracy_score(y_te, dummy.predict(X_te)))
# 输出约 0.92 —— 一个什么都不学的模型准确率也很高!
步骤 2:逻辑回归 + 混淆矩阵 + 分类报告
from sklearn.metrics import classification_report, confusion_matrix
clf = LogisticRegression(max_iter=1000).fit(X_tr, y_tr)
y_pred = clf.predict(X_te)
print(confusion_matrix(y_te, y_pred))
print(classification_report(y_te, y_pred,
target_names=["未掌握", "已掌握"], digits=3))
重点看「已掌握」那一行:precision、recall、f1。比例失真时 accuracy 无意义,这三列才是判断依据。
步骤 3:调阈值,做精确率/召回率权衡
proba = clf.predict_proba(X_te)[:, 1] # 预测「已掌握」的概率
for th in [0.2, 0.5, 0.8]:
pred = (proba >= th).astype(int)
cm = confusion_matrix(y_te, pred)
tp, fp, fn = cm[1, 1], cm[0, 1], cm[1, 0]
print(f"阈值={th} 精确率={tp/(tp+fp):.3f} 召回率={tp/(tp+fn):.3f}")
你会看到:阈值调高 → 精确率升、召回率降;调低则相反。业务翻译:推难题场景要精确率(乱推会伤体验),挽回流失用户场景要召回率(宁可多推不错漏)。阈值是产品开关,不是模型参数。
步骤 4:ROC 曲线与 AUC
import matplotlib.pyplot as plt
from sklearn.metrics import roc_auc_score, RocCurveDisplay
print("AUC =", roc_auc_score(y_te, proba).round(3))
RocCurveDisplay.from_estimator(clf, X_te, y_te)
plt.show()
AUC 的含义:随机抽一个正样本和一个负样本,模型给正样本打分更高的概率。0.5 等于瞎猜,0.8 以上算不错。它的优点是与阈值无关,适合比较不同模型的整体排序能力。
步骤 5:交叉验证与过拟合诊断
from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeClassifier
deep = DecisionTreeClassifier(random_state=42) # 不限深度
shallow = DecisionTreeClassifier(max_depth=3, random_state=42)
for name, m in [("不限深度", deep), ("max_depth=3", shallow)]:
tr = m.fit(X_tr, y_tr).score(X_tr, y_tr)
cv = cross_val_score(m, X_tr, y_tr, cv=5).mean()
print(f"{name} 训练集准确率={tr:.3f} 5折CV={cv:.3f}")
不限深度的决策树训练集能到 1.000,CV 却低不少——典型的过拟合(把训练集的噪声也背下来了);两个都低则是欠拟合(模型太简单)。
讲清楚要点
- 指标家族分工:F1 是精确率/召回率的调和平均,适合单一汇总;AUC 衡量「排序能力」与阈值无关;混淆矩阵回答「错在哪」。
- 不平衡数据切分必须 stratify=y,否则少数类在测试集里占比不稳,指标无法复现。
- 测试集是预算:每看一次、调一次,信息就泄漏一点。选超参数用验证集或交叉验证,测试集只在最终验收时碰一次。
- 过拟合是默认风险:模型容量越大越会背答案,正则化、限深、更多数据都是解药。
你可能会懵的地方
Q1:AUC = 0.75 到底算好还是不好? 看基线和业务。本站难度评分这类强信号问题里 0.75 偏弱;而「预测用户是否会流失」这种弱信号问题里 0.75 已可用。别背标准,跟「瞎猜 0.5」和现有规则基线比。
Q2:什么时候 PR 曲线比 ROC 更靠谱? 极端不平衡时(少数类 < 5%)。ROC 的分母含大量 TN,曲线容易「虚胖」好看;PR 曲线只关注少数类,更诚实。风控、疾病筛查场景优先看 PR。
Q3:阈值 0.5 能改吗? 能。模型输出的是概率,predict 只是「概率 ≥ 0.5 就判正」的默认规则。上线后完全可以根据业务代价改阈值,这是最廉价的模型优化手段。
Q4:测试集能反复用吗? 不能。反复用测试集调参等于把答案抄给了模型。正确做法:训练/验证/测试三分,或交叉验证选参 + 测试集终验。
Q5:过拟合和欠拟合信号怎么记? 「训练差、验证差」= 欠拟合 → 换更强模型、加特征;「训练好、验证差」= 过拟合 → 正则化、减特征、加数据。
错法对照
| 错法 | 后果 | 正确做法 |
|---|---|---|
| 不平衡数据看 accuracy | 92% 的假繁荣 | 看 precision/recall/F1/AUC + 混淆矩阵 |
| 在训练集上报告效果 | 严重高估 | train/test 分离 + 交叉验证 |
| 反复用测试集调参 | 测试集变相进训练集 | 验证集或 CV 选参,测试集只碰一次 |
| 切分不分层 | 指标抖动不可复现 | train_test_split(stratify=y) |
| 只盯单一指标 | 忽略错误分布 | 先看混淆矩阵,再定优化方向 |
卡点急救
- 报错
cannot import name 'RocCurveDisplay'→ sklearn 版本 < 0.24,用from sklearn.metrics import plot_roc_curve或升级。 - classification_report 出现 UndefinedMetricWarning → 某一类的预测数为 0(精确率分母为 0),说明阈值或模型有问题,不是代码错。
- cross_val_score 太慢 → 先在子样本上调通流程,或减小 cv 折数。
面试常问
Q1:精确率和召回率的取舍,举个例子? 垃圾邮件拦截宁可漏判(保精确率,别把正常邮件扔进垃圾箱);癌症筛查宁可错杀(保召回率,漏诊代价远大于误报)。取舍由错误代价决定,不由模型决定。
Q2:AUC 的统计学含义是什么? 随机取一正一负样本,模型给正样本打分更高的概率。它对阈值不敏感,衡量的是整体排序能力;但对不平衡数据的敏感性不如 PR 曲线。
Q3:为什么类别不平衡时不看 accuracy? 多数类占比本身就是 accuracy 的下界——全猜多数类即可拿到 92%,指标失去区分度。要用对少数类敏感的指标。
自测三问
- 一个模型召回率 1.0、精确率 0.08,它最可能怎么预测的?(几乎把所有样本都判为正类)
- AUC = 0.5 说明什么?(模型排序能力等同于随机瞎猜)
- 5 折交叉验证中,每份数据各当几次测试角色?(恰好 1 次)
小结一句话
评估的本质是把「模型错在哪、错了多贵」量化成业务看得懂的数字——指标服务于代价,而不是反过来。
延伸
- 官方文档:scikit-learn 模型评估
- 下一课预告:把预处理、调参、评估串成一条防泄漏的自动化流水线——Scikit-learn Pipeline 与 GridSearchCV。