变换器(transformers)通常与分类器、回归器或其他估计器相结合来构建复合估计器。最常用的工具是Pipeline
。Pipeline 通常与FeatureUnion 结合使用,FeatureUnion 将变换器(transformers)的输出连接到一个复合特征空间中。TransformedTargetRegressor 用于变换 target (与对数变换 y)。不同,Pipeline 只变换观测数据(X)。
1. 管道:链接估计器(chaining estimators)
Pipeline
可用于将多个估计器链接为一个估计器。这是有用的,因为在处理数据时通常有固定的步骤,例如特征选择、规范化和分类。Pipeline
在这里有多种用途:
方便性和封装性
你只需要对数据调用一次 fit 和 predict 就可以拟合一个完整的估计序列。
联合参数选择
可以同时对 pipeline 中所有估计器的参数进行grid search。
安全性
使用相同的样本来训练变换器(transformers)和预测器(predictors),管道能避免将测试数据中的统计信息泄漏到交叉验证的训练模型中。
管道中除最后一个估计器外的所有估计器都必须是变换器(transformers)(即必须有 transform 方法)。最后一个估计器可以是任何类型(变换器、分类器等)。
1.1. 用法
1.1.1. 构造(Construction)
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
是用于构造管道;它接受可变数量的估计器并返回管道,自动填写估计器名称:
>>> 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())])
1.1.2. 访问步骤
The estimators of a pipeline are stored as a list in the steps
attribute, but can be accessed by index or name by indexing (with [idx]
) the Pipeline:
管道的估计器作为列表存储在steps
属性中,可以通过索引或名称(使用[idx]
)来访问管道中的估计器:
>>> pipe.steps[0]
('reduce_dim', PCA())
>>> pipe[0]
PCA()
>>> pipe['reduce_dim']
PCA()
Pipeline’s named_steps
attribute allows accessing steps by name with tab completion in interactive environments:
管道的named_steps
属性允许在交互环境中按名称(使用tab键补全)访问步骤:
>>> pipe.named_steps.reduce_dim is pipe['reduce_dim']
True
还可以使用通常用于Python序列(如列表或字符串)的切片表示法来提取子管道(sub-pipeline)(尽管只允许步骤1)。这只便于执行某些转换(或其逆转换):
>>> pipe[:1]
Pipeline(steps=[('reduce_dim', PCA())])
>>> pipe[-1:]
Pipeline(steps=[('clf', SVC())])
1.1.3. 嵌套参数(Nested parameters)
可以使用__
语法访问管道中的估计器参数:
>>> 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'
忽略:
>>> from sklearn.linear_model import LogisticRegression
>>> 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)
可通过索引检索管道的估计器:
>>> pipe[0]
PCA()
或通过名字:
>>> pipe['reduce_dim']
PCA()
示例:
管道方差分析支持向量机
用于文本特征提取和评估的样例管道
管道:链接PCA和逻辑回归
RBF核函数的显式特征映射近似
SVM方差分析:具有单变量特征选择的SVM
使用管道和网格搜索进行选择降维
也可以参阅:
复合估计器和参数空间
1.2. 注意
在管道上调用fit
等同于依次调用每个估计器上的fit
方法,transform
输入并将其传递到下一步。管道具有管道中最后一个估计器所具有的所有方法,即如果最后一个估计器是分类器,那么Pipeline
可以用作分类器。如果最后一个估计器是变换器(transformer),那么管道也可以用作变换器(transformer)。
1.3. 缓存变换器:避免重复计算
训练变换器(transformer)的计算成本比较高。通过设置memory
参数,Pipeline
将在调用fit
后缓存每个变换器。此功能用于避免在参数和输入数据相同的情况下再次拟合管道内的变换器。一个典型的例子是网格搜索,在这种情况下,变换器只要拟合一次,则对每个配置都可重复使用。
必须提供memory
参数来缓存变换器(transformer)。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
,可以检查原始实例(original instance),例如:
>>> 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]]
启用缓存会在训练之前触发变换器的克隆,因此不能直接检查给定给管道的变换器实例。在下面的示例中,由于pca2
是未经拟合的变换器,因此访问PCA
实例pca2
将引发AttributeError
;相反,可以使用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)
示例:
使用管道和网格搜索选择降维
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_inverse
设置为False
来绕过此检查:
>>> 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
来触发转换。但是,同时设置这两个选项将引发错误。
示例:
在回归模型中转换目标的影响
3. FeatureUnion:组合特征空间
FeatureUnion
将多个transformer对象组合到一个新的transformer中,以合并它们的输出。FeatureUnion
获取transformer对象的列表。在拟合过程中,每一个transformer都独立地拟合数据。transformer并发使用,输出的特征矩阵将并排连接成一个较大的矩阵。
如果要对数据的每个字段应用不同的转换,请参阅sklearn.compose.ColumnTransformer
(见 user guide)。
FeatureUnion
与Pipeline
具有相同的用途—方便性和联合参数估计与验证。
FeatureUnion
和Pipeline
可以结合起来创建复杂的模型。
(FeatureUnion
无法检查两个变换器是否可能产生相同的特征,它只在特征集不相交时生成联合,并确保它们是调用方的职责。)
3.1. 用法
FeatureUnion
是使用(key, value)
对的列表构建的,其中key
是给定转换(任意字符串;它仅用作标识符)的名称,值是估计器对象:
>>> 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')])
示例:
串联多特征提取方法
4. 异构数据的列变换器(ColumnTransformer for heterogeneous data)
警告: compose.ColumnTransformer
类还在实验中,其API可能会发生更改。
许多数据集包含不同类型的特征,例如文本、浮点和日期,其中特征的每种类型都需要单独进行预处理或特征提取。通常,在应用scikit-learn方法之前,最容易对数据进行预处理,是使用 pandas 。在将数据传递给scikit-learn之前对其进行处理可能会出现问题,原因如下:
将测试数据中的统计信息合并到预处理器中会使交叉验证分数不可靠(称为数据泄漏),例如在使用缩放器(scalers)或输入缺失值的情况下。
您可能希望在parameter search中包含预处理器的参数。
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.OneHotEncoder
将'city'
列编码为分类变量,而对 'title'
列应用feature_extraction.text.CountVectorizer
。由于我们可能在同一列上使用多个特征提取方法,因此我们为每个变换器指定一个唯一的名称,例如'city_category'
和'title_bow'
。默认情况下,将忽略其余的 rating 列(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([[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'
)。但是,preprocessing.OneHotEncoder
与大多数其他转换器一样需要一个二维数据作为输入,因此在这种情况下,需要将列指定为字符串列表(['city']
)。
除了标量或单项列表之外,列选择还可以指定为多项列表、整数数组、切片、布尔掩码(boolean mask)或使用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,那么可以使用字符串引用列,而整数始终解释为位置列。
我们可以通过设置 remainder='passthrough'
来保留剩余的 rating 列。这些值将附加到转换的末尾:
>>> 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
参数可以设置为估计器,以转换剩余的 rating 列。转换后的值将附加到转换的末尾:
>>> 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
对象。具体来说,这些名字将自动给出。上述示例的等价为:
>>> 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')])
示例:
处理异构数据源的列转换器
处理混合型数据的变换器
文壹由“伴编辑器”提供技术支持
☆☆☆为方便大家查阅,小编已将scikit-learn学习路线专栏 文章统一整理到公众号底部菜单栏,同步更新中,关注公众号,点击左下方“系列文章”,如图:欢迎大家和我一起沿着scikit-learn文档这条路线,一起巩固机器学习算法基础。(添加微信:mthler,备注:sklearn学习,一起进【sklearn机器学习进步群】开启打怪升级的学习之旅。)