简介这是一份系统讲解机器学习模型评估与模型选择的PPT课件围绕泛化误差与经验误差、过拟合与欠拟合、留出法/K折交叉验证/自助法、错误率与精度、查准率查全率与F1、ROC与AUC、集成学习等主题展开基本覆盖从入门到进阶的关键知识点。课件先讲评估方法再讲性能度量最后落到比较检验与集成策略结构清晰适合机器学习初学者、高校学生期末复习以及算法工程师在面试前快速回顾理论要点。资源包共1个文件类型为ppt压缩包大小3MB内容精炼便于直接阅读、打印或二次整理。已有146人浏览学习。通过这份PPT可以系统梳理模型评估与选择的方法脉络理解不同评估方法各自的适用条件与数据利用方式掌握回归和分类任务中性能度量的选择依据建立从单个模型评估到集成对比的完整思路。1. 机器学习之模型评估与模型选择先回答“模型到底行不行”再谈调参“模型评估与模型选择”是机器学习项目里最容易被跳过的环节。很多人以为它只是讲准确率和交叉验证真正落地才发现数据怎么切、指标选什么、参数调到哪一步停直接决定模型上线后会不会翻车。这部分内容在周志华《机器学习》配套PPT、头歌实训和各类机器学习期末复习提纲里都是必讲章节属于看着简单、做起来全是细节的知识点。这里把每一页概念拆成可复现的操作先定评估协议再选指标再做模型比较最后把流程固化成一个模板。适合正在跑实验、想搞清楚“这个模型到底能不能用”的从业者和备考学生。2. 数据划分与交叉验证落地本地验证分数不能直接当上线成绩确定评估协议的第一步不是选模型而是决定数据怎么切。如果拿同一份数据既训练又汇报成绩你看到的只是模型把数据背下来的程度。模型评估要模拟线上预测新样本的过程因此训练集、验证集、测试集三者必须各司其职。2.1 留出法是“能跑但波动不小”的第一个选择最常见也最省事的数据划分是留出法按比例把样本分为训练集和测试集训练集拟合模型测试集模拟未来数据。比例上我一般默认 8:2数据量特别大十万条以上时可以用 9:1教学演示常用 7:3实际项目里很少用因为测试集太小会让最终评估显著受噪声影响。对分类问题划分时要做分层抽样。所谓分层就是让训练集和测试集中每个类别的占比尽量和全集一致。例如总体里正类占 10%那么切出去的测试集里也应当大约占 10%。这样做能避免小样本下某一类全部掉进训练集、测试集里只剩另一类的情况。scikit-learn 的 train_test_split 加上 stratifyy 参数就能做到这一点。留出法最大的弱点是只切一次结果受随机划分影响很大。同一个模型换一个随机种子测试分数可能差几个百分点。它适合在数据量非常大、或只是想快速看通路的场景下使用如果数据只有几千条我会毫不犹豫改用交叉验证。2.2 用 scikit-learn 做一次标准的留出法与 K 折交叉验证先把测试集切出去再用交叉验证训练一个基准模型from sklearn.model_selection import train_test_split, StratifiedKFold, cross_val_score from sklearn.datasets import load_breast_cancer from sklearn.linear_model import LogisticRegression # 加载数据一次划分测试集只用于最后评估 X, y load_breast_cancer(return_X_yTrue) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) # 在训练集内部做5折分层交叉验证得到模型的泛化估计 cv StratifiedKFold(n_splits5, shuffleTrue, random_state42) model LogisticRegression(max_iter1000) scores cross_val_score(model, X_train, y_train, cvcv, scoringaccuracy) print(fCV accuracy: mean{scores.mean():.4f} std{scores.std():.4f}) # 用完整训练集拟合模型最后在测试集上报一次分数 model.fit(X_train, y_train) print(fTest accuracy: {model.score(X_test, y_test):.4f})这里有几个参数值得说清楚。test_size 是测试集占比0.2 意味着 20% 的样本进入测试集stratifyy 强制分层抽样random_state 控制随机数生成器设成同一个值才能在不同实验间复现划分结果。StratifiedKFold 是分层 K 折交叉验证把训练集均分成 5 份每次拿 4 份训练、1 份评估轮流 5 次。和普通 KFold 的区别是每一折的类别比例也尽量与全集一致。cross_val_score 返回每一折的得分因此 scores 是 5 个数的数组。打印时我把均值和标准差一起输出这比只看平均值重要得多如果均值 0.96 但标准差 0.08说明模型在不同数据划分下表现差异很大均值 0.93 但标准差 0.02 的模型反而更可预测。还要注意测试集必须留到所有调参结束后再碰。如果每次实验都拿测试分数决定下一步怎么调那测试集的信息已经通过你的手写进了模型最后的测试分数就是虚高的。2.3 自助法与留一法小样本场景的备用方案交叉验证不是唯一的评估方式。样本量很小比如几百条以内时留出法浪费太多训练数据这时候留一法是一种常见选择每次只留一个样本作为测试集其余全部用于训练重复 n 次。它的优点是训练数据利用充分缺点是训练 n 次模型计算成本高而且每次只测一个样本结果波动大。如果样本不是独立同分布比如同一用户的多条记录留一法还会高估模型表现所以我很少把它当作默认方案。自助法则更常出现在课程和头歌实训题里从 n 个样本中有放回地抽样 n 次作为训练集没被抽到的大约 36.8% 的样本作为袋外数据。可以用下面这段代码直观理解import numpy as np rng np.random.default_rng(2024) n 80 train_idx rng.choice(n, sizen, replaceTrue) # 有放回抽样 oob_mask ~np.isin(np.arange(n), train_idx) print(训练集实际去重样本数:, np.unique(train_idx).size) print(袋外样本数:, int(oob_mask.sum()))n 在这里代表总样本数rng.choice 从 0 到 n-1 中有放回地抽取 n 次因此同一个样本可以出现多次也有约三分之一的原始样本完全没进训练集这部分就是袋外样本。随机森林里的 oob_score 用的正是这个思路。但作为独立的模型评估流程自助法会引入抽样分布偏差在实际算法选型时很少单独使用它更适合估计置信区间或者在做集成学习时顺带得到验证分数。3. 评估指标怎么选分类看 F1 和 AUC回归看误差的形状数据划分定了之后第二个要下决心的是“拿什么数字衡量好坏”。同一个模型在不同指标下可能一好一坏一个高 recall 的模型可能 precision 很低一个 MAE 很低的上线系统可能在个别极端样本上预测得离谱。指标不是越多越好而是要和你业务的代价对齐。3.1 分类指标准确率在什么场景下会骗人准确率是最直观的分类指标猜对的样本数除以总样本数。问题在于它把每个样本的代价看成一样。反欺诈场景里正常交易占绝大多数模型只要把所有样本都判成正常准确率就能到 99%但一个欺诈都抓不到。这种“复读机”模型在评估报告上很好看上线后没有任何价值。于是有了查准率和查全率习惯上叫 precision 和 recallprecision 是预测为正类的样本里真正为正类的比例recall 是所有真正为正类的样本中被召回的比例。F1 是两者的调和平均数值高意味着两个指标都不差。具体选哪个要看漏报和误报哪个代价更高垃圾邮件拦截宁可漏掉一两封也不希望误杀正常邮件所以更看重 precision疾病筛查宁可多查一批人也不希望漏掉真正的患者所以更看重 recall。多分类场景里还会看到 macro、micro、weighted 三种平均法。macro 对每个类算指标再取平均给少数类同等话语权weighted 按每个类的样本数加权受大类影响更大micro 在样本分布极度不均时结果接近总体准确率。选哪种没有绝对对错但要写清楚否则后续比较模型时口径对不上。3.2 回归指标MSE、MAE 与 R² 各自的脾气回归任务最常用的三个指标是均方误差 MSE、平均绝对误差 MAE 和决定系数 R²。MSE 先平方再平均会把“离群点”的误差放大一个误差 10 的样本在 MSE 里等于 100 个误差 1 的样本。这样模型会优先去拟合那些极端样本如果业务上极端样本本来就不重要MSE 会把你带歪。RMSE 是 MSE 的平方根把单位还原回预测目标的单位解释起来方便一些但对离群点的惩罚依然存在。MAE 直接对误差取平均每个样本的影响相同业务上想知道“平均差多少”时我一般看 MAE。它的缺点是误差在零附近不光滑某些优化算法用起来不如 MSE 顺手。R² 则是一个相对指标模型误差比“永远预测均值”这个基线模型小了多少。R² 越接近 1 说明模型的残差占比越低如果 R² 是负数说明模型还不如直接取均值这时候要回头检查特征或模型选择是否出了问题。实际项目里我不会只挑一个指标用常见做法是 R² 和 RMSE 一起报前者看整体拟合程度后者看误差具体量级。3.3 用一份代码同时看分类场景的四个常用指标下面这份代码用一个随机森林分类器同时输出准确率、precision、recall、F1 和 ROC-AUC用来对比指标间的差异from sklearn.metrics import ( accuracy_score, precision_score, recall_score, f1_score, roc_auc_score ) from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import train_test_split from sklearn.datasets import load_breast_cancer X, y load_breast_cancer(return_X_yTrue) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.3, stratifyy, random_state7 ) clf RandomForestClassifier(n_estimators100, random_state7) clf.fit(X_train, y_train) y_pred clf.predict(X_test) y_prob clf.predict_proba(X_test)[:, 1] print(fAccuracy : {accuracy_score(y_test, y_pred):.4f}) print(fPrecision: {precision_score(y_test, y_pred):.4f}) print(fRecall : {recall_score(y_test, y_pred):.4f}) print(fF1 : {f1_score(y_test, y_pred):.4f}) print(fROC-AUC : {roc_auc_score(y_test, y_prob):.4f})代码里有个容易踩坑的细节roc_auc_score 的第二个参数必须是正类的预测概率不是预测类别。sklearn 中二分类默认把数值较大的类别当正类如果数据里负类不是 1要用 pos_label 参数显式指定。precision、recall、F1 依赖 0.5 这个默认判定阈值一旦你调整阈值这三个数字都会变化。ROC-AUC 只和模型对样本的排序能力有关不依赖具体阈值因此在对类别不平衡做阈值调整时我优先用 ROC-AUC 比较模型再根据实际成本在验证集上定阈值。4. 模型选择的底层逻辑偏差-方差、统计检验与调参停止点模型选择的“选择”两个字很容易被理解成跑一堆模型、挑最高分。实际上模型选择要回答两个问题分数差距是不是真实差距参数调到什么程度该停这两个问题的答案都在评估理论里。4.1 偏差-方差分解为什么“训练分数高”和“泛化好”是两回事泛化误差通常可以分解成偏差、方差和噪声三部分。偏差衡量的是模型预测期望与真实值的差距方差衡量的是模型对训练集变化的敏感程度。高偏差通常发生在模型过于简单时训练误差和测试误差都高特征是“两条曲线一起高”高方差则发生在模型过于复杂时训练误差很低测试误差高特征是“两条曲线开口拉大”。调参的过程本质上是在找偏差和方差的平衡点不是在追训练集上的满分。用决策树深度举例深度太小模型学不到样本里的结构训练和测试都差深度太大模型把训练样本的个别噪声也记住了测试误差开始上升。常见的做法是画一条有谷底的曲线横轴是复杂度纵轴是误差两条线分别表示训练误差和测试误差测试误差的最低点对应的是一个可用的复杂度区间。实际操作里很多人的做法是直接选“训练误差最低”的那个参数这其实是把测试曲线上升段的风险都忽略了。4.2 模型比较不能只看均值用重采样加配对 t 检验做决定两个模型的交叉验证均值差 0.01不代表 A 比 B 好。K 折交叉验证本身包含数据划分带来的随机性均值差可能完全来自噪声。正确做法是重复多次交叉验证得到一批分数再做统计检验。下面这段代码对同一个数据集重复 20 次 5 折分层交叉验证比较随机森林和逻辑回归from scipy import stats from sklearn.model_selection import StratifiedKFold, cross_val_score, train_test_split from sklearn.linear_model import LogisticRegression from sklearn.ensemble import RandomForestClassifier from sklearn.datasets import load_breast_cancer import numpy as np X, y load_breast_cancer(return_X_yTrue) X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) def repeated_cv_accuracy(model, X_tr, y_tr, repeats20, n_splits5): mean_scores [] for r in range(repeats): cv StratifiedKFold(n_splitsn_splits, shuffleTrue, random_state100 r) scores cross_val_score(model, X_tr, y_tr, cvcv, scoringaccuracy) mean_scores.append(scores.mean()) return np.array(mean_scores) rf_acc repeated_cv_accuracy(RandomForestClassifier(n_estimators50, random_state1), X_train, y_train) lr_acc repeated_cv_accuracy(LogisticRegression(max_iter2000), X_train, y_train) print(fRF mean: {rf_acc.mean():.4f} ± {rf_acc.std():.4f}) print(fLR mean: {lr_acc.mean():.4f} ± {lr_acc.std():.4f}) t_stat, p_value stats.ttest_rel(rf_acc, lr_acc) print(fpaired t-test p-value: {p_value:.4f})逻辑说明repeated_cv_accuracy 的 repeats20 表示完整跑 20 次交叉验证每次用不同的随机种子mean_scores 保存每次 5 折的平均精度最终得到 20 个相互配对的观测值。因为两个模型在同一批随机划分上评估配对样本可以消掉数据划分本身的影响更接近模型差异本身。stats.ttest_rel 返回配对 t 检验的 p 值p 小于 0.05 时我们认为这个差异在统计上显著如果 p 大于 0.05即使均值有 0.01 的优势也更可能是噪声。这里要提醒一句这个 p 值理论上是近似结果因为不同折之间共享训练样本独立性并不严格成立。但它是工程上最实用的快速判断手段。更严谨的方法还有麦克尼马尔检验和 Friedman 检验前者适合比较两个分类器的预测结果后者适合在多个数据集或多个模型间做整体排序。做研究或期末作业时这两类检验经常作为加分项出现但日常项目里先用好配对 t 检验已经能挡掉一半错误结论。4.3 网格搜索与随机搜索什么时候改参数什么时候改特征手动比较模型之后下一步通常是对候选模型做超参数搜索。参数空间比较小的时候网格搜索足够参数空间很大例如同时搜索多个预处理器和多个模型参数随机搜索的性价比更高先用随机采样画出一个有希望的区间再缩小范围做网格搜索。搜索器里有个容易被忽视的参数是 scoring。sklearn 的 GridSearchCV 不写 scoring 时默认用模型自带的 score 方法分类问题就是 accuracy。如果你在做一个正类只占 2% 的业务那搜索器会按照准确率选参数结果往往完全不对路。我一般会在搜索器里显式写上 scoringf1 或 scoringroc_auc如下from sklearn.model_selection import GridSearchCV from sklearn.ensemble import RandomForestClassifier # X_train, y_train 沿用前面划分好的训练集 param_grid { n_estimators: [50, 100, 200], max_depth: [4, 6, 8, None], } search GridSearchCV( RandomForestClassifier(random_state0), param_grid, scoringroc_auc, cv5, n_jobs-1, ) search.fit(X_train, y_train) print(search.best_params_) print(search.best_score_)这段代码里的 cv5 表示每一组参数都做 5 折交叉验证n_jobs-1 使用所有 CPU 核并行scoringroc_auc 让搜索目标与业务对齐。search.best_score_ 是交叉验证平均分它不等于测试集最终表现只是一个选择依据。调参停止点还有一个经验法则如果搜索过程中多组参数的 CV 分数差都在标准差以内别再膨胀参数空间。继续加候选参数只会增加在训练集上碰运气选到高分再正常上线却打折扣的几率这个现象本质上是多重比较陷阱。与其继续加参数不如回头看特征工程和数据质量。5. 模型评估与模型选择的 5 个避坑记录现象、原因、解决这一章把项目里反复出现的坑按“现象 - 原因 - 解决”整理出来。每一条我都实际遭遇过或帮别人排错过贯穿了两类问题一类是数据划分和预处理泄漏一类是指标和评分函数选择偏了。5.1 坑一拿测试集反复调参模型成绩虚高一截现象同一个测试集被用于每一轮实验整个调参周期里分数从 0.90 一路涨到 0.96可一上线真实表现只有 0.87。原因测试集的信息通过你的调参动作进入了模型。你每看一次测试分数测试集就开始参与模型选择最后它就不再是“未见过的数据”了。解决把数据一次性切成训练集、验证集和测试集三份调参只用验证集或交叉验证测试集只允许在最终方案确定后碰一次。如果测试结果不理想要回头继续调也要重新划分数据而不是继续用同一个测试集刷分。这个坑在教学场景尤其常见。很多人在做课程设计时训练集和测试集没有区分直接在整个数据集上训练再汇报准确率属于典型的“假成绩”。把它记住面试时被问到“为什么你的测试集分数不能完全相信”时也能对应上。5.2 坑二类别不平衡时只看准确率模型变成“复读机”现象二分类任务中正类占比只有 1%模型把所有样本都判为负类准确率达到 99%。如果把测试代码打印出来看混淆矩阵会发现正类的召回率是 0。原因准确率这个指标被多数类主导模型“猜多数”就能拿高分。解决改用 ROC-AUC、F1 或 recall 评估训练时给少数类更高的代价最简单的做法是在模型里设置 class_weightfrom sklearn.linear_model import LogisticRegression model LogisticRegression(class_weightbalanced, max_iter1000)class_weightbalanced 会自动按类别频率反比调整权重让少数类样本的损失占比更大。这样做的代价是 precision 可能会降所以不能只看一个指标孤零零地报结论。更完整的做法是结合混淆矩阵观察真正例、假正例、假负例分别有多少再决定是否需要调整判决阈值。5.3 坑三数据泄漏——预处理写在了划分之前现象交叉验证分数看似非常完美比如 0.98但线上只有 0.82排查时发现 StandardScaler 是在整体数据上 fit 后再划分的。原因数据标准化计算均值和方差时用到了测试集的统计量相当于测试集信息早已进入训练环节这就是数据泄漏。解决把预处理放进 Pipeline只在训练集上 fit再应用到验证集和测试集。下面这段代码展示了正确做法和错误做法的区别# 错误做法scaler 在全量数据上拟合测试集统计量已经泄漏进训练流程 from sklearn.preprocessing import StandardScaler from sklearn.model_selection import train_test_split scaler StandardScaler() X_scaled scaler.fit_transform(X) X_train, X_test, y_train, y_test train_test_split( X_scaled, y, test_size0.2, random_state42 ) # 正确做法用 Pipeline 把预处理和模型绑在一起 from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, random_state42 ) pipe Pipeline([ (scaler, StandardScaler()), (model, LogisticRegression(max_iter1000)), ]) pipe.fit(X_train, y_train) pipe.score(X_test, y_test)注意看正确做法里StandardScaler 从未接触 X_test数据划分发生在前预处理接口拆分在后。交叉验证时 Pipeline 会在每一折内部重新 fit确保每一折的预处理只基于当前训练折。数据泄漏不只发生在标准化上缺失值填充、PCA、特征选择都有可能踩同一坑。5.4 坑四交叉验证结果随随机种子剧烈波动均值好像都差不多现象把 random_state 从 1 改成 42某个模型的 CV 均值从 0.85 跳到 0.88另一个模型反而从 0.87 掉到 0.84用不同种子多跑几次结论都是反的。原因样本量不够大或者数据划分时类别比例没有控制好导致每一折的分布差异很大。交叉验证的均值本身带有方差只看一次单点平均值会被随机性带偏。解决记录每次实验的均值和标准差多次重复交叉验证比较模型时用配对 t 检验把“噪声带”算出来。如果两个模型的 CV 均值差小于 1 个标准差我一般直接选更简单的模型上线而不是继续调参。5.5 坑五网格搜索用默认评分函数选出来的模型不对路现象GridSearchCV 不写 scoring跑完选出来的 best_params_ 在业务指标上很一般甚至不如默认参数。原因不写 scoring 时搜索器用默认 accuracy而业务关注的指标可能是 recall 或者自定义代价值和准确率不一致。解决在搜索器里显式指定 scorer。比如要按召回率选模型可以用 make_scorer 包一层from sklearn.metrics import make_scorer, recall_score from sklearn.model_selection import GridSearchCV scorer make_scorer(recall_score, pos_label1) search GridSearchCV(model, param_grid, scoringscorer, cv5)make_scorer 让 sklearn 的评分函数变成 GridSearchCV 可以直接调用的 scorer 对象pos_label1 指定正类。如果同时想比较多个指标可以传一个字典给 scoring再用 refit 指定哪个指标决定最优模型比如 {f1: ..., recall: ...} 加 refitf1。这个细节看似简单但搜索器选出的参数组合会直接决定后续所有实验方向值得一开始就写对。6. 把评估流程固定下来一份可复用的模板与我的工程习惯6.1 一个最小可复用的评估与选择模板把前面几章的内容串成一个可直接套用的流程包括划分、Pipeline、随机搜索和最终测试评估from sklearn.model_selection import train_test_split, RandomizedSearchCV from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.ensemble import RandomForestClassifier from sklearn.metrics import roc_auc_score # 假设 X, y 已经加载并完成必要清洗 X_train, X_test, y_train, y_test train_test_split( X, y, test_size0.2, stratifyy, random_state42 ) pipe Pipeline([ (scaler, StandardScaler()), (clf, RandomForestClassifier(random_state0)), ]) search RandomizedSearchCV( pipe, param_distributions{ clf__n_estimators: [100, 200], clf__max_depth: [4, 6, 8, None], }, n_iter8, scoringroc_auc, cv5, n_jobs-1, random_state0, ) search.fit(X_train, y_train) y_prob search.best_estimator_.predict_proba(X_test)[:, 1] print(Test ROC-AUC:, roc_auc_score(y_test, y_prob))这个模板里有几个值得注意的参数。clf__max_depth 里有两个下划线表示 Pipeline 中 clf 组件的参数缺少两个下划线会被当成未知参数报错。n_iter8 控制随机搜索尝试的参数组合数数据量越大、特征越多这个数应该相应增大。search.best_estimator_ 是已经用完整训练集拟合好的最优模型可以直接用。6.2 先画学习曲线再上网格搜索我的个人习惯是在做大规模参数搜索之前先画两条学习曲线判断当前模型处于欠拟合还是过拟合。曲线的画法很简单取训练集的不同子集大小在每个子集上分别记录训练误差和验证误差观察两者是否收敛。如果训练误差和验证误差都很高且差距很小是欠拟合。这时候加大模型复杂度、换更强的模型比继续调正则化参数更有用。如果训练误差很低而验证误差高是过拟合。这时候才值得去调正则化参数、减少特征或者增加数据。这样能避免一整夜干跑网格搜索后发现方向错了。把评估流程固定成模板每次实验都走同一套划分和评分口径至少能保证结果的毛病不在评估协议上。以上是我从大量课程作业和线上项目里提炼出的习惯也是我评估“一个新方案是否值得上线”的主要依据希望对你有所帮助。本文还有配套的精品资源点击获取