如你所见,为机器学习准备数据通常包含多个连续步骤:处理缺失值、编码分类特征、缩放数值特征,甚至可能生成新特征。逐一应用这些步骤可能会变得繁琐且易出错,特别是当你需要确保将完全相同的转换序列一致地应用于训练数据和任何新数据(例如测试集或生产中遇到的数据)时。想象一下,在将数据集分割为训练集和测试集之前,你对整个数据集拟合一个 StandardScaler。缩放器会从所有数据(包括测试集)中学习均值和标准差。当你训练模型时,它会通过缩放参数隐式地获得关于测试集的信息。这种现象,称为数据泄露,可能在开发过程中导致过于乐观的性能评估,因为你的模型在预处理阶段无意中“看到了”测试数据。将仅从训练数据中学习到的转换应用于测试数据对于可靠的模型评估非常重要。这就是 Scikit-learn 的 Pipeline 对象变得非常有用的地方。Pipeline 允许你将多个处理步骤(转换器)以及一个可选的最终估计器(如分类器或回归器)串联成一个单一对象。此对象的行为类似于标准的 Scikit-learn 估计器,具有 fit、transform 和 predict 方法(取决于最后一步)。为何使用管道?使用 Pipeline 具有几个重要优势:便捷性和封装性: 它将多个处理步骤捆绑成一个单元,简化了你的代码。你无需多次调用 fit_transform 或 fit 和 transform,只需与单一的管道对象交互。联合参数选择: 在进行超参数调整时(例如,使用 GridSearchCV 或 RandomizedSearchCV),你可以同时调整管道中所有步骤的参数,包括预处理步骤和最终估计器。防止数据泄露: 这是一个主要优点。当你在一个管道上调用 fit 时,它会正确地仅在提供给 fit 方法的训练数据上拟合转换器。中间步骤调用 fit_transform,将转换后的数据传递给下一步。当你对新数据(如测试集)调用 transform 或 predict 时,管道会确保只调用已拟合转换器的 transform 方法,一致地应用所学转换,而无需在新数据上重新拟合。管道的结构构建 Pipeline 需要提供一个步骤列表。每个步骤都是一个元组,包含一个独特的名称(你选择的字符串)和一个转换器或估计器的实例。# 示例结构 from sklearn.pipeline import Pipeline from sklearn.preprocessing import StandardScaler from sklearn.impute import SimpleImputer from sklearn.linear_model import LogisticRegression # 定义步骤:(名称,转换器/估计器实例) steps = [ ('imputer', SimpleImputer(strategy='mean')), # 步骤 1: 填充缺失值 ('scaler', StandardScaler()), # 步骤 2: 缩放特征 ('classifier', LogisticRegression()) # 步骤 3: 最终估计器 ] # 创建管道 pipe = Pipeline(steps=steps) # 现在 'pipe' 可以像普通估计器一样使用: # pipe.fit(X_train, y_train) # predictions = pipe.predict(X_test) # score = pipe.score(X_test, y_test)管道中的所有中间步骤必须是转换器,这意味着它们必须同时实现 fit 和 transform 方法。最后一步可以是任何估计器:转换器、分类器或回归器。当调用 pipe.fit(X_train, y_train) 时:imputer 在 X_train 上拟合,然后 X_train 被 imputer 转换。转换后的数据被传递给 scaler,scaler 进行拟合然后转换数据。缩放后的数据连同 y_train 一起传递给 classifier,然后 classifier 进行拟合。当调用 pipe.predict(X_test) 时:X_test 由已拟合的 imputer 转换。结果数据由已拟合的 scaler 转换。缩放后的数据传递给已拟合的 classifier 的 predict 方法。这种顺序应用确保处理逻辑被正确一致地应用,防止在评估阶段发生数据泄露。这是一个简单的管道结构可视化:digraph G { rankdir=LR; node [shape=box, style=filled, fillcolor="#a5d8ff", fontname="Helvetica"]; edge [fontname="Helvetica"]; splines=ortho; subgraph cluster_pipeline { label = "管道"; bgcolor = "#e9ecef"; RawData [label="原始数据\n(例如, X_train)", shape=ellipse, fillcolor="#ced4da"]; Step1 [label="转换器 1\n(例如, 填充器)\nfit_transform()", fillcolor="#74c0fc"]; Step2 [label="转换器 2\n(例如, 缩放器)\nfit_transform()", fillcolor="#74c0fc"]; Step3 [label="估计器\n(例如, 分类器)\nfit()", fillcolor="#4dabf7"]; ProcessedData [label="转换后的数据\n(用于步骤 2)", shape=ellipse, style=dashed, fillcolor="#dee2e6"]; FinalProcessedData [label="转换后的数据\n(用于估计器)", shape=ellipse, style=dashed, fillcolor="#dee2e6"]; RawData -> Step1; Step1 -> ProcessedData [style=dashed, arrowhead=none]; ProcessedData -> Step2; Step2 -> FinalProcessedData [style=dashed, arrowhead=none]; FinalProcessedData -> Step3; } subgraph cluster_predict { label = "预测阶段 (使用已拟合的管道)"; bgcolor = "#fff9db"; NewData [label="新数据\n(例如, X_test)", shape=ellipse, fillcolor="#ffec99"]; PStep1 [label="转换器 1\ntransform()", fillcolor="#ffe066"]; PStep2 [label="转换器 2\ntransform()", fillcolor="#ffe066"]; PStep3 [label="估计器\npredict()", fillcolor="#ffd43b"]; PredTransformed1 [label="转换后的数据", shape=ellipse, style=dashed, fillcolor="#fff3bf"]; PredTransformed2 [label="转换后的数据", shape=ellipse, style=dashed, fillcolor="#fff3bf"]; Predictions [label="预测结果", shape=ellipse, fillcolor="#fcc419"]; NewData -> PStep1; PStep1 -> PredTransformed1 [style=dashed, arrowhead=none]; PredTransformed1 -> PStep2; PStep2 -> PredTransformed2 [style=dashed, arrowhead=none]; PredTransformed2 -> PStep3; PStep3 -> Predictions; } }显示 Scikit-learn 管道在拟合阶段(上方)和预测阶段(下方)的数据流图。请注意,在拟合阶段,转换器使用 fit_transform,而在预测阶段仅使用 transform。当需要对不同列应用不同的转换时(例如,缩放数值列和对分类列进行独热编码),管道通常会变得更复杂。Scikit-learn 的 ColumnTransformer 正是为了处理这种情况而设计的,并且在 Pipeline 内运行良好,使你能够构建高效的预处理工作流。我们将在后续章节和练习中查看构建这些管道的实际例子。