文章目录
-
- [一、准确率 99% 的谎言](#一、准确率 99% 的谎言)
- 二、分类评估指标:选对指标才能说真话
-
- [2.1 指标适用场景全景](#2.1 指标适用场景全景)
- [2.2 核心指标详解](#2.2 核心指标详解)
- [2.3 回归评估指标](#2.3 回归评估指标)
- [三、交叉验证策略:选错了 CV,评估就是幻觉](#三、交叉验证策略:选错了 CV,评估就是幻觉)
-
- [3.1 四种交叉验证策略对比](#3.1 四种交叉验证策略对比)
- [3.2 CV 策略选择错误示例](#3.2 CV 策略选择错误示例)
- [四、超参搜索进化:从 Grid Search 到贝叶斯优化](#四、超参搜索进化:从 Grid Search 到贝叶斯优化)
-
- [4.1 三种搜索策略对比](#4.1 三种搜索策略对比)
- [4.2 Optuna 实战](#4.2 Optuna 实战)
- 五、偏差-方差权衡:学习曲线诊断模型问题
-
- [5.1 学习曲线的三种形态](#5.1 学习曲线的三种形态)
- 六、集成方法与自动化模型选择
-
- [6.1 Stacking vs Voting](#6.1 Stacking vs Voting)
- [6.2 与 AutoML 的关系](#6.2 与 AutoML 的关系)
- 七、实战:信用卡欺诈检测的完整评估链路
-
- [7.1 完整代码](#7.1 完整代码)
- [7.2 关键设计决策](#7.2 关键设计决策)
- [7.3 效果对比](#7.3 效果对比)
- 八、小结
一、准确率 99% 的谎言
在一个信用卡欺诈检测场景中,欺诈交易仅占全部交易的 0.1%。一个模型如果将所有交易都预测为"正常",准确率将高达 99.9%------但它一个欺诈案例都检测不出来。这就是"准确率陷阱":在不平衡数据上,高准确率往往与模型质量无关。
模型评估的核心问题不是"分数有多高",而是"这个分数在什么条件下成立"。交叉验证的策略选择错误、评估指标使用不当、超参搜索方式不科学,都会导致评估结果成为自欺欺人的幻觉。本文从评估指标、交叉验证策略、超参搜索方法和模型选择策略四个维度,建立一套严谨的模型评估体系,并以信用卡欺诈检测为实战场景,演示从数据不平衡到 Optuna 自动调参的完整链路。
二、分类评估指标:选对指标才能说真话
2.1 指标适用场景全景
#mermaid-svg-tYpjZ9vA6i8e5mYG{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tYpjZ9vA6i8e5mYG .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tYpjZ9vA6i8e5mYG .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tYpjZ9vA6i8e5mYG .error-icon{fill:#552222;}#mermaid-svg-tYpjZ9vA6i8e5mYG .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tYpjZ9vA6i8e5mYG .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tYpjZ9vA6i8e5mYG .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tYpjZ9vA6i8e5mYG .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tYpjZ9vA6i8e5mYG .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tYpjZ9vA6i8e5mYG .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tYpjZ9vA6i8e5mYG .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tYpjZ9vA6i8e5mYG .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tYpjZ9vA6i8e5mYG .marker.cross{stroke:#333333;}#mermaid-svg-tYpjZ9vA6i8e5mYG svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tYpjZ9vA6i8e5mYG p{margin:0;}#mermaid-svg-tYpjZ9vA6i8e5mYG .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-tYpjZ9vA6i8e5mYG .cluster-label text{fill:#333;}#mermaid-svg-tYpjZ9vA6i8e5mYG .cluster-label span{color:#333;}#mermaid-svg-tYpjZ9vA6i8e5mYG .cluster-label span p{background-color:transparent;}#mermaid-svg-tYpjZ9vA6i8e5mYG .label text,#mermaid-svg-tYpjZ9vA6i8e5mYG span{fill:#333;color:#333;}#mermaid-svg-tYpjZ9vA6i8e5mYG .node rect,#mermaid-svg-tYpjZ9vA6i8e5mYG .node circle,#mermaid-svg-tYpjZ9vA6i8e5mYG .node ellipse,#mermaid-svg-tYpjZ9vA6i8e5mYG .node polygon,#mermaid-svg-tYpjZ9vA6i8e5mYG .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-tYpjZ9vA6i8e5mYG .rough-node .label text,#mermaid-svg-tYpjZ9vA6i8e5mYG .node .label text,#mermaid-svg-tYpjZ9vA6i8e5mYG .image-shape .label,#mermaid-svg-tYpjZ9vA6i8e5mYG .icon-shape .label{text-anchor:middle;}#mermaid-svg-tYpjZ9vA6i8e5mYG .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-tYpjZ9vA6i8e5mYG .rough-node .label,#mermaid-svg-tYpjZ9vA6i8e5mYG .node .label,#mermaid-svg-tYpjZ9vA6i8e5mYG .image-shape .label,#mermaid-svg-tYpjZ9vA6i8e5mYG .icon-shape .label{text-align:center;}#mermaid-svg-tYpjZ9vA6i8e5mYG .node.clickable{cursor:pointer;}#mermaid-svg-tYpjZ9vA6i8e5mYG .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-tYpjZ9vA6i8e5mYG .arrowheadPath{fill:#333333;}#mermaid-svg-tYpjZ9vA6i8e5mYG .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-tYpjZ9vA6i8e5mYG .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-tYpjZ9vA6i8e5mYG .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tYpjZ9vA6i8e5mYG .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-tYpjZ9vA6i8e5mYG .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tYpjZ9vA6i8e5mYG .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-tYpjZ9vA6i8e5mYG .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-tYpjZ9vA6i8e5mYG .cluster text{fill:#333;}#mermaid-svg-tYpjZ9vA6i8e5mYG .cluster span{color:#333;}#mermaid-svg-tYpjZ9vA6i8e5mYG div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-tYpjZ9vA6i8e5mYG .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-tYpjZ9vA6i8e5mYG rect.text{fill:none;stroke-width:0;}#mermaid-svg-tYpjZ9vA6i8e5mYG .icon-shape,#mermaid-svg-tYpjZ9vA6i8e5mYG .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-tYpjZ9vA6i8e5mYG .icon-shape p,#mermaid-svg-tYpjZ9vA6i8e5mYG .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-tYpjZ9vA6i8e5mYG .icon-shape .label rect,#mermaid-svg-tYpjZ9vA6i8e5mYG .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-tYpjZ9vA6i8e5mYG .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-tYpjZ9vA6i8e5mYG .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-tYpjZ9vA6i8e5mYG :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 平衡
正例 ~ 50%
轻度不平衡
正例 10%~30%
严重不平衡
正例 < 5%
减少误报
宁可漏检
减少漏检
宁可误报
两者兼顾
分类问题
类别是否平衡?
Accuracy / F1 / ROC-AUC
F1-Score /
ROC-AUC
PR-AUC /
Precision-Recall
可解释性强
适合业务汇报
F1 兼顾 Precision 和 Recall
PR-AUC 对少数类敏感
ROC-AUC 会高估性能
业务更看重什么?
高 Precision
严格阈值
高 Recall
宽松阈值
F1-Score
2.2 核心指标详解
混淆矩阵四象限
| 实际情况 \ 预测 | 预测为正 | 预测为负 |
|---|---|---|
| 实际为正 | TP(真正例) | FN(假负例) |
| 实际为负 | FP(假正例) | TN(真负例) |
Precision(精确率)
在所有预测为正例的样本中,实际为正例的比例。适用于"宁可漏检,不可误报"的场景,如垃圾邮件过滤------误将正常邮件标为垃圾的代价远高于漏掉一封垃圾邮件。
Precision = TP / (TP + FP)
Recall(召回率)
在所有实际为正例的样本中,被正确预测为正例的比例。适用于"宁可误报,不可漏检"的场景,如疾病筛查------漏掉一个病人的代价远高于多检查几个健康人。
Recall = TP / (TP + FN)
F1-Score
Precision 和 Recall 的调和平均,在两者之间取得平衡。当 Precision 和 Recall 存在冲突时,F1 是更全面的指标。
F1 = 2 × Precision × Recall / (Precision + Recall)
ROC-AUC vs PR-AUC
ROC 曲线以假正例率(FPR)为横轴、真正例率(TPR)为横轴,AUC 表示随机抽取一个正例和一个负例,模型将正例排在负例前面的概率。ROC-AUC 在不平衡数据上表现稳定,但对少数类的变化不敏感。
PR 曲线以 Recall 为横轴、Precision 为纵轴,PR-AUC 直接衡量模型在正例上的查准-查全权衡。在类别严重不平衡(正例 < 5%)时,PR-AUC 比 ROC-AUC 更能反映模型的真实能力------因为 ROC 的 FPR 分母(TN + FP)中 TN 数量巨大,FP 的变化几乎不会改变 FPR。
python
from sklearn.metrics import roc_auc_score, average_precision_score
roc_auc = roc_auc_score(y_true, y_proba)
pr_auc = average_precision_score(y_true, y_proba)
# 严重不平衡时,PR-AUC 才是可信指标
print(f"ROC-AUC: {roc_auc:.4f}") # 可能高达 0.98
print(f"PR-AUC: {pr_auc:.4f}") # 可能只有 0.45
| 指标 | 公式 | 适用场景 | 不平衡数据可靠性 |
|---|---|---|---|
| Accuracy | (TP+TN)/(TP+TN+FP+FN) | 类别完全平衡 | 低 |
| Precision | TP/(TP+FP) | 重视误报控制 | 中 |
| Recall | TP/(TP+FN) | 重视漏检控制 | 中 |
| F1 | 2PR/(P+R) | 平衡 Precision/Recall | 中 |
| ROC-AUC | ROC 曲线下面积 | 通用,跨模型比较 | 中高 |
| PR-AUC | PR 曲线下面积 | 严重不平衡(正例<5%) | 高 |
2.3 回归评估指标
| 指标 | 公式 | 特性 | 适用场景 |
|---|---|---|---|
| MAE | mean(|y - ŷ|) | 对异常值不敏感 | 异常值较多的数据 |
| MSE | mean((y - ŷ)²) | 对大误差惩罚重 | 大误差代价高的场景 |
| RMSE | sqrt(MSE) | 与目标变量同量纲 | 通用,最常用 |
| R² | 1 - MSE/var(y) | 被解释方差比例 | 模型拟合度评估 |
| MAPE | mean(|y - ŷ|/y) | 百分比误差 | 目标值范围跨度大的场景 |
RMSE 对大误差敏感的原因在于平方运算:一个误差为 10 的样本对 MSE 的贡献等于 100 个误差为 1 的样本。因此,在房价预测等场景中,如果大误差(如豪宅预测偏差)的代价远高于小误差,RMSE 是比 MAE 更合适的选择。
三、交叉验证策略:选错了 CV,评估就是幻觉
交叉验证的核心目的是估计模型在未见数据上的泛化能力。但 CV 策略的选择直接影响这个估计的可信度------用错了 CV 策略,评估结果可能比单折划分更不可靠。
3.1 四种交叉验证策略对比
#mermaid-svg-AF5c6c6flGxypkw5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-AF5c6c6flGxypkw5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-AF5c6c6flGxypkw5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-AF5c6c6flGxypkw5 .error-icon{fill:#552222;}#mermaid-svg-AF5c6c6flGxypkw5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-AF5c6c6flGxypkw5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-AF5c6c6flGxypkw5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-AF5c6c6flGxypkw5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-AF5c6c6flGxypkw5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-AF5c6c6flGxypkw5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-AF5c6c6flGxypkw5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-AF5c6c6flGxypkw5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-AF5c6c6flGxypkw5 .marker.cross{stroke:#333333;}#mermaid-svg-AF5c6c6flGxypkw5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-AF5c6c6flGxypkw5 p{margin:0;}#mermaid-svg-AF5c6c6flGxypkw5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-AF5c6c6flGxypkw5 .cluster-label text{fill:#333;}#mermaid-svg-AF5c6c6flGxypkw5 .cluster-label span{color:#333;}#mermaid-svg-AF5c6c6flGxypkw5 .cluster-label span p{background-color:transparent;}#mermaid-svg-AF5c6c6flGxypkw5 .label text,#mermaid-svg-AF5c6c6flGxypkw5 span{fill:#333;color:#333;}#mermaid-svg-AF5c6c6flGxypkw5 .node rect,#mermaid-svg-AF5c6c6flGxypkw5 .node circle,#mermaid-svg-AF5c6c6flGxypkw5 .node ellipse,#mermaid-svg-AF5c6c6flGxypkw5 .node polygon,#mermaid-svg-AF5c6c6flGxypkw5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-AF5c6c6flGxypkw5 .rough-node .label text,#mermaid-svg-AF5c6c6flGxypkw5 .node .label text,#mermaid-svg-AF5c6c6flGxypkw5 .image-shape .label,#mermaid-svg-AF5c6c6flGxypkw5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-AF5c6c6flGxypkw5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-AF5c6c6flGxypkw5 .rough-node .label,#mermaid-svg-AF5c6c6flGxypkw5 .node .label,#mermaid-svg-AF5c6c6flGxypkw5 .image-shape .label,#mermaid-svg-AF5c6c6flGxypkw5 .icon-shape .label{text-align:center;}#mermaid-svg-AF5c6c6flGxypkw5 .node.clickable{cursor:pointer;}#mermaid-svg-AF5c6c6flGxypkw5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-AF5c6c6flGxypkw5 .arrowheadPath{fill:#333333;}#mermaid-svg-AF5c6c6flGxypkw5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-AF5c6c6flGxypkw5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-AF5c6c6flGxypkw5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AF5c6c6flGxypkw5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-AF5c6c6flGxypkw5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AF5c6c6flGxypkw5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-AF5c6c6flGxypkw5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-AF5c6c6flGxypkw5 .cluster text{fill:#333;}#mermaid-svg-AF5c6c6flGxypkw5 .cluster span{color:#333;}#mermaid-svg-AF5c6c6flGxypkw5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-AF5c6c6flGxypkw5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-AF5c6c6flGxypkw5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-AF5c6c6flGxypkw5 .icon-shape,#mermaid-svg-AF5c6c6flGxypkw5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-AF5c6c6flGxypkw5 .icon-shape p,#mermaid-svg-AF5c6c6flGxypkw5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-AF5c6c6flGxypkw5 .icon-shape .label rect,#mermaid-svg-AF5c6c6flGxypkw5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-AF5c6c6flGxypkw5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-AF5c6c6flGxypkw5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-AF5c6c6flGxypkw5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 普通 IID 数据
类别不平衡
存在分组结构
同组样本相关
时间序列
数据集
数据特点?
KFold
随机分 K 折
StratifiedKFold
每折保持类别比例
GroupKFold
同组不出现在多折
TimeSeriesSplit
训练集只包含过去数据
通用场景
默认选择
分类任务必用
尤其不平衡
医学影像
同一患者多切片
推荐系统同一用户多条记录
时间序列/金融预测
防止未来信息泄漏
KFold
将数据随机分为 K 份,轮流用 K-1 份训练、1 份验证。K=5 或 K=10 是最常用的配置。适用于数据独立同分布(IID)的场景。
python
from sklearn.model_selection import KFold
kf = KFold(n_splits=5, shuffle=True, random_state=42)
for train_idx, val_idx in kf.split(X):
X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
StratifiedKFold
KFold 的改进版,确保每一折中各类别的比例与整体数据一致。在分类任务中,尤其是类别不平衡时,StratifiedKFold 是必须的选择。普通 KFold 可能将某一折的所有正例都分到了验证集,导致训练集没有正例可学。
python
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, X_val = X.iloc[train_idx], X.iloc[val_idx]
GroupKFold
当数据存在分组结构时使用。例如,医学影像数据中同一患者的多张切片高度相关,如果将同一患者的切片分散到训练集和验证集,模型会"记住"患者特征而非学到真正的病理模式。GroupKFold 确保同一组的所有样本要么全在训练集,要么全在验证集。
python
from sklearn.model_selection import GroupKFold
gkf = GroupKFold(n_splits=5)
for train_idx, val_idx in gkf.split(X, y, groups=patient_ids):
X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
TimeSeriesSplit
时间序列数据的专用 CV 策略。训练集只包含验证集之前的时间点,严格模拟"用过去预测未来"的真实场景。普通 KFold 会随机打乱时间顺序,导致未来数据泄漏到训练集------在时间序列中这是致命错误。
python
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
for train_idx, val_idx in tscv.split(X):
# 训练集索引总是小于验证集索引
X_train, X_val = X.iloc[train_idx], X.iloc[val_idx]
3.2 CV 策略选择错误示例
| 数据类型 | 错误策略 | 后果 |
|---|---|---|
| 欺诈检测(0.1% 正例) | KFold | 某折可能无正例,训练失效 |
| 患者影像(同患者多切片) | KFold | 患者特征泄漏,高估性能 |
| 股票价格预测 | KFold | 未来价格泄漏到训练集 |
| 多城市房价 | KFold | 城市间分布差异被忽略 |
四、超参搜索进化:从 Grid Search 到贝叶斯优化
4.1 三种搜索策略对比
#mermaid-svg-6iK70sGXtZs5smRE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6iK70sGXtZs5smRE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6iK70sGXtZs5smRE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6iK70sGXtZs5smRE .error-icon{fill:#552222;}#mermaid-svg-6iK70sGXtZs5smRE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6iK70sGXtZs5smRE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6iK70sGXtZs5smRE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6iK70sGXtZs5smRE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6iK70sGXtZs5smRE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6iK70sGXtZs5smRE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6iK70sGXtZs5smRE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6iK70sGXtZs5smRE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6iK70sGXtZs5smRE .marker.cross{stroke:#333333;}#mermaid-svg-6iK70sGXtZs5smRE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6iK70sGXtZs5smRE p{margin:0;}#mermaid-svg-6iK70sGXtZs5smRE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-6iK70sGXtZs5smRE .cluster-label text{fill:#333;}#mermaid-svg-6iK70sGXtZs5smRE .cluster-label span{color:#333;}#mermaid-svg-6iK70sGXtZs5smRE .cluster-label span p{background-color:transparent;}#mermaid-svg-6iK70sGXtZs5smRE .label text,#mermaid-svg-6iK70sGXtZs5smRE span{fill:#333;color:#333;}#mermaid-svg-6iK70sGXtZs5smRE .node rect,#mermaid-svg-6iK70sGXtZs5smRE .node circle,#mermaid-svg-6iK70sGXtZs5smRE .node ellipse,#mermaid-svg-6iK70sGXtZs5smRE .node polygon,#mermaid-svg-6iK70sGXtZs5smRE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-6iK70sGXtZs5smRE .rough-node .label text,#mermaid-svg-6iK70sGXtZs5smRE .node .label text,#mermaid-svg-6iK70sGXtZs5smRE .image-shape .label,#mermaid-svg-6iK70sGXtZs5smRE .icon-shape .label{text-anchor:middle;}#mermaid-svg-6iK70sGXtZs5smRE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-6iK70sGXtZs5smRE .rough-node .label,#mermaid-svg-6iK70sGXtZs5smRE .node .label,#mermaid-svg-6iK70sGXtZs5smRE .image-shape .label,#mermaid-svg-6iK70sGXtZs5smRE .icon-shape .label{text-align:center;}#mermaid-svg-6iK70sGXtZs5smRE .node.clickable{cursor:pointer;}#mermaid-svg-6iK70sGXtZs5smRE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-6iK70sGXtZs5smRE .arrowheadPath{fill:#333333;}#mermaid-svg-6iK70sGXtZs5smRE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-6iK70sGXtZs5smRE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-6iK70sGXtZs5smRE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6iK70sGXtZs5smRE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-6iK70sGXtZs5smRE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6iK70sGXtZs5smRE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-6iK70sGXtZs5smRE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-6iK70sGXtZs5smRE .cluster text{fill:#333;}#mermaid-svg-6iK70sGXtZs5smRE .cluster span{color:#333;}#mermaid-svg-6iK70sGXtZs5smRE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-6iK70sGXtZs5smRE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-6iK70sGXtZs5smRE rect.text{fill:none;stroke-width:0;}#mermaid-svg-6iK70sGXtZs5smRE .icon-shape,#mermaid-svg-6iK70sGXtZs5smRE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-6iK70sGXtZs5smRE .icon-shape p,#mermaid-svg-6iK70sGXtZs5smRE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-6iK70sGXtZs5smRE .icon-shape .label rect,#mermaid-svg-6iK70sGXtZs5smRE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-6iK70sGXtZs5smRE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-6iK70sGXtZs5smRE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-6iK70sGXtZs5smRE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 超参搜索
Grid Search
全组合穷举
Random Search
随机采样
Bayesian Optimization
智能采样
优点:保证找到最优
缺点:指数爆炸
优点:更高效
缺点:可能遗漏好区域
优点:最快收敛
缺点:需多次迭代
100 个参数组合
5 折 CV = 500 次训练
100 次随机采样
5 折 CV = 500 次训练
但覆盖更广
100 次 trial
每次利用历史结果
指导下一步采样
Grid Search(网格搜索)
对每个超参定义一组候选值,穷举所有组合。假设 4 个超参,每个 5 个候选值,总组合数为 5⁴ = 625。配合 5 折 CV,需要训练 3125 次模型。Grid Search 在超参维度低时可行,但维度稍高就会陷入"组合爆炸"。
python
from sklearn.model_selection import GridSearchCV
param_grid = {
'max_depth': [3, 5, 7],
'learning_rate': [0.01, 0.1],
'n_estimators': [100, 200]
}
grid = GridSearchCV(
XGBClassifier(random_state=42),
param_grid,
cv=StratifiedKFold(5),
scoring='average_precision', # PR-AUC
n_jobs=-1
)
grid.fit(X, y)
Random Search(随机搜索)
在超参空间中随机采样固定次数(如 100 次),而非穷举所有组合。Bergstra & Bengio 的论文证明,在相同计算预算下,Random Search 通常能找到与 Grid Search 相当甚至更好的解------因为并非所有超参都同等重要,Random Search 给重要超参更多探索机会。
python
from sklearn.model_selection import RandomizedSearchCV
from scipy.stats import uniform, randint
param_distributions = {
'max_depth': randint(3, 10),
'learning_rate': uniform(0.001, 0.3),
'n_estimators': randint(100, 1000)
}
random = RandomizedSearchCV(
XGBClassifier(random_state=42),
param_distributions,
n_iter=100,
cv=StratifiedKFold(5),
scoring='average_precision',
n_jobs=-1,
random_state=42
)
Bayesian Optimization(贝叶斯优化)
利用已完成的 trial 结果构建超参空间的代理模型(通常是高斯过程或 Tree-structured Parzen Estimator),预测哪些区域可能产生更好的结果,从而智能地选择下一个采样点。相比 Random Search 的"盲目尝试",Bayesian Optimization 是"有依据地尝试"。
Optuna 是当前 Python 生态中最成熟的贝叶斯优化框架。
4.2 Optuna 实战
Optuna 的核心 API 简洁但功能强大。定义一个 objective 函数,在其中声明待搜索的超参,Optuna 会自动管理搜索过程。
python
import optuna
from sklearn.model_selection import cross_val_score
from xgboost import XGBClassifier
# 早停回调------如果 PR-AUC 连续 10 轮无提升则终止当前 trial
early_stop = optuna.integration.XGBoostPruningCallback
def objective(trial):
# 声明搜索空间
params = {
'max_depth': trial.suggest_int('max_depth', 3, 10),
'learning_rate': trial.suggest_float('learning_rate', 1e-3, 1e-1, log=True),
'n_estimators': trial.suggest_int('n_estimators', 100, 2000),
'subsample': trial.suggest_float('subsample', 0.5, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),
'min_child_weight': trial.suggest_int('min_child_weight', 1, 10),
'gamma': trial.suggest_float('gamma', 1e-8, 1.0, log=True),
'scale_pos_weight': trial.suggest_float('scale_pos_weight', 1, 100, log=True),
'random_state': 42,
'use_label_encoder': False,
'eval_metric': 'logloss'
}
model = XGBClassifier(**params)
# 使用 StratifiedKFold + PR-AUC
scores = cross_val_score(
model, X, y,
cv=StratifiedKFold(n_splits=5, shuffle=True, random_state=42),
scoring='average_precision',
n_jobs=-1
)
return scores.mean()
# 创建 study,目标最大化 PR-AUC
study = optuna.create_study(
direction='maximize',
pruner=optuna.pruners.MedianPruner(n_startup_trials=10)
)
# 运行 100 次 trial
study.optimize(objective, n_trials=100, show_progress_bar=True)
# 最优结果
print(f"最优 PR-AUC: {study.best_value:.4f}")
print(f"最优参数: {study.best_params}")
Optuna 的进阶特性
对数尺度采样 :suggest_float(..., log=True) 对 learning_rate 这类跨多个数量级的超参非常必要。线性采样会在 0.001~0.1 之间均匀取值,而对数采样会在每个数量级(0.0010.01、0.010.1)均匀取值,更好地覆盖低学习率区域。
早停剪枝(Pruning) :MedianPruner 在 trial 运行过程中,如果当前 trial 的表现低于已完成 trial 的中位数,会提前终止该 trial,节省计算资源。
可视化分析
python
import optuna.visualization as vis
# 优化历史------每个 trial 的 PR-AUC 随时间变化
fig1 = vis.plot_optimization_history(study)
fig1.show()
# 参数重要性------哪些超参对性能影响最大
fig2 = vis.plot_param_importances(study)
fig2.show()
# 并行坐标图------高维参数空间的可视化
fig3 = vis.plot_parallel_coordinate(study)
fig3.show()
# 等高线图------两个超参的交互效应
fig4 = vis.plot_contour(study, params=['max_depth', 'learning_rate'])
fig4.show()
五、偏差-方差权衡:学习曲线诊断模型问题
模型在训练集上表现很好但在验证集上很差,这是过拟合(高方差)。模型在训练集和验证集上表现都很差,这是欠拟合(高偏差)。学习曲线是诊断这两种问题的标准工具。
5.1 学习曲线的三种形态
#mermaid-svg-hkJs3wb24Q5xRfzB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-hkJs3wb24Q5xRfzB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-hkJs3wb24Q5xRfzB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-hkJs3wb24Q5xRfzB .error-icon{fill:#552222;}#mermaid-svg-hkJs3wb24Q5xRfzB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-hkJs3wb24Q5xRfzB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-hkJs3wb24Q5xRfzB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-hkJs3wb24Q5xRfzB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-hkJs3wb24Q5xRfzB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-hkJs3wb24Q5xRfzB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-hkJs3wb24Q5xRfzB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-hkJs3wb24Q5xRfzB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-hkJs3wb24Q5xRfzB .marker.cross{stroke:#333333;}#mermaid-svg-hkJs3wb24Q5xRfzB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-hkJs3wb24Q5xRfzB p{margin:0;}#mermaid-svg-hkJs3wb24Q5xRfzB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-hkJs3wb24Q5xRfzB .cluster-label text{fill:#333;}#mermaid-svg-hkJs3wb24Q5xRfzB .cluster-label span{color:#333;}#mermaid-svg-hkJs3wb24Q5xRfzB .cluster-label span p{background-color:transparent;}#mermaid-svg-hkJs3wb24Q5xRfzB .label text,#mermaid-svg-hkJs3wb24Q5xRfzB span{fill:#333;color:#333;}#mermaid-svg-hkJs3wb24Q5xRfzB .node rect,#mermaid-svg-hkJs3wb24Q5xRfzB .node circle,#mermaid-svg-hkJs3wb24Q5xRfzB .node ellipse,#mermaid-svg-hkJs3wb24Q5xRfzB .node polygon,#mermaid-svg-hkJs3wb24Q5xRfzB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-hkJs3wb24Q5xRfzB .rough-node .label text,#mermaid-svg-hkJs3wb24Q5xRfzB .node .label text,#mermaid-svg-hkJs3wb24Q5xRfzB .image-shape .label,#mermaid-svg-hkJs3wb24Q5xRfzB .icon-shape .label{text-anchor:middle;}#mermaid-svg-hkJs3wb24Q5xRfzB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-hkJs3wb24Q5xRfzB .rough-node .label,#mermaid-svg-hkJs3wb24Q5xRfzB .node .label,#mermaid-svg-hkJs3wb24Q5xRfzB .image-shape .label,#mermaid-svg-hkJs3wb24Q5xRfzB .icon-shape .label{text-align:center;}#mermaid-svg-hkJs3wb24Q5xRfzB .node.clickable{cursor:pointer;}#mermaid-svg-hkJs3wb24Q5xRfzB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-hkJs3wb24Q5xRfzB .arrowheadPath{fill:#333333;}#mermaid-svg-hkJs3wb24Q5xRfzB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-hkJs3wb24Q5xRfzB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-hkJs3wb24Q5xRfzB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hkJs3wb24Q5xRfzB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-hkJs3wb24Q5xRfzB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hkJs3wb24Q5xRfzB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-hkJs3wb24Q5xRfzB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-hkJs3wb24Q5xRfzB .cluster text{fill:#333;}#mermaid-svg-hkJs3wb24Q5xRfzB .cluster span{color:#333;}#mermaid-svg-hkJs3wb24Q5xRfzB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-hkJs3wb24Q5xRfzB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-hkJs3wb24Q5xRfzB rect.text{fill:none;stroke-width:0;}#mermaid-svg-hkJs3wb24Q5xRfzB .icon-shape,#mermaid-svg-hkJs3wb24Q5xRfzB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-hkJs3wb24Q5xRfzB .icon-shape p,#mermaid-svg-hkJs3wb24Q5xRfzB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-hkJs3wb24Q5xRfzB .icon-shape .label rect,#mermaid-svg-hkJs3wb24Q5xRfzB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-hkJs3wb24Q5xRfzB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-hkJs3wb24Q5xRfzB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-hkJs3wb24Q5xRfzB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 平衡状态
训练分数略高于验证分数
差距小且收敛
继续增加数据
边际收益递减
高方差(过拟合)
训练分数 >> 验证分数
差距大
增加数据有帮助
解决方案:
正则化 / 减少特征 / 更多数据
高偏差(欠拟合)
训练分数 ~ 验证分数
两者都低
增加数据无帮助
解决方案:
增加特征 / 增加模型复杂度
python
from sklearn.model_selection import learning_curve
import numpy as np
import matplotlib.pyplot as plt
train_sizes, train_scores, val_scores = learning_curve(
estimator=model, X=X, y=y,
cv=StratifiedKFold(5),
scoring='average_precision',
train_sizes=np.linspace(0.1, 1.0, 10),
n_jobs=-1
)
train_mean = train_scores.mean(axis=1)
val_mean = val_scores.mean(axis=1)
plt.plot(train_sizes, train_mean, label='Training PR-AUC')
plt.plot(train_sizes, val_mean, label='Validation PR-AUC')
plt.fill_between(train_sizes, train_mean - train_scores.std(axis=1),
train_mean + train_scores.std(axis=1), alpha=0.1)
plt.fill_between(train_sizes, val_mean - val_scores.std(axis=1),
val_mean + val_scores.std(axis=1), alpha=0.1)
plt.xlabel('Training Set Size')
plt.ylabel('PR-AUC')
plt.legend()
plt.title('Learning Curve')
plt.show()
六、集成方法与自动化模型选择
6.1 Stacking vs Voting
VotingClassifier(投票集成)
多个基模型独立预测,按投票规则(硬投票:多数表决;软投票:概率加权平均)得出最终结果。实现简单,但基模型间差异越大,集成效果越好。
python
from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from xgboost import XGBClassifier
voting = VotingClassifier(
estimators=[
('lr', LogisticRegression(max_iter=1000)),
('rf', RandomForestClassifier(n_estimators=200)),
('xgb', XGBClassifier(use_label_encoder=False, eval_metric='logloss'))
],
voting='soft' # 软投票通常优于硬投票
)
StackingClassifier(堆叠集成)
两层结构:第一层用多个基模型生成预测结果(元特征),第二层用另一个模型(元学习器,通常为逻辑回归)学习如何组合这些元特征。Stacking 比 Voting 更灵活,但需要注意第一层模型需用交叉验证生成元特征,防止数据泄漏。
python
from sklearn.ensemble import StackingClassifier
stacking = StackingClassifier(
estimators=[
('lr', LogisticRegression(max_iter=1000)),
('rf', RandomForestClassifier(n_estimators=200)),
('xgb', XGBClassifier(use_label_encoder=False, eval_metric='logloss'))
],
final_estimator=LogisticRegression(max_iter=1000),
cv=5,
passthrough=False # 是否将原始特征也传给元学习器
)
6.2 与 AutoML 的关系
Stacking 和 Voting 是 AutoML 框架(如 AutoGluon、auto-sklearn)的核心组件。AutoML 的本质是自动完成"模型选择 + 超参优化 + 集成"的循环。对于需要快速原型验证的场景,AutoGluon 可以在一行代码内完成上述全部流程:
python
from autogluon.tabular import TabularPredictor
predictor = TabularPredictor(label='target', eval_metric='average_precision').fit(
train_data=df,
presets='best_quality',
time_limit=3600
)
但 AutoML 的黑盒特性意味着难以调试和解释。在生产环境中,理解每个超参的作用、每个评估指标的含义,仍然是不可替代的工程能力。
七、实战:信用卡欺诈检测的完整评估链路
这是一个典型的极端不平衡分类问题:欺诈交易仅占 0.1%,评估指标、CV 策略和采样方法的选择都直接影响模型效果。
7.1 完整代码
python
import pandas as pd
import numpy as np
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.metrics import classification_report, average_precision_score
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline
from xgboost import XGBClassifier
import optuna
# 加载数据(creditcard.csv 是公开数据集)
df = pd.read_csv('creditcard.csv')
X = df.drop('Class', axis=1)
y = df['Class']
# 特征标准化(除 Time 和 Amount 外,其他特征已 PCA 处理)
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X[['Time', 'Amount']] = scaler.fit_transform(X[['Time', 'Amount']])
# Optuna 目标函数
def objective(trial):
params = {
'max_depth': trial.suggest_int('max_depth', 3, 10),
'learning_rate': trial.suggest_float('learning_rate', 1e-3, 3e-1, log=True),
'n_estimators': trial.suggest_int('n_estimators', 100, 2000),
'subsample': trial.suggest_float('subsample', 0.5, 1.0),
'colsample_bytree': trial.suggest_float('colsample_bytree', 0.5, 1.0),
'scale_pos_weight': trial.suggest_float('scale_pos_weight', 10, 1000, log=True),
'min_child_weight': trial.suggest_int('min_child_weight', 1, 10),
'random_state': 42,
'use_label_encoder': False,
'eval_metric': 'logloss'
}
model = XGBClassifier(**params)
# StratifiedKFold 保证每折都包含欺诈样本
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# 注意:SMOTE 必须在每折内部应用,不能全局过采样后再 CV
scores = []
for train_idx, val_idx in skf.split(X, y):
X_train_fold, X_val_fold = X.iloc[train_idx], X.iloc[val_idx]
y_train_fold, y_val_fold = y.iloc[train_idx], y.iloc[val_idx]
# 只在训练 fold 内做 SMOTE
smote = SMOTE(random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train_fold, y_train_fold)
model.fit(X_resampled, y_resampled)
y_proba = model.predict_proba(X_val_fold)[:, 1]
scores.append(average_precision_score(y_val_fold, y_proba))
return np.mean(scores)
# 运行优化
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=50, show_progress_bar=True)
print(f"最优 PR-AUC: {study.best_value:.4f}")
print(f"最优参数: {study.best_params}")
7.2 关键设计决策
| 决策点 | 选择 | 原因 |
|---|---|---|
| 评估指标 | PR-AUC | 类别极度不平衡(0.1%),ROC-AUC 会高估 |
| CV 策略 | StratifiedKFold | 保证每折都有欺诈样本 |
| 采样方法 | SMOTE(折内应用) | 全局 SMOTE 会导致验证集数据泄漏 |
| 超参搜索 | Optuna 贝叶斯优化 | 50 次 trial 远超 Grid Search 的覆盖能力 |
| 类别权重 | scale_pos_weight | XGBoost 原生支持,与 SMOTE 可叠加使用 |
7.3 效果对比
| 方案 | PR-AUC | 说明 |
|---|---|---|
| 基线(无处理,Accuracy 评估) | 0.78 | 被 Accuracy 误导,实际 PR-AUC 很低 |
| + StratifiedKFold + PR-AUC | 0.82 | 正确的评估方式暴露了真实性能 |
| + SMOTE 过采样 | 0.89 | 折内 SMOTE 缓解了类别不平衡 |
| + Optuna 调参 | 0.93 | 贝叶斯优化找到最优超参组合 |
| + scale_pos_weight 调优 | 0.95 | 类别权重与 SMOTE 的协同效应 |
八、小结
模型评估的可靠性建立在三个正确选择之上:正确的评估指标、正确的交叉验证策略、正确的超参搜索方法。
在不平衡数据上,Accuracy 是最大的谎言。PR-AUC 比 ROC-AUC 更能反映模型对少数类的识别能力。StratifiedKFold 是分类任务的默认选择,GroupKFold 用于分组数据,TimeSeriesSplit 用于时序数据------用错了 CV 策略,评估结果就是自欺欺人的幻觉。
超参搜索从 Grid Search 的全组合穷举,进化到 Random Search 的随机采样,再到 Bayesian Optimization 的智能采样。Optuna 的 log=True 对数尺度、MedianPruner 早停剪枝、以及可视化分析工具,将调参从"玄学"变成了有据可依的工程实践。
学习曲线是诊断模型问题的 X 光片:训练集和验证集分数都低 → 高偏差,需要增加特征或模型复杂度;训练集远高于验证集 → 高方差,需要正则化或更多数据。
此前专栏关于特征工程系统方法论、scikit-learn Pipeline 工程化以及 Pandas 数据处理的文章,为本文提供了从特征构造到模型评估的完整上游支撑。如果本文对模型评估与调优实践有所启发,欢迎点赞、收藏与关注。