【精选优质专栏推荐】


每个专栏均配有案例与图文讲解,循序渐进,适合新手与进阶学习者,欢迎订阅。

在这里插入图片描述

介绍

Pipeline 可能是 scikit-learn 中最被低估但同时最强大的功能之一,它能帮助构建高效、模块化的机器学习工作流。从数据准备、特征工程到建模、调优和验证,它简化了整个过程,同时降低数据泄漏风险,使代码更具可复现性,也更简洁易维护。

在本文中,我们将通过简洁但偏中高级的用例,介绍并演示五个 pipeline 技巧,帮助你提升正在进行的机器学习项目。

初始设置

下面的代码及其组成部分会在后续多个示例中使用,因此建议先完成这些准备步骤。

需要注意的是,接下来我们主要使用常见的 Titanic 生存预测数据集:

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.impute import SimpleImputer
from sklearn.datasets import fetch_openml

# 加载数据集 (Titanic 生存预测)
titanic = fetch_openml("titanic", version=1, as_frame=True)
X = titanic.data[["pclass", "sex", "age", "fare"]]
y = titanic.target == "1"

# 将数据集划分为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)

# 按特征类型选择特征
num_features = ["age", "fare"]
cat_features = ["pclass", "sex"]

现在正式进入本文的核心实践部分。

1. 使用 ColumnTransformer 处理混合数据类型

在第一个示例中,我们将实例化一个 ColumnTransformer 对象来定义一个健壮的数据预处理 pipeline。这个类允许针对不同的特征子集灵活应用不同的转换操作,以统一的方式处理混合数据类型和缺失值,避免繁琐的操作和重复代码。

# 实例化 ColumnTransformer 以进行健壮的数据预处理
preprocessor = ColumnTransformer([
    ("num", Pipeline([
        ("imputer", SimpleImputer(strategy="median")),
        ("scaler", StandardScaler())
    ]), num_features),
    ("cat", Pipeline([
        ("imputer", SimpleImputer(strategy="most_frequent")),
        ("onehot", OneHotEncoder(handle_unknown="ignore"))
    ]), cat_features)
])

pipe = Pipeline([
    ("preprocessor", preprocessor),
    ("model", LogisticRegression(max_iter=1000))
])

pipe.fit(X_train, y_train)
print("Accuracy:", pipe.score(X_test, y_test))

这种方法能同时处理数值特征、类别特征和缺失值,并在训练逻辑回归分类器前,把它们整合进一个整体 pipeline。


2. 使用自定义 Transformer 进行特征工程

在 scikit-learn 中,自定义 transformer 允许我们定义自己的特征级转换步骤(无论是特征工程还是预处理),并直接嵌入 pipeline。常见做法是继承 TransformerMixin,实现 fit 和 transform 方法,这样就可以无缝使用 fit_transform()。

下面的代码通过继承定义了一个自定义 transformer 类。它会将数值特征 age 转换为二进制特征(0 或 1),表示乘客是否为成年人。然后,我们将该逻辑集成到类似前例的 ColumnTransformer 中。

from sklearn.base import BaseEstimator, TransformerMixin

# 自定义 transformer:基于 age 创建二进制特征 "is_adult"
class IsAdult(BaseEstimator, TransformerMixin):
    def fit(self, X, y=None):
        return self
    def transform(self, X):
        return (X["age"].fillna(0) >= 18).astype(int).to_frame("is_adult")

# 扩展 ColumnTransformer,加入自定义 transformer
extended = ColumnTransformer([
    ("num", Pipeline([
        ("imputer", SimpleImputer(strategy="median")),
        ("scaler", StandardScaler())
    ]), num_features),
    ("cat", Pipeline([
        ("imputer", SimpleImputer(strategy="most_frequent")),
        ("onehot", OneHotEncoder(handle_unknown="ignore"))
    ]), cat_features),
    ("is_adult", IsAdult(), ["age"])
])

pipe = Pipeline([
    ("preprocessor", extended),
    ("model", LogisticRegression(max_iter=1000))
])

pipe.fit(X_train, y_train)
print("Accuracy with custom feature:", pipe.score(X_test, y_test))

3. 在整个 Pipeline 上进行超参数调优

超参数调优(在多个选项中找到最佳配置)并不仅限于模型本身的参数,它也可以应用到前面的预处理步骤。下面的示例展示了如何做到这一点:

from sklearn.svm import SVC

pipe = Pipeline([
    ("preprocessor", preprocessor),
    ("model", SVC())
])

param_grid = {
    "preprocessor__num__imputer__strategy": ["mean", "median"],
    "model__C": [0.1, 1, 10],
    "model__kernel": ["linear", "rbf"]
}

search = GridSearchCV(pipe, param_grid, cv=3)
search.fit(X_train, y_train)
print("Best params:", search.best_params_)
print("Best score:", search.best_score_)

请注意,preprocessor 就是示例 1 中定义的 ColumnTransformer。关键在于搜索网格中加入了一个与预处理步骤相关的超参数,即缺失值填充策略。换句话说,训练出的多个模型版本不仅基于模型自身的超参数,还涉及预处理中的具体设置。


4. 在 Pipeline 中集成特征选择

对于特征数量较多的数据集,一个有力的技巧是将特征选择步骤动态地放进 pipeline,使最终模型更简洁。

下面的示例在训练模型之前,自动选择最有信息量的预处理特征。这里通过 SelectKBest 类调用评分函数,挑选出得分最高的特征:

from sklearn.feature_selection import SelectKBest, f_classif

pipe = Pipeline([
    ("preprocessor", preprocessor),
    ("feature_selection", SelectKBest(score_func=f_classif, k=5)),
    ("model", LogisticRegression(max_iter=1000))
])

pipe.fit(X_train, y_train)
print("Test accuracy:", pipe.score(X_test, y_test))

SelectKBest 需要一个评分函数或判定标准来选择前 k 个特征。例如,也可以使用 score_func=chi2,它基于卡方检验选择特征,适合类别特征占主导的数据集。


5. 堆叠 Pipeline

最后一个示例展示如何堆叠多个 pipeline 来构建集成学习方案。在不同模型(甚至包含不同预处理步骤)的场景下,pipeline 能帮助我们避免数据管理不一致,同时实现高度可定制的集成。

在下面的示例中,我们定义了两个“顶层” pipeline:一个对数据进行预处理并训练逻辑回归分类器,另一个应用相同的预处理,但训练决策树分类器:

from sklearn.ensemble import StackingClassifier
from sklearn.tree import DecisionTreeClassifier

log_reg_pipe = Pipeline([
    ("preprocessor", preprocessor),
    ("logreg", LogisticRegression(max_iter=1000))
])

tree_pipe = Pipeline([
    ("preprocessor", preprocessor),
    ("tree", DecisionTreeClassifier(max_depth=5))
])

stack = StackingClassifier(
    estimators=[("lr", log_reg_pipe), ("dt", tree_pipe)],
    final_estimator=LogisticRegression()
)

stack.fit(X_train, y_train)
print("Stacked accuracy:", stack.score(X_test, y_test))

这两个 pipeline 随后通过 StackingClassifier 堆叠。该类会使用一个最终估计器来学习如何最佳组合基础模型的预测,从而得到更强大、更具泛化能力的模型。


总结

本文展示了五个关于 scikit-learn pipeline 的实用示例,用来加速并增强机器学习工作流的效果、可定制性和性能。从针对混合数据类型的自定义预处理 pipeline,到将超参数调优扩展至预处理步骤,我们介绍了多个技巧和方法,帮助你把机器学习建模项目提升到新的水平。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐