7.1. 管道和复合估计量#

To build a composite estimator, transformers are usually combined with other transformers or with predictors (such as classifiers or regressors). The most common tool used for composing estimators is a Pipeline. Pipelines require all steps except the last to be a transformer. The last step can be anything, a transformer, a predictor, or a clustering estimator which might have or not have a .predict(...) method. A pipeline exposes all methods provided by the last estimator: if the last step provides a transform method, then the pipeline would have a transform method and behave like a transformer. If the last step provides a predict method, then the pipeline would expose that method, and given a data X, use all steps except the last to transform the data, and then give that transformed data to the predict method of the last step of the pipeline. The class Pipeline is often used in combination with ColumnTransformer or FeatureUnion which concatenate the output of transformers into a composite feature space. TransformedTargetRegressor deals with transforming the target (i.e. log-transform y).

7.1.1. 管道:连锁估计器#

Pipeline 可用于将多个估计量链成一个。这很有用,因为处理数据时通常有固定的步骤序列,例如特征选择、规范化和分类。 Pipeline 这里有多种用途:

方便和封装

你只需要打电话 fitpredict 一次对您的数据进行调整,以适应整个估计值序列。

关节参数选择

你可以 grid search 同时检查管道中所有估计器的参数。

安全

通过确保使用相同的样本来训练变换器和预测器,管道有助于避免在交叉验证中将测试数据中的统计数据泄露到训练模型中。

管道中的所有估计器(除最后一个估计器外)都必须是变压器(即必须有 transform 方法)。最后一个估计器可以是任何类型(Transformer、分类器等)。

备注

调用 fit 与调用 fit 依次在每个估计器上, transform 输入并将其传递到下一步。流水线具有流水线中最后一个估计器具有的所有方法,即,如果最后一个估计器是分类器,则 Pipeline 可以用作分类器。如果最后一个估计器是一个Transformer,那么管道也是。

7.1.1.1. 使用#

7.1.1.1.1. 修建一条管道#

Pipeline 是使用列表构建的 (key, value) 对, key 是一个字符串,包含您要赋予此步骤的名称,并且 value 是估计器对象::

>>> from sklearn.pipeline import Pipeline
>>> from sklearn.svm import SVC
>>> from sklearn.decomposition import PCA
>>> estimators = [('reduce_dim', PCA()), ('clf', SVC())]
>>> pipe = Pipeline(estimators)
>>> pipe
Pipeline(steps=[('reduce_dim', PCA()), ('clf', SVC())])
简写版本使用 make_pipeline#

效用函数 make_pipeline 是构建管道的缩写;它需要可变数量的估计器并返回管道,自动填写名称::

>>> from sklearn.pipeline import make_pipeline
>>> make_pipeline(PCA(), SVC())
Pipeline(steps=[('pca', PCA()), ('svc', SVC())])

7.1.1.1.2. 进入管道步骤#

管道的估计值作为列表存储在 steps 属性可以使用通常用于Python序列的切片符号(例如列表或字符串)来提取子管道(尽管只允许1的步骤)。这对于仅执行某些变换(或其逆变换)来说很方便:

>>> pipe[:1]
Pipeline(steps=[('reduce_dim', PCA())])
>>> pipe[-1:]
Pipeline(steps=[('clf', SVC())])
按姓名或职位指定步骤#

特定步骤还可以通过索引或通过索引(使用 [idx] )管道::

>>> pipe.steps[0]
('reduce_dim', PCA())
>>> pipe[0]
PCA()
>>> pipe['reduce_dim']
PCA()

Pipelinenamed_steps 属性允许在交互式环境中按名称访问步骤并完成制表符::

>>> pipe.named_steps.reduce_dim is pipe['reduce_dim']
True

7.1.1.1.3. 跟踪管道中的要素名称#

为了实现模型检查, Pipeline 具有 get_feature_names_out() 方法,就像所有变形金刚一样。您可以使用管道切片来获取进入每个步骤的要素名称::

>>> from sklearn.datasets import load_iris
>>> from sklearn.linear_model import LogisticRegression
>>> from sklearn.feature_selection import SelectKBest
>>> iris = load_iris()
>>> pipe = Pipeline(steps=[
...    ('select', SelectKBest(k=2)),
...    ('clf', LogisticRegression())])
>>> pipe.fit(iris.data, iris.target)
Pipeline(steps=[('select', SelectKBest(...)), ('clf', LogisticRegression(...))])
>>> pipe[:-1].get_feature_names_out()
array(['x2', 'x3'], ...)
自定义功能名称#

您还可以使用为输入数据提供自定义要素名称 get_feature_names_out

>>> pipe[:-1].get_feature_names_out(iris.feature_names)
array(['petal length (cm)', 'petal width (cm)'], ...)

7.1.1.1.4. 访问嵌套参数#

调整管道内估计器的参数是常见的。因此,该参数被嵌套,因为它属于特定的子步骤。管道中估计器的参数可以使用 <estimator>__<parameter> 语法::

>>> pipe = Pipeline(steps=[("reduce_dim", PCA()), ("clf", SVC())])
>>> pipe.set_params(clf__C=10)
Pipeline(steps=[('reduce_dim', PCA()), ('clf', SVC(C=10))])
什么时候有关系?#

这对于进行网格搜索尤其重要::

>>> from sklearn.model_selection import GridSearchCV
>>> param_grid = dict(reduce_dim__n_components=[2, 5, 10],
...                   clf__C=[0.1, 10, 100])
>>> grid_search = GridSearchCV(pipe, param_grid=param_grid)

各个步骤也可以作为参数替换,并且可以通过将非最终步骤设置为来忽略它们 'passthrough'

>>> param_grid = dict(reduce_dim=['passthrough', PCA(5), PCA(10)],
...                   clf=[SVC(), LogisticRegression()],
...                   clf__C=[0.1, 10, 100])
>>> grid_search = GridSearchCV(pipe, param_grid=param_grid)

示例

7.1.1.2. 缓存变压器:避免重复计算#

安装变压器可能在计算上很昂贵。与其 memory 参数集, Pipeline 调用后将缓存每个Transformer fit .此功能用于避免在参数和输入数据相同时计算管道内的匹配变换器。一个典型的例子是网格搜索的情况,其中变压器只能安装一次并可用于每个配置。最后一步永远不会被缓存,即使它是一个Transformer。

参数 memory 需要以缓存变压器。 memory 可以是包含缓存转换器的目录的字符串,也可以是 joblib.Memory 对象::

>>> from tempfile import mkdtemp
>>> from shutil import rmtree
>>> from sklearn.decomposition import PCA
>>> from sklearn.svm import SVC
>>> from sklearn.pipeline import Pipeline
>>> estimators = [('reduce_dim', PCA()), ('clf', SVC())]
>>> cachedir = mkdtemp()
>>> pipe = Pipeline(estimators, memory=cachedir)
>>> pipe
Pipeline(memory=...,
         steps=[('reduce_dim', PCA()), ('clf', SVC())])
>>> # Clear the cache directory when you don't need it anymore
>>> rmtree(cachedir)
缓存转换器的副作用#

使用 Pipeline 在不启用缓存的情况下,可以检查原始实例,例如::

>>> from sklearn.datasets import load_digits
>>> X_digits, y_digits = load_digits(return_X_y=True)
>>> pca1 = PCA(n_components=10)
>>> svm1 = SVC()
>>> pipe = Pipeline([('reduce_dim', pca1), ('clf', svm1)])
>>> pipe.fit(X_digits, y_digits)
Pipeline(steps=[('reduce_dim', PCA(n_components=10)), ('clf', SVC())])
>>> # The pca instance can be inspected directly
>>> pca1.components_.shape
(10, 64)

启用缓存会在安装之前触发变形器的克隆。因此,不能直接检查提供给管道的Transformer实例。在下面的示例中,访问 PCA 例如 pca2 将引发 AttributeError 以来 pca2 将是一个未安装的Transformer。相反,使用属性 named_steps 检查管道内的估计器::

>>> cachedir = mkdtemp()
>>> pca2 = PCA(n_components=10)
>>> svm2 = SVC()
>>> cached_pipe = Pipeline([('reduce_dim', pca2), ('clf', svm2)],
...                        memory=cachedir)
>>> cached_pipe.fit(X_digits, y_digits)
Pipeline(memory=...,
         steps=[('reduce_dim', PCA(n_components=10)), ('clf', SVC())])
>>> cached_pipe.named_steps['reduce_dim'].components_.shape
(10, 64)
>>> # Remove the cache directory
>>> rmtree(cachedir)

示例

7.1.2. 回归中转变目标#

TransformedTargetRegressor 转变目标 y 然后再适应回归模型。预测通过逆变换映射回原始空间。它将用于预测的回归量和将应用于目标变量的Transformer作为参数::

>>> import numpy as np
>>> from sklearn.datasets import fetch_california_housing
>>> from sklearn.compose import TransformedTargetRegressor
>>> from sklearn.preprocessing import QuantileTransformer
>>> from sklearn.linear_model import LinearRegression
>>> from sklearn.model_selection import train_test_split
>>> X, y = fetch_california_housing(return_X_y=True)
>>> X, y = X[:2000, :], y[:2000]  # select a subset of data
>>> transformer = QuantileTransformer(output_distribution='normal')
>>> regressor = LinearRegression()
>>> regr = TransformedTargetRegressor(regressor=regressor,
...                                   transformer=transformer)
>>> X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=0)
>>> regr.fit(X_train, y_train)
TransformedTargetRegressor(...)
>>> print('R2 score: {0:.2f}'.format(regr.score(X_test, y_test)))
R2 score: 0.61
>>> raw_target_regr = LinearRegression().fit(X_train, y_train)
>>> print('R2 score: {0:.2f}'.format(raw_target_regr.score(X_test, y_test)))
R2 score: 0.59

对于简单的转换,可以传递一对函数,而不是Transformer对象,定义转换及其逆映射::

>>> def func(x):
...     return np.log(x)
>>> def inverse_func(x):
...     return np.exp(x)

随后,对象被创建为::

>>> regr = TransformedTargetRegressor(regressor=regressor,
...                                   func=func,
...                                   inverse_func=inverse_func)
>>> regr.fit(X_train, y_train)
TransformedTargetRegressor(...)
>>> print('R2 score: {0:.2f}'.format(regr.score(X_test, y_test)))
R2 score: 0.51

默认情况下,每次匹配时都会检查提供的函数是否相互相反。但是,可以通过设置绕过此检查 check_inverseFalse

>>> def inverse_func(x):
...     return x
>>> regr = TransformedTargetRegressor(regressor=regressor,
...                                   func=func,
...                                   inverse_func=inverse_func,
...                                   check_inverse=False)
>>> regr.fit(X_train, y_train)
TransformedTargetRegressor(...)
>>> print('R2 score: {0:.2f}'.format(regr.score(X_test, y_test)))
R2 score: -1.57

备注

可以通过设置以下两种方式触发转换 transformer 或一对功能 funcinverse_func .但是,设置这两个选项将引发错误。

示例

7.1.3. DeliverUnion:复合要素空间#

FeatureUnion 将几个Transformer对象组合成一个新的Transformer,该transformer组合它们的输出。一 FeatureUnion 获取Transformer对象列表。在匹配过程中,每一个都独立地与数据进行匹配。转换器并行应用,它们输出的特征矩阵并排连接到一个更大的矩阵中。

当您想要对数据的每个字段应用不同的转换时,请参阅相关类 ColumnTransformer (见 user guide ).

FeatureUnion 其目的与 Pipeline - 便利性以及联合参数估计和验证。

FeatureUnionPipeline 可以组合起来创建复杂的模型。

(A FeatureUnion 无法检查两个变压器是否可能产生相同的功能。只有当功能集不相交时,它才会产生联合,而确保它们不相交是调用者的责任。)

7.1.3.1. 使用#

A FeatureUnion 是使用列表构建的 (key, value) 对, key 是您想要赋予给定转换的名称(任意字符串;它仅用作标识符),并且 value 是估计器对象::

>>> from sklearn.pipeline import FeatureUnion
>>> from sklearn.decomposition import PCA
>>> from sklearn.decomposition import KernelPCA
>>> estimators = [('linear_pca', PCA()), ('kernel_pca', KernelPCA())]
>>> combined = FeatureUnion(estimators)
>>> combined
FeatureUnion(transformer_list=[('linear_pca', PCA()),
                               ('kernel_pca', KernelPCA())])

与管道一样,功能联合也有一个名为 make_union 这不需要明确命名组件。

Pipeline ,个别步骤可以使用替换 set_params ,并通过设置为忽略 'drop'

>>> combined.set_params(kernel_pca='drop')
FeatureUnion(transformer_list=[('linear_pca', PCA()),
                               ('kernel_pca', 'drop')])

示例

7.1.4. 适用于异类数据的列转换器#

许多数据集包含不同类型的特征,例如文本、浮点数和日期,其中每种类型的特征都需要单独的预处理或特征提取步骤。 通常,在应用scikit-learn方法之前预处理数据是最简单的,例如使用 pandas .由于以下原因之一,在将数据传递给scikit-learn之前处理数据可能会出现问题:

  1. 将测试数据中的统计数据分解到预处理器中会使交叉验证分数不可靠(称为 data leakage ),例如在缩放器或插补缺失值的情况下。

  2. 您可能希望将预处理器的参数包含在 parameter search .

ColumnTransformer 帮助在 Pipeline 可以防止数据泄露并且可以参数化。 ColumnTransformer 研究数组、稀疏矩阵和 pandas DataFrames .

可以对每列应用不同的转换,例如预处理或特定的特征提取方法::

>>> import pandas as pd
>>> X = pd.DataFrame(
...     {'city': ['London', 'London', 'Paris', 'Sallisaw'],
...      'title': ["His Last Bow", "How Watson Learned the Trick",
...                "A Moveable Feast", "The Grapes of Wrath"],
...      'expert_rating': [5, 3, 4, 5],
...      'user_rating': [4, 5, 4, 3]})

对于此数据,我们可能需要对 'city' 将列作为分类变量,使用 OneHotEncoder 但应用 CountVectorizer'title' 柱由于我们可能会在同一列上使用多种特征提取方法,因此我们为每个Transformer赋予一个唯一的名称,例如 'city_category''title_bow' .默认情况下,其余评级列将被忽略 (remainder='drop' ):

>>> from sklearn.compose import ColumnTransformer
>>> from sklearn.feature_extraction.text import CountVectorizer
>>> from sklearn.preprocessing import OneHotEncoder
>>> column_trans = ColumnTransformer(
...     [('categories', OneHotEncoder(dtype='int'), ['city']),
...      ('title_bow', CountVectorizer(), 'title')],
...     remainder='drop', verbose_feature_names_out=False)

>>> column_trans.fit(X)
ColumnTransformer(transformers=[('categories', OneHotEncoder(dtype='int'),
                                 ['city']),
                                ('title_bow', CountVectorizer(), 'title')],
                  verbose_feature_names_out=False)

>>> column_trans.get_feature_names_out()
array(['city_London', 'city_Paris', 'city_Sallisaw', 'bow', 'feast',
'grapes', 'his', 'how', 'last', 'learned', 'moveable', 'of', 'the',
 'trick', 'watson', 'wrath'], ...)

>>> column_trans.transform(X).toarray()
array([[1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0],
       [1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0],
       [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0],
       [0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1]]...)

在上面的例子中, CountVectorizer 期望1D数组作为输入,因此列被指定为字符串 ('title' ).然而, OneHotEncoder 由于大多数其他转换器都需要2D数据,因此在这种情况下,您需要将列指定为字符串列表 (['city'] ).

除了纯量或单个项列表之外,列选择还可以指定为多个项的列表、整组数组、切片、布尔屏蔽或具有 make_column_selector .的 make_column_selector 用于根据数据类型或列名选择列::

>>> from sklearn.preprocessing import StandardScaler
>>> from sklearn.compose import make_column_selector
>>> ct = ColumnTransformer([
...       ('scale', StandardScaler(),
...       make_column_selector(dtype_include=np.number)),
...       ('onehot',
...       OneHotEncoder(),
...       make_column_selector(pattern='city', dtype_include=object))])
>>> ct.fit_transform(X)
array([[ 0.904,  0.      ,  1. ,  0. ,  0. ],
       [-1.507,  1.414,  1. ,  0. ,  0. ],
       [-0.301,  0.      ,  0. ,  1. ,  0. ],
       [ 0.904, -1.414,  0. ,  0. ,  1. ]])

如果输入是DataFrame,则字符串可以引用列,而integer始终被解释为位置列。

我们可以通过设置保留剩余的评级列 remainder='passthrough' .值附加到转换的结尾::

>>> column_trans = ColumnTransformer(
...     [('city_category', OneHotEncoder(dtype='int'),['city']),
...      ('title_bow', CountVectorizer(), 'title')],
...     remainder='passthrough')

>>> column_trans.fit_transform(X)
array([[1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 5, 4],
       [1, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 3, 5],
       [0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 4, 4],
       [0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 5, 3]]...)

remainder 可以将参数设置为估计器以转换剩余的评级列。转换后的值附加到转换的结尾::

>>> from sklearn.preprocessing import MinMaxScaler
>>> column_trans = ColumnTransformer(
...     [('city_category', OneHotEncoder(), ['city']),
...      ('title_bow', CountVectorizer(), 'title')],
...     remainder=MinMaxScaler())

>>> column_trans.fit_transform(X)[:, -2:]
array([[1. , 0.5],
       [0. , 1. ],
       [0.5, 0.5],
       [1. , 0. ]])

make_column_transformer 功能可以更轻松地创建 ColumnTransformer object.具体来说,姓名将自动给出。上面例子的等效内容是::

>>> from sklearn.compose import make_column_transformer
>>> column_trans = make_column_transformer(
...     (OneHotEncoder(), ['city']),
...     (CountVectorizer(), 'title'),
...     remainder=MinMaxScaler())
>>> column_trans
ColumnTransformer(remainder=MinMaxScaler(),
                  transformers=[('onehotencoder', OneHotEncoder(), ['city']),
                                ('countvectorizer', CountVectorizer(),
                                 'title')])

如果 ColumnTransformer 安装了一个收件箱,并且收件箱只有字符串列名,那么转换收件箱将使用列名来选择列::

>>> ct = ColumnTransformer(
...          [("scale", StandardScaler(), ["expert_rating"])]).fit(X)
>>> X_new = pd.DataFrame({"expert_rating": [5, 6, 1],
...                       "ignored_new_col": [1.2, 0.3, -0.1]})
>>> ct.transform(X_new)
array([[ 0.9],
       [ 2.1],
       [-3.9]])

7.1.5. 可视化复合估计器#

当在jupyter笔记本中显示时,估计器会以HTML表示形式显示。这对于诊断或可视化具有许多估计量的管道非常有用。默认情况下激活此可视化::

>>> column_trans

可以通过设置 display 选项 set_config 到“文本”::

>>> from sklearn import set_config
>>> set_config(display='text')
>>> # displays text representation in a jupyter context
>>> column_trans

HTML输出的一个示例可以在 HTML representation of Pipeline混合类型的列Transformer .作为替代方案,可以使用将HTML写入文件 estimator_html_repr

>>> from sklearn.utils import estimator_html_repr
>>> with open('my_estimator.html', 'w') as f:
...     f.write(estimator_html_repr(clf))

示例