Pipeline 与 ColumnTransformer
用 Pipeline 固化步骤顺序,用 ColumnTransformer 为不同列类型建立独立处理分支。
学习前需要掌握
背景与问题
真实表格常混合数值与类别列,既需要不同的预处理,也需要让这些处理在每个训练折中正确拟合。只把几行变换代码按顺序写好,不一定能保证验证边界。
Pipeline 与 ColumnTransformer 分别解决顺序组合和按列分流,使输入契约、已学状态和模型选择更容易保持一致。
概念定义
Pipeline 将多步变换与最终估计器串联;ColumnTransformer 将不同列交给不同变换器,再按约定合并输出。前者主要组织步骤依赖,后者主要组织列类型与分支。
它们不仅是代码简写,也让 fit、transform、参数搜索与持久化可以围绕完整流程工作。但错误的特征定义或时间泄漏,不会因为套了管线就自动消失。
直观理解
把原始表分到数值与类别两条加工线,再把统一表示送入分类器。每条加工线保留自己的训练状态,未来表格走同样的路径,而不是临时重新发明编码规则。
两列数值、一列类别会变成几列
假设训练表有 age、income 两列数值和 city 一列类别,城市只见过 A、B。两列缩放值加两列 One-Hot,总共形成四个模型输入,而不是仍然三列。
未来城市 C 使用忽略未知值规则时,城市的两列均为 0,但数值分支仍按训练统计量处理。必须检查这是否符合业务,而不能因为输出维度没有变就断言新城市已经被充分验证。
核心原理
数值分支可先填补再缩放,类别分支可先处理缺失再编码。步骤顺序有含义,例如先缩放含缺失数据与先填补后缩放可能不是同一过程。
在交叉验证和参数搜索中传入完整管线,才能让每折独立拟合需要学习的步骤。内部参数通常用步骤名与双下划线定位,搜索空间也应对应这些实际对象。
列名、顺序、未知类别与输出稀疏性属于接口。最好保留原始列名与 get_feature_names_out 等核对信息,防止拼接后的特征与解释脱节。
保存完整管线有助于复用预处理与模型,但还应记录依赖版本和输入契约。对象保存成功,不代表从不可信文件加载是安全的,也不代表不同版本必然兼容。
计算与实现步骤
- 列出数值和类别列。
- 为每类列定义子 Pipeline。
- 用 ColumnTransformer 组合。
- 与模型组成总 Pipeline。
- 在训练数据 fit。
- 用 CV 搜索内部参数。
- 保存完整对象并验证加载预测。
代码实现
示例 1
混合列从原始表到预测的完整小例子
example_01.py用途:用已定义的列名和输入核对四维编码与未知类别。
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
train = pd.DataFrame({"age": [20., 30., 40., 50.],
"income": [1., 2., 3., 4.], "city": ["A", "B", "A", "B"]})
future = pd.DataFrame({"age": [60.], "income": [5.], "city": ["C"]})
numeric = Pipeline([("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler())])
categorical = Pipeline([("imputer", SimpleImputer(strategy="most_frequent")),
("one_hot", OneHotEncoder(handle_unknown="ignore", sparse_output=False))])
prep = ColumnTransformer([("numeric", numeric, ["age", "income"]),
("categorical", categorical, ["city"])])
model = Pipeline([("preprocessor", prep), ("classifier", LogisticRegression())])
model.fit(train, [0, 0, 1, 1])
encoded = model.named_steps["preprocessor"].transform(future)
print("transformed_shape:", encoded.shape)
print("unknown_city:", encoded[0, -2:].tolist())
print("prediction:", model.predict(future).tolist())代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
用已定义的列名和输入核对四维编码与未知类别。
model- 组合预处理和估计器的模型对象。
scaler- 保存训练集缩放参数的转换器。
Step 1 · 1–21 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
import pandas as pd
from sklearn.compose import ColumnTransformer
from sklearn.impute import SimpleImputer
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import OneHotEncoder, StandardScaler
train = pd.DataFrame({"age": [20., 30., 40., 50.],
"income": [1., 2., 3., 4.], "city": ["A", "B", "A", "B"]})
future = pd.DataFrame({"age": [60.], "income": [5.], "city": ["C"]})
numeric = Pipeline([("imputer", SimpleImputer(strategy="median")),
("scaler", StandardScaler())])
categorical = Pipeline([("imputer", SimpleImputer(strategy="most_frequent")),
("one_hot", OneHotEncoder(handle_unknown="ignore", sparse_output=False))])
prep = ColumnTransformer([("numeric", numeric, ["age", "income"]),
("categorical", categorical, ["city"])])
model = Pipeline([("preprocessor", prep), ("classifier", LogisticRegression())])
model.fit(train, [0, 0, 1, 1])
encoded = model.named_steps["preprocessor"].transform(future)
print("transformed_shape:", encoded.shape)
print("unknown_city:", encoded[0, -2:].tolist())
print("prediction:", model.predict(future).tolist())代码与数学原理
减去训练均值并除以训练标准差,对应代码中的 mean/std 或 StandardScaler。
预期输出或运行结果
transformed_shape: (1, 4) unknown_city: [0.0, 0.0] prediction: [1]
常见错误 · 5 条
- 不要在测试集上调用 fit 或 fit_transform;测试集只能复用训练参数。
- 手工对训练和测试分别 get_dummies
- 忘记未知类别
- 只保存分类器不保存预处理
- 确认样本轴、特征轴以及 X 与 y 的第一维完全对应。
实际应用
- 混合类型表格
- 交叉验证与调参
- 模型保存交付
常见错误
- 手工对训练和测试分别 get_dummies
- 忘记未知类别
- 只保存分类器不保存预处理
推荐学习资料
参考库不会生成虚假资源或无效外部链接。