6.1 管道和复合估算器

转换器(Transformers )通常与分类器,回归器或其他估计器组合在一起,以构建复合估计器。最常用的工具是 管道(Pipeline)。管道通常与FeatureUnion结合使用, FeatureUnion将转换器的输出连接到复合特征空间中。 TransformedTargetRegressor处理转换目标 (即log-transform y)。否则,Pipelines仅变换观察到的数据(X)。

6.1.1 管道(Pipeline):链式估计器

Pipeline可用于将多个估计器链接为一个。这很有用,因为在处理数据时通常会有固定的步骤顺序,例如特征选择,归一化和分类。Pipeline在这里有多种用途:

  • 便捷和封装

    只需要对数据集调用一次fitpredict,就可以适配一系列评估器。

  • 联合参数选择

    您可以对管道中估计器的所有参数进行一次网格搜索

  • 安全

    转换器( transformers)和预测器(predictors)使用相同的样本训练,管道有助于避免将统计数据从测试数据泄漏到经过交叉验证训练的模型中。

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

6.1.1.1 用法

6.1.1.1.1 构造

管道是由包含(键,值)对的列表构建的,其中键是包含此步骤名称的字符串,而值是估计器对象:

>>> 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是构造管道的简写。它使用可变数量的估计器并返回管道,而且自动填充名称:

>>> from sklearn.pipeline import make_pipeline
>>> from sklearn.naive_bayes import MultinomialNB
>>> from sklearn.preprocessing import Binarizer
>>> make_pipeline(Binarizer(), MultinomialNB())
Pipeline(steps=[('binarizer', Binarizer()), ('multinomialnb', MultinomialNB())])

6.1.1.1.2 访问步骤

管道的估计器在steps属性中以列表形式存储,但是可以对管道建立索引并通过索引或名称(通过[idx])来访问管道:

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

管道的named_steps属性允许在交互式环境中按名称和制表符(tab)补全的方式访问步骤:

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

也可以使用通常用于Python序列(例如列表或字符串)的切片方法来提取子管道(尽管步长只能为1)。 这对于仅执行某些转换(或其逆转换)是很方便的:

>>> pipe[:1]
Pipeline(steps=[('reduce_dim', PCA())])
>>> pipe[-1:]
Pipeline(steps=[('clf', SVC())])

6.1.1.1.3 嵌套参数

可以使用<estimator>__<parameter>语法访问管道中估计器的参数 :

>>> 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=[2510],
...                   clf__C=[0.110100])
>>> grid_search = GridSearchCV(pipe, param_grid=param_grid)

各个单独的步骤可以替换为多个参数,并将非最终步骤设置为'passthrough'

>>> from sklearn.linear_model import LogisticRegression
>>> param_grid = dict(reduce_dim=['passthrough', PCA(5), PCA(10)],
...                   clf=[SVC(), LogisticRegression()],
...                   clf__C=[0.110100])
>>> grid_search = GridSearchCV(pipe, param_grid=param_grid)

管道的估算器可以通过索引检索:

>>> pipe[0]
PCA()

或通过名称:

>>> pipe['reduce_dim']
PCA()

示例

也可以参阅:

6.1.1.2 注释

管道调用fit方法与依次调用每个估计器的fit方法效果相同(transform输入并将其传递到下一步)。管道具有管道中最后一个估计器的所有方法,即,如果最后一个估计器是一个分类器,则Pipeline可以用作分类器。如果最后一个估计器是转换器,那么管道可以用作转换器。

6.1.1.3 缓存转换器:避免重复计算

适配转换器很耗费计算资源,通过设置memory参数, Pipeline将在调用fit方法后缓存每个转换器。如果参数和输入数据一致,则此功能可避免重复计算适配管道内的转换器。一个典型的例子是网格搜索中,转换器只需适配一次即可应用于每种配置。

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()
>>> svm1 = SVC()
>>> pipe = Pipeline([('reduce_dim', pca1), ('clf', svm1)])
>>> pipe.fit(X_digits, y_digits)
Pipeline(steps=[('reduce_dim', PCA()), ('clf', SVC())])
>>> # The pca instance can be inspected directly
>>> print(pca1.components_)
 [[-1.77484909e-19  ... 4.07058917e-18]]  

启用缓存会在适配前触发转换器的克隆。因此,管道的转换器实例不能直接进行查看。在下面的示例中,访问PCA实例pca2 将引发AttributeError,因为pca2是未进行适配的转换器,应该使用属性named_steps检查管道中的评估器:

>>> cachedir = mkdtemp()
>>> pca2 = PCA()
>>> 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()), ('clf', SVC())])
>>> print(cached_pipe.named_steps['reduce_dim'].components_)
 [[-1.77484909e-19  ... 4.07058917e-18]]
>>> # Remove the cache directory
>>> rmtree(cachedir)

示例:

6.1.2 回归中转换目标

TransformedTargetRegressor在拟合回归模型之前先转换目标y。通过逆变换将预测映射回原始空间。它以将用于预测的回归器和将应用于目标变量的转换器作为参数:

>>> import numpy as np
>>> from sklearn.datasets import load_boston
>>> 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 = load_boston(return_X_y=True)
>>> 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.67
>>> 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.64

对于简单的转换,可以传递一对函数,而不是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.65

默认情况下,所提供的函数在每次拟合时都检查是否彼此相反。但是,可以通过设置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: -4.50

**注意:**所述转化可以通过设定任一方式触发,transformer或双功能参数func以及inverse_func。但是,同时设置这两个选项将引发错误。

示例

在回归模型中转换目标的效果

6.1.3 联合特征(FeatureUnion):复合特征空间

FeatureUnion将多个转换器对象组合为一个新的转换器,该转换器将多个转换器的输出合并在一起。

一个 FeatureUnion包含一个转换器对象列表。在拟合期间,每个参数都独立地适配数据。并行应用这些转换器,并将它们输出的特征矩阵并排连接成一个更大的矩阵。

如果您想对数据的每个字段应用不同的转换,请参阅相关的类sklearn.compose.ColumnTransformer (请参阅用户指南)。

FeatureUnion具有与Pipeline相同的目的--便利性及联合参数估计和验证。

结合FeatureUnionPipeline可以创建复杂的模型。

(一个FeatureUnion无法检查两个转换器是否可能产生相同的功能。仅当特征集合不相交时才产生并集,并确保是调用者的责任。)

6.1.3.1 用法

FeatureUnion是使用(key, value)对的列表构建的,其中key是要为给定的转换指定的名称(任意字符串;它仅用作标识符),而value1是估计对象:

>>> 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')])

示例:

6.1.4 异构数据的列转换器

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

  1. 将测试数据中的统计信息合并到预处理器中,会使交叉验证得分不可靠(称为数据泄漏),例如在定标器或估算缺失值的情况下。
  2. 您可能希望通过参数搜索调整预处理器中的参数

ColumnTransformer有助于在管道内对数据的不同列进行不同的变换,以防止数据泄漏并且可以对其进行参数化。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]})

对于此数据,我们可能希望使用preprocessing.OneHotEncodercity列编码为类别变量,同时将feature_extraction.text.CountVectorizer 应用于“title列。由于我们可能在同一列上使用多种特征提取方法,因此我们给每个转换器一个唯一的名称,例如'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(
...     [('city_category', OneHotEncoder(dtype='int'),['city']),
...      ('title_bow', CountVectorizer(), 'title')],
...     remainder='drop')

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

>>> column_trans.get_feature_names()
['city_category__x0_London''city_category__x0_Paris''city_category__x0_Sallisaw',
'title_bow__bow''title_bow__feast''title_bow__grapes''title_bow__his',
'title_bow__how''title_bow__last''title_bow__learned''title_bow__moveable',
'title_bow__of''title_bow__the''title_bow__trick''title_bow__watson',
'title_bow__wrath']

>>> column_trans.transform(X).toarray()
array([[1001001010000000],
       [1000000101001110],
       [0100100000100000],
       [0010010000011001]]...)

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

除了标量或单个项目列表之外,可以将列选择指定为多个项目的列表,整数数组,切片,布尔掩码或使用make_column_selectormake_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,则字符串可以引用列,整数始终被解释为位置列。

通过设置,我们可以通过设置 remainder='passthrough'保留其余的评分栏。这些值将附加到转换的末尾:

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

>>> column_trans.fit_transform(X)
array([[100100101000000054],
       [100000010100111035],
       [010010000010000044],
       [001001000001100153]]...)

通过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.50.5],
       [1. , 0. ]])

make_column_transformer函数可用于更轻松地创建ColumnTransformer对象。具体来说,名称将自动给出。以上示例的等效项为:

>>> 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')])

6.1.5 可视化复合估计器

在jupyter notebook中显示估算器时,可以用HTML形式显示估算器。这对于许多使用估算器诊断或可视化管道很有用。通过在 sklearn.set_config中设置display选项激活此可视化:

>>> from sklearn import set_config
>>> set_config(display='diagram')   
>>> # diplays HTML representation in a jupyter context
>>> column_trans  

HTML输出的示例可以在具有混合类型Column Transformer的输出为HTML形式的Pipeline中看到 。或者,可以使用以下命令将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))

示例: