元数据卡
- 前置知识:ch04-ch07 所有监督/无监督模型基础
- 预计时间:45 分钟
- 核心难度:进阶
- 阅读模式:高度专注
- 完成标志:能正确解读混淆矩阵和 ROC 曲线、用交叉验证和网格搜索调优模型
你的进度
模型工坊的墙上贴满了训练曲线。你盯着其中一张——训练损失一路向下,漂亮。但你把模型拿到工坊外的新数据上一测,结果一塌糊涂。
模型工坊的老学徒路过看了一眼:“你在训练集上过拟合了。”
你意识到:训练模型只是前半段。后半段是——怎么知道你的模型真的有用。
你的任务
模型评估不是简单看准确率。在类别不均衡、误分类代价不同、模型复杂度不你需要更精细的指标和更可靠的验证策略。本章补齐这个工具箱,最终教你系统地搜索超参数。
本章分层
- 必读:混淆矩阵、Precision/Recall/F1、ROC-AUC、交叉验证、GridSearch
- 选读:Bootstrap 置信区间、贝叶斯优化
- 进阶:统计假设检验比较模型
破局 · 溯源
你训练了一个疾病检测模型,准确率 99%——听起来很棒。但发病率只有 1%,所以一个永远说"健康"的模型准确率也是 99%。准确率在这个场景下完全没用。
你需要一套能揭示模型真实能力的评估工具。有些问题关注"别漏判"(召回率),有些关注"别误判"(精确率),有些两者都重要(F1)。
混淆矩阵
混淆矩阵是评估的起点。四个格子:真阳性(TP)、假阳性(FP)、真阴性(TN)、假阴性(FN)。
预测阳性 预测阴性
实际阳性 TP FN
实际阴性 FP TNfrom sklearn.metrics import confusion_matrix, classification_report
cm = confusion_matrix(y_test, y_pred)
print(cm)
# 更详细的报告
print(classification_report(y_test, y_pred))核心指标:
- 准确率(Accuracy):(TP+TN) / (TP+TN+FP+FN) — 只在平衡数据有效
- 精确率(Precision):TP / (TP+FP) — 你预测为阳性的结果中,有多少是真正的阳性
- 召回率(Recall/Sensitivity):TP / (TP+FN) — 所有真正的阳性中,你找出了多少
- F1 分数:2 * (Precision * Recall) / (Precision + Recall) — 精确率和召回率的调和平均
from sklearn.metrics import precision_score, recall_score, f1_score
print(f"Precision: {precision_score(y_test, y_pred):.3f}")
print(f"Recall: {recall_score(y_test, y_pred):.3f}")
print(f"F1: {f1_score(y_test, y_pred):.3f}")ROC 曲线与 AUC
很多模型输出的是概率而非硬分类。你可以调整阈值(比如大于 0.5 判为正,或大于 0.3 判为正)——每个阈值对应一个不同的假阳性率(FPR)和真阳性率(TPR)。ROC 曲线画出所有这些点的轨迹。
AUC(曲线下面积)告诉你模型的区分能力:AUC=0.5 就是随机猜,AUC=1.0 是完美分类器。
from sklearn.metrics import roc_curve, roc_auc_score
import matplotlib.pyplot as plt
y_proba = model.predict_proba(X_test)[:, 1]
fpr, tpr, thresholds = roc_curve(y_test, y_proba)
auc = roc_auc_score(y_test, y_proba)
print(f"AUC: {auc:.3f}")
plt.plot(fpr, tpr, label=f'AUC={auc:.3f}')
plt.plot([0, 1], [0, 1], 'k--')
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
# plt.show()PR 曲线在类别不平衡时比 ROC 曲线更有用:它关注少数类的表现,不受大量负类的拖累。
交叉验证
单次训练-验证拆分的结果方差很大——万一拆分时运气不好,验证集难或易,结果都会波动。K 折交叉验证将数据分成 K 份,轮流拿 K-1 份训练、1 份验证,取 K 次验证的平均值。
from sklearn.model_selection import cross_val_score
scores = cross_val_score(model, X, y, cv=5)
print(f"每折得分: {scores}")
print(f"平均: {scores.mean():.3f} (+/- {scores.std():.3f})")分层 K 折(Stratified K-Fold)保持每折中的各类比例与整体一致,对分类问题推荐使用。
from sklearn.model_selection import StratifiedKFold
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
for train_idx, val_idx in skf.split(X, y):
X_train_fold = X[train_idx]
y_train_fold = y[train_idx]
# ...超参数搜索
手动调参不可靠,也不可复现。系统化的搜索方法有两种:
- 网格搜索(Grid Search):枚举所有参数组合
- 随机搜索(Random Search):在参数空间中随机采样
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
param_grid = {
'max_depth': [3, 5, 7, 10],
'n_estimators': [50, 100, 200],
'min_samples_split': [2, 5, 10]
}
grid_search = GridSearchCV(
RandomForestClassifier(random_state=42),
param_grid,
cv=5,
scoring='f1',
n_jobs=-1,
verbose=1
)
grid_search.fit(X_train, y_train)
print(f"最佳参数: {grid_search.best_params_}")
print(f"最佳验证分数: {grid_search.best_score_:.3f}")
print(f"测试集分数: {grid_search.score(X_test, y_test):.3f}")随机搜索在高维参数空间中更高效:网格搜索的枚举次数随维度指数增长,随机搜索在同样预算下覆盖更多的参数"值"。
注意:交叉验证嵌套在网格搜索内部时(GridSearchCV 的 cv 参数),实际上已经有三层数据划分——内层 cv 做验证,外层 test 做最终评估。这是正确的做法,不会泄露测试集信息。
常见陷阱
- 在 GridSearchCV 中使用测试集 —— 如果你把 X_test 传给 fit,它会被用来评估。GridSearchCV 有自己的内部验证,外部应该还有单独的测试集。
- 多个指标优化时,需要指定 scoring 参数('f1', 'roc_auc', 'accuracy' 等),选跟业务目标最相关的一个。
- 交叉验证的 K 值选择:K=5 或 K=10 是常用值。K 太小(如 2)方差大,K 太大(如 20)训练集之间高度重叠,而且计算开销大。
- 数据泄露在交叉验证中:任何"看全局数据"的预处理(如在整个 X 上做 PCA 或标准化)必须在交叉验证循环内部做。
- 多次实验后选最佳模型,可能因为多重比较而过拟合验证集——需要嵌套交叉验证或用一个独立的测试集。
通关挑战
- 热身(10 分钟):在 sklearn 的 breast_cancer 数据集上,训练逻辑回归、随机森林、XGBoost,用 5 折交叉验证比较三者的平均 AUC。
- 挑战(30 分钟):用 GridSearchCV 调优一个 XGBoost 模型,搜索空间包括 max_depth (3~10)、learning_rate (0.01,0.1,0.3)、subsample (0.6~1.0)。打印最佳参数和测试集 F1 分数。
- 观察:在 ROC 曲线上标注几个不同阈值对应的点(0.3, 0.5, 0.8),观察阈值变化如何影响 Precision-Recall 的 trade-off。
旅人笔记
没有评价就没有进步。模型评估不是跑分游戏——它是你和模型之间的对话:你问"你学到了什么",评估指标回答"这些我知道了,这些我还不确定"。掌握它,你才能真正比较模型、选择模型、信任模型。
-> 下一站预告
评估就绪,你站在经典 ML 的终点和深度学习的起跑线上。下一章,神经网络——从感知机到反向传播。