数据泄漏
训练或选择流程获得真实预测时不可用的信息,使离线评价虚高。
学习前需要掌握
背景与问题
如果模型训练利用了真实预测时不可能获得的信息,离线成绩就会高估可用能力。泄漏可能来自预处理,也可能来自目标的替代字段、重复实体或时间顺序,并不一定表现为直接把标签放进输入。
检查泄漏要沿数据产生过程追踪:某个值何时产生,哪个步骤在哪些样本上 fit,评价对象是否已经以其他记录出现过。
概念定义
泄漏可来自预处理、特征选择、重复实体、未来特征、测试复用或目标编码进入输入。
直观理解
模型在考试前以任何方式看到了试卷统计、答案线索或重复题。
核心原理
预处理泄漏发生在验证信息参与估计时,例如全数据求中位数、缩放、PCA 或特征选择,再进行交叉验证。正确边界是每个训练折独立 fit,验证折仅使用对应变换。
目标泄漏来自标签或后果的代理信息。例如预测是否违约时使用“催收次数”,若该字段在违约后才产生,即使训练验证严格隔离也仍不符合预测时点。
实体泄漏与时间泄漏需要划分规则处理。Pipeline 只能管理其中步骤的拟合边界,不会自动阻止同一患者跨折,也不会把未来记录从训练集中移除。
泄漏诊断可从异常高分、训练部署差距和特征含义入手,但不能只凭分数判定。应核查数据血缘、时间戳和拆分索引,保留可复现的无泄漏基线再比较改动。
数学表达
核心表达
对每个有状态步骤追问“它 fit 了哪些样本”;对每个特征追问“预测时点是否可用”。
变量含义
变量定义与本页概念定义一致,代码中的同名变量保持相同语义。
计算步骤
- 1画出数据流
- 2标出所有 fit
- 3检查时间和实体
- 4将预处理放入 Pipeline
代码对应位置
示例 1逐折核对填补器只学习训练行:以显式缺失值与保存的折索引验证填补器边界,而非仅展示函数名称。
案例:中位数填补为什么也需要折内拟合
若一列存在缺失值,在交叉验证前用全部数据中位数填补,就让每个验证折的数值影响了训练输入。正确流程把填补器与分类器放进同一 Pipeline,交叉验证会为每一折创建并拟合独立副本。
下方不仅计算分数,还检查每折填补器的 statistics_ 是否等于对应训练行的中位数。这个检查只验证拟合边界;若特征本身是未来才知道的结果字段,Pipeline 仍不能修复它。
计算与实现步骤
- 画出数据流
- 标出所有 fit
- 检查时间和实体
- 将预处理放入 Pipeline
- 封存测试集
- 进行输入可用性审查
代码实现
示例 1
逐折核对填补器只学习训练行
example_01.py用途:以显式缺失值与保存的折索引验证填补器边界,而非仅展示函数名称。
import numpy as np
from sklearn.datasets import load_iris
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_validate
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
X, y = load_iris(return_X_y=True)
X = X.copy()
X[::10, 0] = np.nan
splits = list(StratifiedKFold(3, shuffle=True, random_state=42).split(X, y))
pipeline = make_pipeline(SimpleImputer(strategy="median"), StandardScaler(), LogisticRegression(max_iter=1000))
result = cross_validate(pipeline, X, y, cv=splits, scoring="accuracy", return_estimator=True)
for (train, valid), estimator in zip(splits, result["estimator"]):
fitted = estimator.named_steps["simpleimputer"].statistics_
assert np.allclose(fitted, np.nanmedian(X[train], axis=0))
print("folds:", len(result["estimator"]))
print("fold_medians_match_training:", True)
print("finite_scores:", bool(np.isfinite(result["test_score"]).all()))代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
以显式缺失值与保存的折索引验证填补器边界,而非仅展示函数名称。
X- 完整特征矩阵,每行一个样本、每列一个特征。
y- 目标标签或连续目标值。
Step 1 · 1–7 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
import numpy as np
from sklearn.datasets import load_iris
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_validate
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScalerStep 2 · 9–20 行
输出中间参数、形状或最终指标,用于核对代码是否符合预期。
X, y = load_iris(return_X_y=True)
X = X.copy()
X[::10, 0] = np.nan
splits = list(StratifiedKFold(3, shuffle=True, random_state=42).split(X, y))
pipeline = make_pipeline(SimpleImputer(strategy="median"), StandardScaler(), LogisticRegression(max_iter=1000))
result = cross_validate(pipeline, X, y, cv=splits, scoring="accuracy", return_estimator=True)
for (train, valid), estimator in zip(splits, result["estimator"]):
fitted = estimator.named_steps["simpleimputer"].statistics_
assert np.allclose(fitted, np.nanmedian(X[train], axis=0))
print("folds:", len(result["estimator"]))
print("fold_medians_match_training:", True)
print("finite_scores:", bool(np.isfinite(result["test_score"]).all()))代码与数学原理
减去训练均值并除以训练标准差,对应代码中的 mean/std 或 StandardScaler。
预期输出或运行结果
本地 scikit-learn 1.8.0 的核对输出: folds: 3 fold_medians_match_training: True finite_scores: True
常见错误 · 4 条
- 不要在测试集上调用 fit 或 fit_transform;测试集只能复用训练参数。
- 认为没有把标签列放进 X 就绝不泄漏,忽略代理字段与实体重复。
- 在全数据上填补、缩放或选特征,再把变换后的矩阵交给 CV。
- 以为使用 Pipeline 就自动解决时间、群组与业务预测时点的全部问题。
实际应用
- 实验审计
- 特征审查
常见错误
- 认为没有把标签列放进 X 就绝不泄漏,忽略代理字段与实体重复。
- 在全数据上填补、缩放或选特征,再把变换后的矩阵交给 CV。
- 以为使用 Pipeline 就自动解决时间、群组与业务预测时点的全部问题。
推荐学习资料
参考库不会生成虚假资源或无效外部链接。