模型评估与验证Model Evaluation & Validation
准确率够了吗?ROC、PR、学习曲线 — 系统性地回答"模型到底好不好"
模型评估 · 交互演示
为什么学这步?
训练出模型只是开始——更关键的问题是"它到底好不好"。准确率看似直观,却会在不平衡数据上撒谎:99% 都是负类时,全猜负也有 99% 准确率。真实场景里,医疗诊断怕漏诊(召回率要高),垃圾邮件过滤怕误杀(精确率要高),欺诈检测正类极稀少——单一数字回答不了这些侧重点不同的问题。这一步从"会训练"升级到"会评估",是模型走向工业落地的必备技能。
📌 发生了什么
- ROC 曲线扫阈值画 FPR-TPR 权衡,AUC 概括成一个数(0.5 瞎猜,1.0 完美)。
- PR 曲线正类稀少时比 ROC 更诚实,关注"报出来的正类有几分真"。
- 学习曲线回答"数据够不够"——缝隙大是过拟合,两线都高是欠拟合。
⚠️ 常见陷阱
- 准确率陷阱:不平衡数据下"全猜多数类"就能拿到高准确率,它衡量整体对不对,不是每类好不好。
- Precision 和 Recall 不能两全:调低阈值抓得全但冤枉多,调高阈值抓得准但漏得多,F1 是折中。
- 用训练集算指标?自欺欺人。必须用没见过的测试集才能反映泛化能力。
✅ 本章小结
- 准确率会骗人:不平衡数据下需配合 precision / recall / F1 多角度衡量。
- 混淆矩阵把 TP/FP/FN/TN 摊开,是所有二分类指标的源头。
- ROC/PR 曲线扫阈值画权衡,学习曲线回答数据够不够。
① ROC 曲线:阈值扫出来的"权衡线"
模型输出的不是"是/否",而是一个概率。把决策阈值从 1 慢慢降到 0,每取一个阈值,就得到一对数字:横轴 FPR(负类里被冤枉成正类的比例,"错杀率"),纵轴 TPR(正类里被成功找出的比例,就是召回率,"抓获率")。把这些点连起来就是 ROC 曲线。曲线越贴近左上角,说明模型越能"抓得全又不错杀"。AUC(曲线下面积)把它概括成一个数:0.5 等于瞎猜,1.0 是完美。
② PR 曲线:不平衡数据下更诚实的尺子
横轴是召回率(正类找回了多少),纵轴是精确率(你报的正类里有多少是真的)。ROC 里的 FPR 分母是庞大的负类,负类一多,错杀几百个 FPR 也纹丝不动;而精确率的分母是"你报出来的正类",每冤枉一个负类都直接拉低它。经验法则:正负均衡看 ROC,正类稀少看 PR。
③ 学习曲线:数据够不够,一看便知
做法是:只用 10 个训练样本训一个模型、再用 15 个、20 个……分别记录训练误差和测试误差。数据少时,模型能把几个样本背下来,训练误差(红)很低,但测试误差(蓝)很高——这就是过拟合的样子。两条线最终汇合的高度是模型的极限(欠拟合程度),中间的缝隙是过拟合程度。缝隙大 → 加数据有用;两线都高 → 该换更强的模型了。
📐 精确率、召回率与 F1
二分类混淆矩阵的四个基数(TP/FP/TN/FN)衍生出精确率、召回率和 F1。
F1 分数是精确率与召回率的调和平均,惩罚两者失衡:
更一般地,Fβ 加权调和平均可偏向召回率(β>1)或精确率(β<1):
多分类扩展:宏平均(每类独立计算后取均值)vs 微平均(全局 TP/FP/FN 汇总):
🎛 交叉验证方法对比
不同的评估方法在偏差与方差之间取舍。数据少时用 K 折,数据多时用留出法。
| 方法 | 表现 | 结果 |
|---|---|---|
| 留出法 (70/30) | 训练数据少,评估方差大 | 高方差 |
| 5 折交叉验证 | 偏差与方差均衡,业界默认 | 推荐 |
| 10 折交叉验证 | 训练数据更多,但计算量翻倍 | 更精确 |
| 留一法 (LOOCV) | 偏差最低但方差最高,计算极慢 | 仅小数据集 |
💡 Kohavi (1995) 实验表明:10 折交叉验证在多数场景下最优,既稳定又高效。
💻 混淆矩阵 + F1 计算
从预测结果构建混淆矩阵并计算精确率、召回率、F1(Python):
# 构建混淆矩阵
def confusion_matrix(y_true, y_pred):
tp = fp = fn = tn = 0
for i in range(len(y_true)):
if y_pred[i] == 1 and y_true[i] == 1: tp += 1
elif y_pred[i] == 1 and y_true[i] == 0: fp += 1
elif y_pred[i] == 0 and y_true[i] == 1: fn += 1
else: tn += 1
return tp, fp, fn, tn
# 计算精确率、召回率、F1
def metrics(cm):
tp, fp, fn, tn = cm
precision = tp / (tp + fp)
recall = tp / (tp + fn)
f1 = 2 * precision * recall / (precision + recall)
return precision, recall, f1
# K 折交叉验证
def k_fold_split(n, k):
idx = list(range(n))
# 打乱后均分 k 份,轮流取一份做验证集
return idx # 省略打乱逻辑
📚 参考文献与延伸阅读
- Kohavi, R. (1995). A Study of Cross-Validation and Bootstrap for Accuracy Estimation and Model Selection. IJCAI — 交叉验证方法对比的经典实验
- scikit-learn: Model Evaluation — 精确率、召回率、F1 的工业实现
- Powers, D. M. W. (2011). Evaluation: From Precision, Recall and F-Measure to ROC. — 各类评估指标的统一框架
- Wikipedia: Confusion matrix — 混淆矩阵的完整定义与扩展
📝 课后练习
检验你的理解——答对为止