🔄 模块5:"交叉验证和过拟合:构建能够泛化的模型"

目标:

理解为什么在训练数据上表现良好的模型可能在现实世界中失败。学会使用稳健的验证技术检测、预防和度量过拟合。


5.1 什么是过拟合?

过拟合发生在模型过度学习训练数据时——包括噪声和随机模式——而不是一般模式。结果:

  • ✅ 在训练数据上表现优秀。
  • ❌ 在新数据(测试或生产)上表现差。

想象一个学生死记硬背考试答案而不是理解概念。在真正的考试中,他们失败了。


5.2 如何检测过拟合?

最简单的方法:比较在训练集与验证/测试集上的表现。

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

模型 = LogisticRegression()
模型.fit(X_train, y_train)

# 训练表现
y_train_pred = 模型.predict(X_train)
train_acc = accuracy_score(y_train, y_train_pred)

# 测试表现
y_test_pred = 模型.predict(X_test)
test_acc = accuracy_score(y_test, y_test_pred)

print(f"训练准确性: {train_acc:.4f}")
print(f"测试准确性:     {test_acc:.4f}")

# 如果 train_acc >> test_acc → 过拟合!

⚠️ 典型例子:

  • 训练准确性: 0.998
  • 测试准确性: 0.821
    → 模型过拟合。

5.3 什么导致过拟合?

  • 模型过于复杂(例如,在小数据集上的深度为20的决策树)。
  • 训练数据太少
  • 特征太多(维度诅咒)。
  • 数据中的噪声(错误标签,未处理的异常值)。

5.4 避免过拟合的解决方案

➤ 正则化

在损失函数中添加惩罚项以防止系数变得过大。

# 带L2正则化(Ridge)的逻辑回归
模型_l2 = LogisticRegression(penalty='l2', C=1.0)  # 更小的C = 更多正则化

# 使用L1(Lasso)进行自动特征选择
模型_l1 = LogisticRegression(penalty='l1', solver='liblinear', C=0.1)

L1(Lasso): 可以将系数置零 → 特征选择。
L2(Ridge): 收缩系数但不置零 → 更稳定。


➤ 简化模型

  • 减少树的深度。
  • 减少网络中的层数/神经元。
  • 使用更少的特征(特征选择)。

➤ 更多数据

简单但强大。更多有代表性的数据帮助模型学习更一般的模式。


➤ 交叉验证

在看到测试数据之前评估模型泛化能力的最佳工具。


5.5 交叉验证:抵御乐观情绪的盾牌

简单的验证(训练/测试分割)如果分割幸运或不幸可能会误导。

交叉验证(CV) 将数据分割为K折,训练K次,每次使用不同的折作为验证。

结果:更稳健、可靠的表现估计。

from sklearn.model_selection import cross_val_score

模型 = LogisticRegression()

# 5折交叉验证
scores = cross_val_score(模型, X_train, y_train, cv=5, scoring='roc_auc')

print(f"每折AUC-ROC: {scores}")
print(f"平均AUC-ROC: {scores.mean():.4f} (+/- {scores.std() * 2:.4f})")

优点:

  • 使用所有数据进行训练和验证。
  • 减少性能估计中的方差。
  • 适合比较模型或调整超参数。

5.6 交叉验证的类型

➤ K折交叉验证(标准)

分割为K个相等部分。每折使用一次作为验证。

from sklearn.model_selection import KFold

kf = KFold(n_splits=5, shuffle=True, random_state=42)

➤ 分层K折交叉验证

在每折中保持类别比例。对不平衡数据集特别重要。

from sklearn.model_selection import StratifiedKFold

skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(模型, X_train, y_train, cv=skf, scoring='roc_auc')

➤ 留一交叉验证(LOO)

每折是单个观测。计算成本非常高;仅适用于非常小的数据集。


📈 可视化:比较训练/测试 vs 交叉验证

import numpy as np

# 使用简单的训练/测试进行训练和评估
模型.fit(X_train, y_train)
test_score = roc_auc_score(y_test, 模型.predict_proba(X_test)[:,1])

# 在训练集上使用交叉验证进行评估
cv_scores = cross_val_score(模型, X_train, y_train, cv=5, scoring='roc_auc')

plt.figure(figsize=(8,5))
plt.axhline(y=test_score, color='red', linestyle='--', label=f'测试分数: {test_score:.4f}')
plt.plot(range(1,6), cv_scores, 'bo-', label='每折交叉验证分数')
plt.axhline(y=cv_scores.mean(), color='blue', linestyle='-', label=f'交叉验证平均: {cv_scores.mean():.4f}')
plt.title("比较:交叉验证 vs 最终测试")
plt.xlabel("折")
plt.ylabel("AUC-ROC")
plt.legend()
plt.grid()
plt.show()

📝 练习5.1:过拟合诊断和预防

数据集: 欺诈_特征.csv(预处理后的选定特征)

任务:

  1. 训练最大深度为20的随机森林模型。
  2. 计算训练集和测试集的准确性和AUC-ROC。是否存在过拟合?
  3. 在训练集上应用分层5折交叉验证。将平均AUC-ROC与测试分数比较。
  4. 现在训练最大深度为5的随机森林。重复步骤2和3。
  5. 比较两个模型:哪个泛化更好?为什么?
  6. (可选)尝试带正则化的逻辑回归(C=0.01)并比较。

💡 附加说明:

  • 对于不平衡分类问题总是使用StratifiedKFold。
  • 交叉验证绝不能包含最终测试集。 测试集是神圣的——只在最后使用一次。
  • 过拟合本身并不坏。 有时无法避免。重要的是检测和控制它。
  • 在Kaggle竞赛中,获胜者使用分层交叉验证和多个种子来确保稳定性。

Course Info

Course: AI-course1

Language: ZH

Lesson: Module5