交叉验证中的 Pipeline 与泄漏
填补、标准化、PCA、特征选择、词表和重采样都必须在当前训练折内部学习。
学习前需要掌握
背景与问题
缩放、填补和降维虽然常被叫作预处理,但它们也会从数据学习统计量。若先在全部开发数据上完成这些步骤,再交叉验证模型,验证折已参与塑造训练输入。
Pipeline 把有状态变换与估计器组成同一个可克隆训练流程,让交叉验证能在每个训练折重新拟合全部步骤。
概念定义
先对完整 X fit 变换再 cross_val_score 会让验证折影响变换状态。
直观理解
每一折都要从原始训练材料独立准备笔记,不能共享全班统计。
核心原理
fit 阶段按顺序拟合变换并传递结果,最后训练估计器;predict 阶段只使用已经学到的变换。验证折不能重新 fit 填补器或缩放器,否则训练与推理语义不一致。
交叉验证克隆整个 Pipeline,各折保存独立中位数、均值、主成分与模型参数。监督特征选择、目标编码等依赖 y 的步骤更要放在正确边界内;具体编码器还可能需要内部交叉拟合以避免训练行自泄漏。
Pipeline 管理步骤边界,不代替数据划分与特征可用性设计。跨折重复实体、未来字段和错误标签即使都包进 Pipeline 也依然存在。提前缓存的全数据特征也不能因外面再套一层 Pipeline 就被修复。
过采样等改变样本数量的操作只能作用于训练折,需要使用支持采样步骤的合适流程,而不是让验证折也被采样。最终部署应保存整个已拟合预处理与模型,避免训练预测使用不同统计量。
数学表达
核心表达
把所有有状态步骤与估计器封装;当前验证折只 transform/predict。重采样只作用于训练折。
变量含义
变量定义与本页概念定义一致,代码中的同名变量保持相同语义。
计算步骤
- 1列出所有 fit 步骤
- 2封装 Pipeline
- 3传原始 X 给 CV
- 4重采样用折内工具
代码对应位置
示例 1检查整条 Pipeline 的折内拟合规模:完整拟合 3 折 Pipeline,并读取每折变换器的状态验证边界。
案例:检查每折缩放器看过多少行
Iris 的 150 行做 3 折,每折训练 100 行。下方将填补、标准化、PCA 与逻辑回归串起来,返回每折模型后检查 StandardScaler 的 n_samples_seen_ 都是 100。
PCA 也只在该折训练输入中学习,再把验证行投影到对应空间。这个计数检查能揭示把全数据缩放器偷偷放到流程外的问题,却不能证明特征在预测时点可用,因此还需要单独的数据审计。
计算与实现步骤
- 列出所有 fit 步骤
- 封装 Pipeline
- 传原始 X 给 CV
- 重采样用折内工具
- 检查 cv 结果
- 最终测试
代码实现
示例 1
检查整条 Pipeline 的折内拟合规模
example_01.py用途:完整拟合 3 折 Pipeline,并读取每折变换器的状态验证边界。
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_validate
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
X, y = load_iris(return_X_y=True)
pipeline = Pipeline([
("impute", SimpleImputer(strategy="median")),
("scale", StandardScaler()),
("pca", PCA(n_components=2, svd_solver="full")),
("model", LogisticRegression(max_iter=1000)),
])
cv = StratifiedKFold(3, shuffle=True, random_state=42)
result = cross_validate(pipeline, X, y, cv=cv, return_estimator=True)
seen = [int(model.named_steps["scale"].n_samples_seen_) for model in result["estimator"]]
assert seen == [100, 100, 100]
print("scaler_training_rows:", seen)
print("pca_components:", [model.named_steps["pca"].n_components_ for model in result["estimator"]])代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
完整拟合 3 折 Pipeline,并读取每折变换器的状态验证边界。
X- 完整特征矩阵,每行一个样本、每列一个特征。
y- 目标标签或连续目标值。
model- 组合预处理和估计器的模型对象。
Step 1 · 1–7 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
from sklearn.datasets import load_iris
from sklearn.decomposition import PCA
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_validate
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScalerStep 2 · 9–21 行
输出中间参数、形状或最终指标,用于核对代码是否符合预期。
X, y = load_iris(return_X_y=True)
pipeline = Pipeline([
("impute", SimpleImputer(strategy="median")),
("scale", StandardScaler()),
("pca", PCA(n_components=2, svd_solver="full")),
("model", LogisticRegression(max_iter=1000)),
])
cv = StratifiedKFold(3, shuffle=True, random_state=42)
result = cross_validate(pipeline, X, y, cv=cv, return_estimator=True)
seen = [int(model.named_steps["scale"].n_samples_seen_) for model in result["estimator"]]
assert seen == [100, 100, 100]
print("scaler_training_rows:", seen)
print("pca_components:", [model.named_steps["pca"].n_components_ for model in result["estimator"]])代码与数学原理
减去训练均值并除以训练标准差,对应代码中的 mean/std 或 StandardScaler。
预期输出或运行结果
本地 scikit-learn 1.8.0 的核对输出: scaler_training_rows: [100, 100, 100] pca_components: [2, 2, 2]
常见错误 · 4 条
- 不要在测试集上调用 fit 或 fit_transform;测试集只能复用训练参数。
- 先调用全数据 fit_transform,再将已变换矩阵送进 Pipeline 或 CV。
- 在验证折重新拟合变换,或让重采样改变验证样本分布。
- 认为 Pipeline 能自动解决实体重复、未来字段和所有目标编码泄漏。
实际应用
- 任何含预处理的 CV
常见错误
- 先调用全数据 fit_transform,再将已变换矩阵送进 Pipeline 或 CV。
- 在验证折重新拟合变换,或让重采样改变验证样本分布。
- 认为 Pipeline 能自动解决实体重复、未来字段和所有目标编码泄漏。
推荐学习资料
参考库不会生成虚假资源或无效外部链接。