6.1. 管線與複合估計器#
為了建構複合估計器,轉換器通常會與其他轉換器或預測器(例如分類器或迴歸器)結合。用於組合估計器的最常見工具是管線。管線要求除了最後一個步驟之外的所有步驟都必須是轉換器。最後一個步驟可以是任何東西,一個轉換器,一個預測器,或是一個可能具有或不具有 .predict(...)
方法的分群估計器。管線會公開最後一個估計器提供的所有方法:如果最後一個步驟提供 transform
方法,則管線將具有 transform
方法,並且行為類似轉換器。如果最後一個步驟提供 predict
方法,則管線將會公開該方法,並且在給定資料 X 的情況下,使用除了最後一個步驟以外的所有步驟來轉換資料,然後將轉換後的資料傳遞給管線最後一個步驟的 predict
方法。 Pipeline
類別通常與 ColumnTransformer 或 FeatureUnion 結合使用,它們將轉換器的輸出連接到複合特徵空間。TransformedTargetRegressor 處理轉換目標(即對y 進行對數轉換)。
6.1.1. 管線:串聯估計器#
Pipeline
可用於將多個估計器串聯成一個。這很有用,因為在處理資料時通常會有固定的步驟順序,例如特徵選擇、正規化和分類。Pipeline
在此處有多個用途
- 方便性和封裝
- 聯合參數選擇
您可以同時在管線中所有估計器的參數上進行格網搜尋。
- 安全性
管線透過確保使用相同的樣本來訓練轉換器和預測器,有助於避免在交叉驗證中將測試資料的統計資訊洩漏到訓練好的模型中。
管線中的所有估計器(最後一個除外)都必須是轉換器(即必須具有 transform 方法)。最後一個估計器可以是任何類型(轉換器、分類器等)。
注意
在管線上呼叫 fit
與依次在每個估計器上呼叫 fit
、transform
輸入並將其傳遞到下一步相同。管線具有管線中最後一個估計器具有的所有方法,也就是說,如果最後一個估計器是分類器,則 Pipeline
可以用作分類器。如果最後一個估計器是轉換器,那麼管線也是。
6.1.1.1. 用法#
6.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())])
6.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()
Pipeline
的 named_steps
屬性允許在互動式環境中使用 Tab 完成來依名稱存取步驟
>>> pipe.named_steps.reduce_dim is pipe['reduce_dim']
True
6.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)'], ...)
6.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)
另請參閱
範例
6.1.1.2. 快取轉換器:避免重複計算#
擬合轉換器可能在計算上很耗費資源。透過設定 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(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)
啟用快取會在擬合前觸發轉換器的複製。因此,無法直接檢查提供給管道的轉換器實例。在以下範例中,存取 PCA
實例 pca2
將會引發 AttributeError
,因為 pca2
將會是一個未擬合的轉換器。相反地,請使用屬性 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)
範例
6.1.2. 在迴歸中轉換目標#
TransformedTargetRegressor
在擬合迴歸模型之前,會轉換目標 y
。預測會透過反向轉換映射回原始空間。它會將用於預測的迴歸器和將應用於目標變數的轉換器作為引數。
>>> 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
對於簡單的轉換,可以使用一對函數來傳遞,而不是傳遞轉換器物件,它們定義了轉換及其反向映射。
>>> 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_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: -1.57
注意
可以透過設定 transformer
或函數對 func
和 inverse_func
來觸發轉換。但是,同時設定這兩個選項將會引發錯誤。
範例
6.1.3. FeatureUnion:複合特徵空間#
FeatureUnion
將多個轉換器物件合併為一個新的轉換器,它會組合它們的輸出。 FeatureUnion
接受轉換器物件的清單。在擬合期間,每個轉換器都會獨立地擬合資料。轉換器會平行應用,並且它們輸出的特徵矩陣會並排串聯成一個更大的矩陣。
當您想要將不同的轉換應用於資料的每個欄位時,請參閱相關的類別 ColumnTransformer
(請參閱 使用者指南)。
FeatureUnion
的用途與 Pipeline
相同 - 方便性和聯合參數估計和驗證。
可以組合 FeatureUnion
和 Pipeline
來建立複雜的模型。
(FeatureUnion
無法檢查兩個轉換器是否可能產生相同的特徵。只有在特徵集不相交時才會產生聯集,確保它們不相交是呼叫者的責任。)
6.1.3.1. 用法#
使用 (key, value)
對的清單來建構 FeatureUnion
,其中 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')])
範例
6.1.4. 用於異質資料的 ColumnTransformer#
許多資料集包含不同類型的特徵,例如文字、浮點數和日期,其中每種特徵類型都需要單獨的預處理或特徵提取步驟。通常,在應用 scikit-learn 方法之前預處理資料是最容易的,例如使用 pandas。在將資料傳遞給 scikit-learn 之前處理您的資料可能會因為以下原因之一而產生問題
將測試資料的統計資訊納入預處理器會使交叉驗證分數變得不可靠 (稱為 *資料洩漏*),例如,在縮放器或插補遺失值的情況下。
您可能想要將預處理器的參數包含在 參數搜尋 中。
>>> 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]})
>>> 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]]...)
>>> 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. ]])
>>> 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]]...)
>>> 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. ]])
>>> 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')])
>>> 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...]])
>>> column_trans
>>> from sklearn import set_config
>>> set_config(display='text')
>>> # displays text representation in a jupyter context
>>> column_trans
>>> from sklearn.utils import estimator_html_repr
>>> with open('my_estimator.html', 'w') as f:
... f.write(estimator_html_repr(clf))
範例