【机器学习】模型评估与调优
数据划分:train_test_split—— 避免 “自欺欺人” 的关键在训练模型前,我们必须把数据分成训练集(Training Set) 和测试集(Test Set),这是评估模型泛化能力的前提。为什么必须划分训练集和测试集?模型训练的目标是 “在新数据上表现好”(泛化能力强),而不是 “在训练数据上表现好”。如果不划分数据,直接用全量数据训练并评估,会出现 “过拟合” 的误判:用 train
Scikit-learn
模型评估与调优
模型评估基础
数据划分:train_test_split—— 避免 “自欺欺人” 的关键
在训练模型前,我们必须把数据分成训练集(Training Set) 和测试集(Test Set),这是评估模型泛化能力的前提。
-
为什么必须划分训练集和测试集?
模型训练的目标是 “在新数据上表现好”(泛化能力强),而不是 “在训练数据上表现好”。如果不划分数据,直接用全量数据训练并评估,会出现 “过拟合” 的误判:
- 比如模型记住了训练数据的所有细节(包括噪声),在训练数据上准确率 100%,但遇到新数据时准确率骤降(因为它没学会通用规律);
- 划分数据后,训练集用于 “学习规律”,测试集用于 “模拟新数据”,评估结果才能反映模型的真实泛化能力 —— 这一步的核心是避免信息泄露(Data Leakage)。
-
用 train_test_split 划分数据
以鸢尾花数据集为例,演示标准划分流程:
from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split # 1. 加载数据(特征X,标签y) iris = load_iris() X = iris.data # 4个特征:花萼长、花萼宽、花瓣长、花瓣宽 y = iris.target # 标签:3类鸢尾花(0,1,2) # 2. 划分训练集(80%)和测试集(20%) X_train, X_test, y_train, y_test = train_test_split( X, y, # 特征和标签 test_size=0.2, # 测试集占比(通常取0.2~0.3) random_state=42, # 固定随机种子(保证每次运行结果一致,便于复现) stratify=y # 分类任务必加!保证训练/测试集类别比例与原始数据一致 ) # 查看划分结果 print(f"训练集规模:X_train={X_train.shape}, y_train={y_train.shape}") # (120,4), (120,) print(f"测试集规模:X_test={X_test.shape}, y_test={y_test.shape}") # (30,4), (30,) print(f"原始数据类别分布:{np.bincount(y)}") # [50,50,50](3类各50个) print(f"训练集类别分布:{np.bincount(y_train)}") # [40,40,40](80%训练集,比例不变) print(f"测试集类别分布:{np.bincount(y_test)}") # [10,10,10](20%测试集,比例不变) -
关键参数解读
test_size:测试集占比,默认 0.25(25%),根据数据量调整 —— 数据量大时(如 100 万样本)可设为 0.1(10%),数据量小时(如 100 样本)可设为 0.3(30%)。random_state:随机种子,取值为任意整数(如 42、100)。固定后每次划分结果相同,便于代码复现(否则每次运行划分的样本都不同,无法对比模型效果)。stratify=y:分类任务的 “救命参数”,保证训练集和测试集的类别比例与原始数据一致。若不设置,可能出现 “测试集全是某一类” 的极端情况(如鸢尾花测试集 30 个样本全是类别 0),导致评估结果完全失效。回归任务无需此参数(标签是连续值,无 “类别比例” 一说)。
模型训练与预测 —— 标准流程演示
划分好数据后,用经典模型(随机森林)演示 “训练→预测” 的标准流程,为后续评估做准备:
from sklearn.ensemble import RandomForestClassifier # 导入随机森林分类器
# 1. 初始化模型(固定random_state确保可复现)
model = RandomForestClassifier(n_estimators=100, random_state=42)
# 2. 用训练集训练模型(只给X_train和y_train,模拟“模型只见过训练数据”)
model.fit(X_train, y_train)
# 3. 用训练好的模型预测测试集(模拟“模型遇到新数据”)
y_pred = model.predict(X_test) # 预测测试集的类别(0/1/2)
y_pred_proba = model.predict_proba(X_test) # 预测测试集属于各类别的概率(如[0.01, 0.98, 0.01])
# 查看前5个预测结果
print("测试集前5个样本的真实标签:", y_test[:5]) # 输出:[1 2 0 1 1](示例)
print("测试集前5个样本的预测标签:", y_pred[:5]) # 输出:[1 2 0 1 1](预测正确)
print("测试集前5个样本的预测概率:")
print(y_pred_proba[:5].round(3)) # 输出:[[0. 1. 0.] [0. 0. 1.] ...](概率集中在正确类别)
分类模型常用评估指标 —— 选对指标才能 “评得准”
分类任务的标签是离散值(如 0/1、猫 / 狗 / 鸟),常用指标围绕 “预测是否正确” 展开,不同指标侧重不同场景。
-
准确率(Accuracy)—— 最直观但有陷阱
-
定义:预测正确的样本数占总样本数的比例,公式: A c c u r a c y = 正确预测数 总样本数 Accuracy={正确预测数 \over 总样本数} Accuracy=总样本数正确预测数
-
代码实现:
from sklearn.metrics import accuracy_score accuracy = accuracy_score(y_test, y_pred) print(f"准确率(Accuracy):{accuracy:.4f}") # 输出:1.0(鸢尾花数据简单,预测全对) -
优点:计算简单,直观易懂。
-
缺点:在类别不平衡数据中完全失效!例如 “癌症诊断” 数据中,99% 样本是 “健康”(0),1% 是 “患病”(1),模型全预测为 0,准确率仍有 99%,但完全没意义(漏检了所有患者)。
-
适用场景:类别分布均衡的分类任务(如鸢尾花 3 类各占 1/3)。
-
-
精确率(Precision)、召回率(Recall)、F1 分数 —— 解决不平衡数据的 “三兄弟”
这三个指标针对 “二分类任务” 设计(多分类需加
average参数),核心围绕 “正类(我们关注的类别,如 “患病”)” 展开:-
对二分类问题(0/1)而言:
预测为 1(正类) 预测为 0(负类) 真实为 1(正类) TP(真阳性) FN(假阴性) 真实为 0(负类) FP(假阳性) TN(真阴性) - TP:真实 1,预测 1(正确);
- FN:真实 1,预测 0(漏检);
- FP:真实 0,预测 1(误检);
- TN:真实 0,预测 0(正确)。
-
精确率(Precision):预测为正类的样本中,真正是正类的比例(“预测对的正类占预测正类的多少”,关注 “不错判”)。
公式: P r e c i s i o n = T P T P + F P Precision={TP \over TP+FP} Precision=TP+FPTP
例:预测为 “患病” 的 10 人中,9 人真患病 → 精确率 = 90%(避免 “健康人被误诊为患病”)。
-
召回率(Recall):真实为正类的样本中,被预测为正类的比例(“预测对的正类占真实正类的多少”,关注 “不漏判”)。
公式: R e c a l l = T P T P + F N Recall={TP \over TP+FN} Recall=TP+FNTP
例:真实患病的 10 人中,8 人被预测为患病 → 召回率 = 80%(避免 “患病者被漏诊”)。
-
F1 分数(F1-Score):精确率和召回率的调和平均数,综合两者表现(避免 “精确率高但召回率低” 或反之)。
公式: F 1 = 2 × P r e c i s i o n × R e c a l l P r e c i s i o n + R e c a l l F1=2×{Precision×Recall \over Precision+Recall} F1=2×Precision+RecallPrecision×Recall
-
-
ROC 曲线与 AUC 值
ROC 曲线(Receiver Operating Characteristic Curve)和 AUC 值(Area Under ROC Curve)是衡量二分类模型 “区分能力” 的黄金标准,尤其适合类别不平衡(如正样本占比 < 10%)或需要概率输出(如 “预测用户购买概率”)的场景。
之前学的 Accuracy、Precision、Recall 都是 “基于固定阈值” 的指标(如默认用 0.5 作为概率阈值,大于 0.5 预测为正类),但实际业务中阈值可能需要调整(如癌症诊断希望 “宁可错判,不能漏判”,需降低阈值)。
ROC/AUC 的优势在于:不依赖具体阈值,而是通过 “所有可能阈值下的模型表现”,综合评估模型的 “区分正负类的能力”—— 即使调整阈值,AUC 值也能稳定反映模型本质性能。
ROC 曲线的横轴和纵轴分别对应两个核心指标,需先理解:
-
TPR(True Positive Rate,真阳性率):即 Recall(召回率),表示 “真实正类中被正确预测为正类的比例”,反映模型 “抓正类” 的能力。
公式: T P R = T P T P + F N TPR={TP \over TP+FN} TPR=TP+FNTP
例:100 个癌症患者中,80 个被正确诊断 → TPR=80%。
-
FPR(False Positive Rate,假阳性率):表示 “真实负类中被错误预测为正类的比例”,反映模型 “误判负类” 的代价。
公式: F P R = F P F P + T N FPR={FP \over FP+TN} FPR=FP+TNFP
例:1000 个健康人中,50 个被误诊为癌症 → FPR=5%。
ROC 曲线以FPR(横轴,代价) 为 X 轴,以TPR(纵轴,收益) 为 Y 轴,通过 “遍历所有可能的概率阈值” 绘制而成,每个阈值对应曲线上的一个点。
-
绘制逻辑(简化版):
- 模型输出每个样本的 “正类概率”(如逻辑回归的
predict_proba[:,1]); - 从高到低排序这些概率,以每个概率作为 “阈值”(大于阈值预测为正,否则为负);
- 对每个阈值,计算对应的 TPR 和 FPR;
- 将所有(FPR, TPR)点连成线,即为 ROC 曲线。
- 模型输出每个样本的 “正类概率”(如逻辑回归的
-
绘制 ROC 曲线
以 “二分类版鸢尾花数据”(只区分 Setosa 和非 Setosa)为例:
from sklearn.datasets import load_iris 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 import numpy as np # 1. 准备二分类数据(只区分Setosa(1)和非Setosa(0)) iris = load_iris() X = iris.data y = (iris.target == 0).astype(int) # 1=Setosa,0=其他 # 2. 划分数据并训练模型(逻辑回归输出概率) X_train, X_test, y_test = train_test_split(X, y, test_size=0.2, random_state=42, stratify=y) model = LogisticRegression(random_state=42) model.fit(X_train, y_train) # 3. 获取模型输出的正类概率(关键!ROC需要概率,不是类别) y_pred_proba = model.predict_proba(X_test)[:, 1] # 取“预测为1(Setosa)”的概率 # 4. 计算ROC曲线的“阈值、FPR、TPR” fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba) # 5. 计算AUC值(ROC曲线下的面积) roc_auc = auc(fpr, tpr) # 6. 绘制ROC曲线 plt.figure(figsize=(8, 6)) # 绘制模型的ROC曲线 plt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC Curve (AUC = {roc_auc:.4f})') # 绘制“随机猜测”的基准线(AUC=0.5,对角线) plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--', label='Random Guess (AUC = 0.5)') # 添加标签和图例 plt.xlim([0.0, 1.0]) plt.ylim([0.0, 1.05]) plt.xlabel('False Positive Rate (FPR)', fontsize=12) plt.ylabel('True Positive Rate (TPR)', fontsize=12) plt.title('ROC Curve (Setosa vs Non-Setosa)', fontsize=14) plt.legend(loc="lower right") plt.grid(alpha=0.3) plt.show() print(f"AUC值:{roc_auc:.4f}") # 输出:1.0(Setosa易区分,模型完美)
-
ROC 曲线与 AUC 值解读:
- AUC 值含义:ROC 曲线下的面积,取值范围为 [0.5, 1.0](AUC<0.5 时,模型不如随机猜测,可反向预测修正)。
- AUC=1.0:模型完美区分正负类(如示例,所有 Setosa 都被正确识别,无假阳性);
- AUC=0.8~1.0:模型区分能力优秀(如癌症诊断模型 AUC=0.9,能有效区分患者和健康人);
- AUC=0.6~0.8:模型区分能力一般(需优化,如调整特征或模型);
- AUC=0.5:模型等同于随机猜测(毫无价值,如抛硬币判断正负类)。
- ROC 曲线形状:
- 曲线越 “靠近左上角”,模型性能越好(相同 FPR 下,TPR 越高;相同 TPR 下,FPR 越低);
- 若曲线完全重合于对角线(AUC=0.5),模型无区分能力;
- 实际业务中,需在 “TPR(收益)” 和 “FPR(代价)” 间权衡(如银行风控:希望 TPR 高(抓更多欺诈交易),同时 FPR 低(少误判正常交易))。
- AUC 值含义:ROC 曲线下的面积,取值范围为 [0.5, 1.0](AUC<0.5 时,模型不如随机猜测,可反向预测修正)。
ROC/AUC 的适用场景与注意事项
-
适用场景:
-
二分类任务(多分类需转换为 “一对多” 或 “一对一”,但 AUC 解读复杂,多分类更常用 F1);
-
类别不平衡场景(如 fraud detection 欺诈交易占比 < 1%,Accuracy 会失真,AUC 能更客观反映模型区分能力);
-
需要概率输出的场景(如推荐系统 “预测用户点击概率”,需通过 ROC/AUC 评估排序能力)。
-
-
注意事项:
-
AUC 不适合评估 “绝对概率准确性”:AUC 衡量的是 “相对排序能力”(如 “用户 A 点击概率 0.6,用户 B0.5,模型能正确排序”),但不关心 “0.6 是否真的对应 60% 的点击概率”(概率校准需用其他方法);
-
多分类不能直接用 AUC:需将多分类转换为二分类(如 “一对多”:计算 “类别 1 vs 其他”“类别 2 vs 其他” 等多个 AUC,再取平均),但解读不如 F1 直观;
-
需用 “概率输出” 计算 AUC:必须用模型的
predict_proba(输出概率)或decision_function(输出决策值),不能用predict(输出类别)—— 因为类别是固定阈值的结果,无法遍历所有阈值。
-
-
-
多分类任务的指标计算(加
average参数)鸢尾花是 3 分类任务,需通过
average参数指定 “如何计算多类的指标平均值”,常用三个选项:from sklearn.metrics import precision_score, recall_score, f1_score # 多分类任务计算指标(必须指定average) precision_macro = precision_score(y_test, y_pred, average='macro') # 宏平均:各类别指标平等对待(适合类别平衡) precision_weighted = precision_score(y_test, y_pred, average='weighted') # 加权平均:按类别样本数加权(适合类别不平衡) recall_macro = recall_score(y_test, y_pred, average='macro') f1_macro = f1_score(y_test, y_pred, average='macro') print(f"宏平均精确率:{precision_macro:.4f}") # 输出:1.0(全对) print(f"宏平均召回率:{recall_macro:.4f}") # 输出:1.0 print(f"宏平均F1分数:{f1_macro:.4f}") # 输出:1.0average='macro':先计算每个类别的指标,再取算术平均(不考虑类别样本数)→ 适合类别平衡场景。average='weighted':先计算每个类别的指标,再按类别样本数占比加权平均 → 适合类别不平衡场景(避免少数类被忽略)。average=None:不计算平均值,返回每个类别的指标 → 适合需要分析 “每类表现” 的场景(如 “类别 0 全对,类别 1 错了 2 个”)。
-
混淆矩阵(Confusion Matrix)—— 可视化分类错误
混淆矩阵是一个
n×n的矩阵(n是类别数),直观展示 “各类别样本的预测情况”,能快速定位错误类型:from sklearn.metrics import confusion_matrix import seaborn as sns import matplotlib.pyplot as plt # 计算混淆矩阵(行:真实类别,列:预测类别) cm = confusion_matrix(y_test, y_pred) print("混淆矩阵:") print(cm) # 输出(鸢尾花全对,对角线全是10,其他为0): # [[10 0 0] # [ 0 10 0] # [ 0 0 10]] # 可视化混淆矩阵(更直观) plt.figure(figsize=(6, 4)) sns.heatmap( cm, annot=True, # 显示数值 fmt='d', # 数值格式为整数 cmap='Blues', xticklabels=iris.target_names, # 横轴:预测类别(花名) yticklabels=iris.target_names # 纵轴:真实类别(花名) ) plt.xlabel('Predicted Class') plt.ylabel('True Class') plt.title('Confusion Matrix (Iris Dataset)') plt.show()- 解读:对角线元素是 “预测正确的样本数”(如真实是 setosa、预测也是 setosa 的有 10 个);非对角线元素是 “预测错误的样本数”(如真实是 versicolor、预测是 virginica 的有 0 个)。
- 作用:快速发现 “哪两类容易混淆”(如在复杂数据中,versicolor 和 virginica 可能有交叉,非对角线会有数值)。
-
分类报告(Classification Report)—— 一键输出所有关键指标
classification_report会自动计算每个类别的精确率、召回率、F1 分数,以及整体准确率,无需手动调用多个函数:from sklearn.metrics import classification_report print("分类报告:") print(classification_report( y_test, y_pred, target_names=iris.target_names # 用花名代替类别0/1/2,更易解读 ))输出示例(鸢尾花全对):
precision recall f1-score support setosa 1.00 1.00 1.00 10 versicolor 1.00 1.00 1.00 10 virginica 1.00 1.00 1.00 10 accuracy 1.00 30 macro avg 1.00 1.00 1.00 30 weighted avg 1.00 1.00 1.00 30support:该类别的真实样本数(如 setosa 有 10 个测试样本);macro avg/weighted avg:对应前文的宏平均和加权平均;accuracy:整体准确率。
回归模型常用评估指标 —— 预测连续值的 “尺子”
回归任务的标签是连续值(如房价、温度),指标围绕 “预测值与真实值的误差” 展开,核心是 “误差越小,模型越好”。
-
均方误差(MSE)与均方根误差(RMSE)
-
均方误差(MSE):预测值与真实值差值的平方的平均值(平方放大了大误差,对异常值敏感)。
公式: M S E = 1 n ∑ i = 1 n ( y i − y ^ i ) 2 MSE={1\over n}∑_{i=1}^n(y_i−\hat{y}_i)^2 MSE=n1∑i=1n(yi−y^i)2
-
均方根误差(RMSE):MSE 的平方根(单位与目标值一致,更直观)。
公式: R M S E = M S E RMSE=\sqrt{MSE} RMSE=MSE
代码实现:
from sklearn.datasets import load_diabetes from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error # 加载回归数据(预测糖尿病病情进展) X, y = load_diabetes(return_X_y=True) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) # 训练线性回归模型 model = LinearRegression() model.fit(X_train, y_train) y_pred = model.predict(X_test) # 计算MSE和RMSE mse = mean_squared_error(y_test, y_pred) rmse = mean_squared_error(y_test, y_pred, squared=False) # squared=False返回RMSE print(f"均方误差(MSE):{mse:.2f}") # 输出:2870.56(数值越小越好) print(f"均方根误差(RMSE):{rmse:.2f}") # 输出:53.58(单位与病情进展指标一致,更易理解)- 适用场景:关注大误差的场景(如房价预测,错估 100 万比错估 10 万影响大,MSE/RMSE 能放大这种差异)。
-
-
平均绝对误差(MAE)
-
定义:预测值与真实值差值的绝对值的平均值(对异常值不敏感,更稳健)。
公式: M A E = 1 n ∑ i = 1 n ∣ y i − y ^ i ∣ MAE={1 \over n}∑_{i=1}^n∣y_i−\hat{y}_i∣ MAE=n1∑i=1n∣yi−y^i∣
代码实现:
from sklearn.metrics import mean_absolute_error mae = mean_absolute_error(y_test, y_pred) print(f"平均绝对误差(MAE):{mae:.2f}") # 输出:43.27(比RMSE小,因未放大异常值)- 适用场景:数据中存在较多异常值的场景(如温度预测,偶尔的极端高温不会严重影响 MAE)。
-
-
决定系数(R²)—— 衡量 “拟合优度”
-
定义:衡量模型对目标变量变异的解释能力,取值范围为 (-∞, 1]。
公式: R 2 = 1 − ∑ ( y i − y ^ i ) 2 ∑ ( y i − y ˉ ) 2 R^2=1−{∑(y_i−\hat{y}_i)^2 \over ∑(y_i−\bar{y})^2} R2=1−∑(yi−yˉ)2∑(yi−y^i)2,其中 y ˉ \bar{y} yˉ 是真实值的均值。
代码实现:
from sklearn.metrics import r2_score r2 = r2_score(y_test, y_pred) print(f"决定系数(R²):{r2:.4f}") # 输出:0.4773(越接近1越好)- R²=1:模型完美拟合数据(预测值与真实值完全一致);
- R²=0:模型预测效果和 “直接用所有真实值的均值预测” 一样(模型毫无价值);
- R²<0:模型预测效果比 “用均值预测” 还差(通常是模型选择错误或数据预处理不当);
- 示例中 R²=0.4773,说明模型能解释约 47.73% 的目标变量变异(糖尿病病情进展的差异),还有提升空间。
- 适用场景:需要衡量模型 “整体拟合效果” 的场景,是回归任务最常用的综合指标。
-
如何选择正确的评估指标?—— 按任务类型和业务需求定
不同任务和业务场景对 “好模型” 的定义不同,必须针对性选择指标,避免 “用错尺子量错东西”。
-
分类任务(标签为离散值,如 0/1、猫 / 狗)
-
二分类(仅两个类别,如 “患病 / 健康”“垃圾邮件 / 正常邮件”)
场景特点 核心需求 推荐指标 避坑点 类别均衡(正负样本比例接近 1:1) 综合判断模型整体准确率 ① Accuracy(准确率)② F1-Score(平衡精确率和召回率) 单独用 Accuracy 可能掩盖类别内部的误判(需结合混淆矩阵) 类别不平衡(如正样本占比 < 10%) 优先关注少数类(如 “患病”)的识别 ① Recall(召回率,避免漏判少数类)② Precision(精确率,避免错判多数类)③ F1-Score(平衡两者) 绝对不能用 Accuracy(可能 99% 但无意义) 需要概率 / 排序能力(如推荐、风控) 评估模型对 “正负类的区分能力” ① ROC-AUC(不依赖阈值,衡量排序能力)② PR-AUC(在不平衡数据中比 ROC 更敏感) 需用模型输出的概率(而非类别)计算 -
多分类(三个及以上类别,如 “鸢尾花 3 类”“手写数字 0-9”)
场景特点 核心需求 推荐指标 避坑点 类别均衡(各类样本数接近) 平等对待每类的表现 ① Macro-F1(宏平均,各类指标直接平均)② 分类报告(Classification Report,查看每类细节) 忽略少数类时会高估模型性能 类别不平衡(某类样本极少) 关注多数类和少数类的综合表现 ① Weighted-F1(加权平均,按样本数加权)② 单独查看少数类的 Recall/Precision
-
-
回归任务(标签为连续值,如房价、温度)
场景特点 核心需求 推荐指标 避坑点 关注误差大小(如房价预测) 误差的整体量化(对大误差敏感) ① RMSE(均方根误差,单位与目标一致)② MSE(均方误差,放大极端误差) RMSE 更易向业务方解释(如 “平均误差 5 万元”) 数据含异常值(如温度、销售额) 误差评估需稳健(不受极端值干扰) ① MAE(平均绝对误差,对异常值不敏感) MAE 无法反映大误差的惩罚(如错估 100 万和 10 万权重相同) 衡量整体拟合优度 模型对目标变量变异的解释能力 ① R²(决定系数,越接近 1 越好) 不能单独用 R²(需结合 RMSE/MAE 判断实际误差) -
通用原则:指标选择的 3 个核心依据
- 业务目标优先:指标必须服务于业务需求。例如:
- 癌症诊断:优先 Recall(宁可错判,不能漏诊);
- 垃圾邮件过滤:优先 Precision(宁可漏判,不能误删正常邮件)。
- 数据特点适配:根据数据分布(平衡 / 不平衡、有无异常值)选择指标,避免 “用 Accuracy 评估不平衡数据”“用 MSE 评估含异常值的数据”。
- 多指标结合:单一指标有局限性,需组合使用。例如:
- 二分类不平衡:同时看 Recall(少数类识别率)和 ROC-AUC(整体区分能力);
- 回归:同时看 R²(拟合优度)和 RMSE(实际误差大小)。
- 业务目标优先:指标必须服务于业务需求。例如:
交叉验证
为什么需要交叉验证?—— 解决 “单次划分的偶然性”
前面用 train_test_split 把数据分成 “训练集 + 测试集”,但这种单次划分存在明显缺陷:
- 若测试集恰好包含大量 “简单样本”(如鸢尾花中易区分的 Setosa),模型得分会偏高;
- 若测试集恰好包含大量 “复杂样本”(如 versicolor 和 virginica 的混淆样本),模型得分会偏低;
- 最终评估结果受 “划分运气” 影响大,无法反映模型的真实泛化能力。
交叉验证的核心思想是:把数据多次划分成不同的 “训练集 + 验证集”,用多次评估的平均值作为最终性能,大幅降低偶然性—— 就像考试时 “多做几套模拟题取平均分”,比 “只做一套题” 更能反映真实水平。
最常用的交叉验证:K 折交叉验证(K-Fold Cross Validation)
K 折交叉验证是工业界的 “标准方案”,流程简单清晰,核心是把数据均匀分成 K 份(通常 K=5 或 K=10),循环 K 次完成评估:
- 划分数据:将数据集分成 K 个大小相近的 “子集(Fold)”;
- 循环评估:
- 第 1 次:用 “子集 1” 当验证集,“子集 2~K” 当训练集,计算 1 次得分;
- 第 2 次:用 “子集 2” 当验证集,“子集 1、3~K” 当训练集,计算 1 次得分;
- …
- 第 K 次:用 “子集 K” 当验证集,“子集 1~K-1” 当训练集,计算 1 次得分;
- 计算最终得分:取 K 次得分的平均值(如平均准确率、平均 R²),作为模型的最终评估结果。
以 K=5 为例,流程示意图如下:
数据集:[Fold1][Fold2][Fold3][Fold4][Fold5]
- 迭代 1:训练 [2-5] → 验证 [1]
- 迭代 2:训练 [1,3-5] → 验证 [2]
- 迭代 3:训练 [1-2,4-5] → 验证 [3]
- 迭代 4:训练 [1-3,5] → 验证 [4]
- 迭代 5:训练 [1-4] → 验证 [5]
3 种常用交叉验证实现
-
cross_val_score —— 快速计算单指标得分
适合只需获取 “单一评估指标”(如准确率、R²)的场景,代码最简单:
from sklearn.datasets import load_iris from sklearn.model_selection import cross_val_score from sklearn.ensemble import RandomForestClassifier # 1. 加载数据(二分类简化:只区分 Setosa 和非 Setosa) iris = load_iris() X = iris.data y = (iris.target == 0).astype(int) # 1=Setosa,0=其他 # 2. 初始化模型 model = RandomForestClassifier(n_estimators=100, random_state=42) # 3. 5折交叉验证,评估“准确率” # cv=5:5折;scoring='accuracy':评估指标为准确率 scores = cross_val_score( estimator=model, # 要评估的模型 X=X, y=y, # 特征和标签 cv=5, # 折数(常用5或10) scoring='accuracy', # 评估指标(分类用accuracy,回归用r2) n_jobs=-1 # 并行计算(用所有CPU核心,加速) ) # 4. 查看结果 print(f"每折准确率:{scores.round(4)}") # 输出:[1. 1. 1. 1. 1.](Setosa易区分,全对) print(f"平均准确率:{scores.mean().round(4)}") # 输出:1.0 print(f"准确率标准差:{scores.std().round(4)}") # 输出:0.0(标准差小,模型稳定)- 关键参数:
scoring:指定评估指标(分类常用accuracy/f1/roc_auc,回归常用r2/neg_mean_squared_error);n_jobs=-1:并行计算(K 次迭代同时跑,数据量大时能大幅提速);cv:折数(K=5 或 10,K 越大评估越稳定,但计算时间越长)。
- 关键参数:
-
cross_validate —— 支持多指标与训练集得分
cross_val_score只能返回 “验证集单指标”,而cross_validate更灵活:可同时计算 “训练集 + 验证集” 得分、支持多指标,适合判断模型是否过拟合:from sklearn.model_selection import cross_validate # 1. 5折交叉验证,同时计算“准确率”和“F1分数”,返回训练集得分 results = cross_validate( estimator=model, X=X, y=y, cv=5, scoring=['accuracy', 'f1'], # 同时评估2个指标 return_train_score=True, # 返回训练集得分(关键!用于判断过拟合) n_jobs=-1 ) # 2. 查看结果(results是字典,包含各指标的训练/验证得分) print("验证集平均准确率:", results['test_accuracy'].mean().round(4)) # 1.0 print("验证集平均F1:", results['test_f1'].mean().round(4)) # 1.0 print("训练集平均准确率:", results['train_accuracy'].mean().round(4))# 1.0 print("训练集平均F1:", results['train_f1'].mean().round(4)) # 1.0 # 3. 判断过拟合:若训练得分远高于验证得分,说明过拟合 print("训练集准确率 - 验证集准确率:", (results['train_accuracy'] - results['test_accuracy']).mean().round(4)) # 0.0(无过拟合)- 核心价值:通过对比
train_*(训练集得分)和test_*(验证集得分),快速判断过拟合:- 若
train_accuracy=0.98,test_accuracy=0.7→ 训练集得分远高,模型过拟合; - 若两者接近且都高 → 模型拟合良好。
- 若
- 核心价值:通过对比
-
KFold/StratifiedKFold —— 手动控制数据划分
前两种方法默认用 “随机划分”,但有时需要手动控制划分逻辑(如按时间序列划分),此时用
KFold(回归)或StratifiedKFold(分类):from sklearn.model_selection import KFold, StratifiedKFold import numpy as np # 场景1:回归任务 → 用 KFold(无需考虑类别比例) print("=== 回归任务:KFold ===") kf = KFold(n_splits=5, shuffle=True, random_state=42) # shuffle=True:打乱数据 X_reg = np.random.rand(100, 5) # 模拟回归数据(100样本,5特征) y_reg = np.random.rand(100) # 模拟回归标签 for fold, (train_idx, val_idx) in enumerate(kf.split(X_reg), 1): print(f"第{fold}折:训练集样本数={len(train_idx)},验证集样本数={len(val_idx)}") # 场景2:分类任务 → 用 StratifiedKFold(保证每折类别比例与原始数据一致) print("\n=== 分类任务:StratifiedKFold ===") skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) # 查看每折的类别分布 for fold, (train_idx, val_idx) in enumerate(skf.split(X, y), 1): train_pos_ratio = y[train_idx].sum() / len(train_idx) # 训练集正类比例 val_pos_ratio = y[val_idx].sum() / len(val_idx) # 验证集正类比例 print(f"第{fold}折:训练集正类比例={train_pos_ratio:.2f},验证集正类比例={val_pos_ratio:.2f}")- 关键区别:
KFold:仅按样本数量均分,不考虑类别 → 适合回归任务;StratifiedKFold:保证每折的 “类别比例” 与原始数据一致 → 分类任务必须用这个!(避免某一折全是负样本,导致评估失效);shuffle=True:划分前打乱数据(避免数据按顺序排列导致某一折样本分布异常,如 “前 100 个全是正样本”)。
- 关键区别:
交叉验证的注意事项
- 分类任务必须用 StratifiedKFold:若用普通 KFold,可能导致某一折类别比例失衡(如正样本占比 0%),评估结果完全无效。
- 数据预处理不能 “提前全局做”:必须在 “每折训练” 时单独做预处理(如标准化、PCA),避免 “验证集信息泄露”(例如用全量数据的均值标准化,验证集提前知道了训练集的统计信息)。正确做法是用
Pipeline串联 “预处理 + 模型”(后续 5.3 节会讲)。 - 折数(cv)的选择:K=5 或 10 是行业标准 ——K=5 计算快,K=10 评估更稳定;数据量小时(如 <1000 样本)用 K=10,数据量大时(如>10 万 样本)用 K=5(平衡速度和稳定性)。
- 避免 “测试集参与交叉验证”:交叉验证的 “验证集” 是用于 “调整模型” 的,最终还需用独立的 “测试集” 评估模型泛化能力(即 “训练集→交叉验证调参→测试集最终评估” 的完整流程)。
| 交叉验证方法 | 核心优势 | 适用场景 |
|---|---|---|
| cross_val_score | 代码简单,快速获取单指标 | 快速评估模型基础性能,无需复杂分析 |
| cross_validate | 支持多指标、返回训练集得分 | 判断模型过拟合,需要综合评估多个指标 |
| KFold | 手动控制划分,灵活 | 回归任务,或需要自定义划分逻辑的场景 |
| StratifiedKFold | 保证类别比例,评估更客观 | 所有分类任务(二分类、多分类),必选 |
超参数调优
什么是超参数?
在开始调优前,必须区分 “参数” 和 “超参数”:
- 模型参数:模型训练过程中自动学习的参数(如线性回归的权重
w、逻辑回归的系数); - 超参数:模型训练前需要手动设定的参数(如随机森林的
n_estimators、SVM 的C),无法通过数据自动学习,只能通过调优确定。
举个例子,常见模型的超参数:
| 模型 | 超参数示例 | 作用说明 |
|---|---|---|
| 随机森林 | n_estimators(树的数量) |
数量越多,模型越稳定,但计算成本越高 |
| 随机森林 | max_depth(树的最大深度) |
限制树的复杂度,防止过拟合 |
| SVM | C(正则化系数) |
C 越大,对错误样本惩罚越重,易过拟合 |
| SVM | kernel(核函数) |
选择线性(linear)或非线性(rbf)核 |
| KNN | n_neighbors(近邻数量) |
k 越小,模型越复杂,易过拟合 |
超参数调优的核心逻辑
超参数调优不是 “凭感觉试参数”,而是遵循 “定义参数空间 → 遍历 / 采样参数 → 交叉验证评估 → 选择最优组合” 的系统流程:
- 定义参数空间:确定每个超参数的候选取值范围(如
max_depth=[3,5,7,None]); - 评估参数组合:对每个参数组合,用交叉验证(如 5 折)评估模型性能,避免单次划分的偶然性;
- 选择最优组合:在所有参数组合中,选择交叉验证得分最高的组合作为最终超参数。
sklearn 提供了两个工具实现这个流程:GridSearchCV(网格搜索)和 RandomizedSearchCV(随机搜索)。
GridSearchCV(网格搜索)—— 穷举所有组合
网格搜索是 “暴力穷举法”:把所有超参数的候选值组合成 “网格”,逐一用交叉验证评估,适合参数少、候选值范围小的场景。
-
用 GridSearchCV 调优随机森林
以 “鸢尾花分类” 为例,调优随机森林的 3 个超参数:
from sklearn.datasets import load_iris from sklearn.ensemble import RandomForestClassifier from sklearn.model_selection import GridSearchCV, StratifiedKFold # 1. 加载数据(多分类任务) iris = load_iris() X, y = iris.data, iris.target # 2. 定义超参数网格(要遍历的参数组合) param_grid = { 'n_estimators': [50, 100, 200], # 树的数量:3个候选值 'max_depth': [3, 5, None], # 树的最大深度:3个候选值 'min_samples_split': [2, 5] # 分裂节点的最小样本数:2个候选值 } # 总组合数:3×3×2=18种(网格搜索会遍历所有18种组合) # 3. 初始化交叉验证器(分类任务用 StratifiedKFold) cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42) # 4. 初始化网格搜索 grid_search = GridSearchCV( estimator=RandomForestClassifier(random_state=42), # 要调优的模型 param_grid=param_grid, # 超参数网格 cv=cv, # 交叉验证器 scoring='accuracy', # 评估指标(多分类用准确率) n_jobs=-1, # 并行计算(用所有CPU核心) refit=True # 用最优参数组合重新训练全量数据 ) # 5. 执行网格搜索(自动遍历所有参数组合,用交叉验证评估) grid_search.fit(X, y) -
提取最优结果
网格搜索完成后,通过属性获取关键信息:
# 1. 最优超参数组合 print("最优超参数组合:", grid_search.best_params_) # 输出示例:{'max_depth': 5, 'min_samples_split': 2, 'n_estimators': 100} # 2. 最优组合的交叉验证平均得分 print("最优交叉验证平均准确率:", grid_search.best_score_.round(4)) # 输出示例:0.98(5折交叉验证的平均准确率) # 3. 用最优参数训练的模型(可直接用于预测) best_model = grid_search.best_estimator_ # 预测新数据 y_pred = best_model.predict(X[:5]) print("前5个样本的预测结果:", y_pred) # 4. 查看所有参数组合的评估结果(可选,用于分析) import pandas as pd results = pd.DataFrame(grid_search.cv_results_) # 显示前5个组合的参数、验证得分 print("\n前5个参数组合的评估结果:") print(results[['params', 'mean_test_score', 'rank_test_score']].head().round(4)) -
GridSearchCV 的优缺点
- 优点:穷举所有组合,能找到参数空间内的 “全局最优解”,结果确定;
- 缺点:参数多、候选值多时,计算量爆炸(如 5 个参数各 5 个候选值,共 3125 种组合),耗时严重。
RandomizedSearchCV(随机搜索)—— 随机采样组合
当参数多或候选值范围大时,随机搜索比网格搜索更高效:它不遍历所有组合,而是从参数空间中随机采样指定数量的组合(如 10 组),用交叉验证评估,大概率能找到 “近似最优解”,且速度快很多。
-
用 RandomizedSearchCV 调优随机森林
from sklearn.model_selection import RandomizedSearchCV from scipy.stats import randint, uniform # 用于生成随机参数分布 # 1. 定义超参数分布(不是固定列表,而是随机分布) param_dist = { 'n_estimators': randint(50, 300), # 随机整数:50~300之间 'max_depth': [3, 5, 7, None], # 固定列表(也可混合分布) 'min_samples_split': randint(2, 11), # 随机整数:2~10之间 'min_samples_leaf': uniform(0.1, 0.5)# 随机浮点数:0.1~0.6之间 } # 2. 初始化随机搜索 random_search = RandomizedSearchCV( estimator=RandomForestClassifier(random_state=42), param_distributions=param_dist, # 超参数分布(不是网格) n_iter=10, # 随机采样10组参数组合(关键!) cv=cv, # 5折交叉验证 scoring='accuracy', random_state=42, # 固定随机种子,结果可复现 n_jobs=-1, refit=True ) # 3. 执行随机搜索 random_search.fit(X, y) -
提取最优结果
print("随机搜索最优超参数组合:", random_search.best_params_) # 输出示例:{'max_depth': 5, 'min_samples_leaf': 0.2, 'min_samples_split': 3, 'n_estimators': 150} print("随机搜索最优交叉验证平均准确率:", random_search.best_score_.round(4)) # 输出示例:0.9733(与网格搜索的0.98接近,但只采样了10组,速度快很多) -
RandomizedSearchCV 的核心优势
- 效率高:无需遍历所有组合,采样 10~20 组即可接近最优解(研究表明,随机搜索在高维空间中比网格搜索效率高 10 倍以上);
- 支持连续参数:可通过
uniform等分布生成连续参数(如min_samples_leaf=0.2),而网格搜索只能用固定列表; - 更适合大规模调优:当超参数数量≥3 时,优先用随机搜索。
超参数调优 + Pipeline(避免信息泄露)
前面的例子中,我们没有做数据预处理(如标准化),但实际任务中(如 SVM、逻辑回归)需要预处理。如果直接对全量数据做预处理再调优,会导致 “验证集信息泄露”(验证集提前用了全量数据的统计信息)。
正确的做法是用 Pipeline 串联 “预处理 + 模型”,让预处理在每折交叉验证的训练集上单独执行,避免泄露:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC
# 1. 定义Pipeline(串联标准化和SVM)
pipe = Pipeline([
('scaler', StandardScaler()), # 第一步:标准化(预处理)
('svc', SVC(random_state=42)) # 第二步:SVM模型(要调优)
])
# 2. 定义超参数网格(注意:参数名要加“步骤名__”前缀)
pipe_param_grid = {
'svc__C': [0.1, 1, 10], # SVM的C参数:步骤名“svc”+双下划线+参数名“C”
'svc__kernel': ['linear', 'rbf'],# SVM的核函数
'svc__gamma': [0.001, 0.01, 0.1]# SVM的gamma参数(仅rbf核生效)
}
# 3. 网格搜索(用Pipeline作为estimator)
pipe_grid_search = GridSearchCV(
estimator=pipe,
param_grid=pipe_param_grid,
cv=cv,
scoring='accuracy',
n_jobs=-1
)
# 4. 执行搜索(自动在每折训练集上做标准化,避免信息泄露)
pipe_grid_search.fit(X, y)
print("Pipeline最优超参数:", pipe_grid_search.best_params_)
# 输出示例:{'svc__C': 1, 'svc__gamma': 0.1, 'svc__kernel': 'rbf'}
- 关键细节:Pipeline 中参数名的格式是
步骤名__参数名(双下划线),如svc__C表示 “Pipeline 中名为‘svc’的步骤的‘C’参数”。
超参数调优的建议
- 分步调优:先粗调(用随机搜索,大参数范围),再精调(用网格搜索,缩小到粗调找到的最优范围)。例如:
- 粗调:
n_estimators=50~300(随机搜索); - 精调:
n_estimators=100~200(网格搜索,步长 20)。
- 粗调:
- 优先调重要参数:不同超参数对模型影响不同,优先调关键参数(如随机森林的
n_estimators、max_depth,SVM 的C、kernel),再调次要参数(如min_samples_split)。 - 控制计算成本:
- 网格搜索:参数组合数≤50(否则耗时太长);
- 随机搜索:
n_iter设为 10~20(足够找到近似最优解); - 用
n_jobs=-1并行计算,充分利用 CPU 资源。
| 对比维度 | GridSearchCV(网格搜索) | RandomizedSearchCV(随机搜索) |
|---|---|---|
| 核心逻辑 | 穷举所有参数组合 | 随机采样指定数量组合 |
| 优点 | 能找全局最优解,结果确定 | 效率高,支持连续参数,适合多参数 |
| 缺点 | 计算量大,不支持连续参数 | 结果随机(需固定 seed),可能漏最优解 |
| 适用场景 | 参数少(≤2)、候选值范围小 | 参数多(≥3)、候选值范围大 |
| 推荐优先级 | 小规模调优(精调) | 大规模调优(粗调),优先选择 |
学习曲线与验证曲线
学习曲线(Learning Curve)—— 看 “数据量” 对模型的影响
学习曲线的核心是回答一个问题:“随着训练数据量的增加,模型的训练性能和泛化性能会如何变化?” 它通过可视化 “训练样本数” 与 “模型得分” 的关系,帮你判断数据量是否充足、模型是否过拟合 / 欠拟合。
学习曲线的原理与解读
学习曲线以 “训练样本数” 为横轴,“模型得分(如准确率、R²)” 为纵轴,同时绘制两条曲线:
- 训练曲线:模型在训练集上的得分(数据量越少,训练得分越高,因为模型容易记住少量样本);
- 验证曲线:模型在验证集上的得分(数据量越少,验证得分越不稳定,随着数据量增加,逐渐收敛)。
通过两条曲线的形状,可判断模型拟合状态:
| 拟合状态 | 曲线特征 | 核心原因 |
|---|---|---|
| 欠拟合 | 训练曲线和验证曲线得分都低,且两者接近 | 模型太简单(如用线性模型分非线性数据),无法捕捉数据规律 |
| 过拟合 | 训练曲线得分高,验证曲线得分低,且差距大 | 模型太复杂(如决策树深度过大),记住训练数据噪声,泛化差 |
| 拟合良好 | 训练曲线和验证曲线得分都高,且差距小 | 模型复杂度适中,既学会规律,又能泛化到新数据 |
绘制学习曲线(以随机森林分类为例)
from sklearn.datasets import load_digits
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import learning_curve
import matplotlib.pyplot as plt
import numpy as np
# 1. 加载数据(手写数字分类,64维特征)
X, y = load_digits(return_X_y=True)
# 2. 初始化模型(故意用复杂模型,模拟过拟合倾向)
model = RandomForestClassifier(n_estimators=100, max_depth=None, random_state=42)
# 3. 计算学习曲线数据
# train_sizes:训练样本比例(从10%到100%,取5个点)
train_sizes, train_scores, val_scores = learning_curve(
estimator=model,
X=X, y=y,
train_sizes=np.linspace(0.1, 1.0, 5), # 5个不同的训练样本比例
cv=5, # 5折交叉验证
scoring='accuracy', # 分类任务用准确率
n_jobs=-1
)
# 4. 计算得分的均值和标准差(消除交叉验证的波动)
train_mean = np.mean(train_scores, axis=1) # 每类样本数的训练平均得分
train_std = np.std(train_scores, axis=1) # 训练得分的标准差(误差线)
val_mean = np.mean(val_scores, axis=1) # 验证平均得分
val_std = np.std(val_scores, axis=1) # 验证得分的标准差
# 5. 绘制学习曲线
plt.figure(figsize=(10, 6))
# 绘制训练曲线(红色实线,带误差阴影)
plt.plot(train_sizes, train_mean, 'o-', color='r', label='Training Accuracy')
plt.fill_between(train_sizes, train_mean - train_std, train_mean + train_std, alpha=0.2, color='r')
# 绘制验证曲线(绿色实线,带误差阴影)
plt.plot(train_sizes, val_mean, 'o-', color='g', label='Validation Accuracy')
plt.fill_between(train_sizes, val_mean - val_std, val_mean + val_std, alpha=0.2, color='g')
# 添加标签和图例
plt.xlabel('Number of Training Samples', fontsize=12)
plt.ylabel('Accuracy', fontsize=12)
plt.title('Learning Curve (Random Forest on Digits Dataset)', fontsize=14)
plt.legend(loc='lower right')
plt.grid(alpha=0.3)
plt.show()
# 输出关键得分(判断拟合状态)
print(f"训练集最大平均准确率:{train_mean[-1]:.4f}") # 用全量数据训练的得分
print(f"验证集最大平均准确率:{val_mean[-1]:.4f}") # 用全量数据训练后的验证得分
print(f"训练-验证得分差:{train_mean[-1] - val_mean[-1]:.4f}") # 差距越小越好
输出结果:
训练集最大平均准确率:1.0000
验证集最大平均准确率:0.9405
训练-验证得分差:0.0595

- 判断数据量是否充足:若两条曲线仍在上升(未收敛),说明增加数据量能提升模型性能(需收集更多数据);若曲线已收敛,说明数据量足够,增加数据无意义。
- 诊断过拟合 / 欠拟合:如示例中,训练准确率≈1.0,验证准确率≈0.94,差距小且都高 → 拟合良好;若差距 > 0.1(如训练 1.0,验证 0.85)→ 过拟合。
验证曲线(Validation Curve)—— 看 “超参数” 对模型的影响
验证曲线的核心是回答一个问题:“当某个超参数变化时,模型的训练性能和泛化性能会如何变化?” 它聚焦单一超参数,帮你找到该参数的 “最优范围”。
验证曲线的原理与解读
验证曲线以 “超参数取值” 为横轴,“模型得分” 为纵轴,同样绘制训练曲线和验证曲线:
- 当超参数取值较小时(如 KNN 的 k=1):模型复杂,训练得分高,验证得分低 → 过拟合;
- 当超参数取值适中时(如 k=5):模型复杂度适中,训练和验证得分都高,且差距小 → 最优;
- 当超参数取值较大时(如 k=20):模型简单,训练和验证得分都低 → 欠拟合。
绘制验证曲线(以 KNN 的 k 值为例)
from sklearn.datasets import load_digits
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import validation_curve
import matplotlib.pyplot as plt
import numpy as np
# 1. 加载数据
X, y = load_digits(return_X_y=True)
# 2. 定义要分析的超参数:KNN的n_neighbors(k值)
param_name = 'n_neighbors'
param_range = np.arange(1, 20, 2) # k从1到19,步长2(共10个取值)
# 3. 计算验证曲线数据
train_scores, val_scores = validation_curve(
estimator=KNeighborsClassifier(),
X=X, y=y,
param_name=param_name, # 要分析的超参数名
param_range=param_range, # 超参数的取值范围
cv=5, # 5折交叉验证
scoring='accuracy',
n_jobs=-1
)
# 4. 计算均值和标准差
train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
val_mean = np.mean(val_scores, axis=1)
val_std = np.std(val_scores, axis=1)
# 5. 绘制验证曲线
plt.figure(figsize=(10, 6))
# 训练曲线
plt.plot(param_range, train_mean, 'o-', color='r', label='Training Accuracy')
plt.fill_between(param_range, train_mean - train_std, train_mean + train_std, alpha=0.2, color='r')
# 验证曲线
plt.plot(param_range, val_mean, 'o-', color='g', label='Validation Accuracy')
plt.fill_between(param_range, val_mean - val_std, val_mean + val_std, alpha=0.2, color='g')
# 添加标签和图例
plt.xlabel(f'Hyperparameter: {param_name}', fontsize=12)
plt.ylabel('Accuracy', fontsize=12)
plt.title(f'Validation Curve (KNN: {param_name} vs Accuracy)', fontsize=14)
plt.legend(loc='best')
plt.grid(alpha=0.3)
plt.show()
# 找到最优超参数
best_param_idx = np.argmax(val_mean)
best_param = param_range[best_param_idx]
best_val_score = val_mean[best_param_idx]
print(f"最优{k}值:{best_param},对应的验证准确率:{best_val_score:.4f}")
输出结果:
最优k值:3,对应的验证准确率:0.9666

- 定位超参数最优范围:如示例中,k=3~7 时验证得分最高,超过 7 后得分下降 → 最优 k 值在 3~7 之间,无需盲目测试所有 k 值;
- 理解超参数的影响趋势:如 k 值越小,模型越复杂(过拟合风险高);k 值越大,模型越简单(欠拟合风险高),帮你建立 “超参数 - 模型复杂度” 的直观认知。
学习曲线 vs 验证曲线
| 对比维度 | 学习曲线(Learning Curve) | 验证曲线(Validation Curve) |
|---|---|---|
| 横轴(自变量) | 训练样本数量(数据量) | 单一超参数的取值(如 k、max_depth) |
| 核心目标 | 判断数据量是否充足、模型拟合状态 | 找到超参数的最优范围 |
| 能解决的问题 | “要不要加数据?”“模型过拟合 / 欠拟合吗?” | “这个超参数设多少合适?” |
| 适用阶段 | 模型初期探索(数据准备阶段) | 模型调优阶段(超参数优化) |
在实际调优中,可结合两条曲线,按以下步骤快速定位问题:
- 第一步:画学习曲线 → 判断数据量和拟合状态:
- 若欠拟合 → 增加模型复杂度(如增大随机森林 max_depth、用非线性核 SVM);
- 若过拟合 → 降低模型复杂度(如减小 max_depth、增加正则化)或增加数据;
- 若数据不足 → 收集更多数据。
- 第二步:针对关键超参数画验证曲线 → 找到最优范围:
- 如随机森林的 max_depth、KNN 的 n_neighbors、SVM 的 C 值,分别画验证曲线,确定每个参数的最优区间。
- 第三步:结合网格 / 随机搜索精调 → 在验证曲线找到的最优范围内,用交叉验证做精细化搜索,确定最终超参数。
更多推荐


所有评论(0)