6.1. Трубопроводы и составные оценщики
Для построения составного оценщика трансформеры обычно комбинируются с другими трансформерами или с предикторами (такими как классификаторы или регрессоры). Наиболее распространённым инструментом для композиции оценщиков является трубопровод. Трубопроводы требуют, чтобы все шаги, кроме последнего, были трансформерами. Последний шаг может быть любым: трансформером, предиктором или оценщиком кластеризации, который может или не может иметь метод .predict(...). Трубопровод предоставляет все методы, предоставляемые последним оценщиком: если последний шаг предоставляет метод transform, то трубопровод будет иметь метод transform и вести себя как трансформер. Если последний шаг предоставляет метод predict, то трубопровод предоставит этот метод и, получив данные X, использует все шаги, кроме последнего, для преобразования данных, а затем передаёт эти преобразованные данные методу predict последнего шага трубопровода. Класс Pipeline часто используется в сочетании с ColumnTransformer или FeatureUnion, которые конкатенируют выходные данные трансформеров в составное пространство признаков. TransformedTargetRegressor обрабатывает преобразование мишени (например, логарифмическое преобразование y).
6.1.1. Трубопровод: объединение оценщиков
Pipeline может использоваться для объединения нескольких оценщиков в один. Это полезно, так как часто существует фиксированная последовательность шагов в обработке данных, например, отбор признаков, нормализация и классификация. Pipeline служит здесь нескольким целям:
- Удобство и инкапсуляция
-
Вам нужно только один раз вызвать fit и predict с вашими данными, чтобы обучить целую последовательность оценщиков.
- Совместный подбор параметров
-
Вы можете произвести подбор параметров для всех оценщиков в трубопроводе одновременно.
- Безопасность
-
Трубопроводы помогают избежать утечки статистических данных из тестовых данных в обученную модель при перекрёстной проверке, гарантируя, что одни и те же образцы используются для обучения трансформеров и предикторов.
Все оценщики в трубопроводе, кроме последнего, должны быть трансформерами (то есть должны иметь метод 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 позволяет получать доступ к шагам по имени с автодополнением в интерактивных средах:
>>> 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)
Примеры
- Трубопровод ANOVA SVM
- Образец трубопровода для извлечения и оценки текстовых признаков
- Трубопроводы: объединение PCA и логистической регрессии
- Явное приближение отображения признаков для ядер RBF
- SVM-Anova: SVM с отбором признаков с использованием однофакторного анализа
- Выбор уменьшения размерности с помощью Pipeline и GridSearchCV
- Отображение трубопроводов
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
Для простых преобразований вместо объекта 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_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. Использование
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')])
Примеры
6.1.4. ColumnTransformer для разнородных данных
Многие наборы данных содержат признаки разных типов, например, текст, числа с плавающей точкой и даты, где каждый тип признака требует отдельных шагов предварительной обработки или извлечения признаков. Часто проще всего предварительно обработать данные перед применением методов scikit-learn, например, с помощью pandas. Обработка данных перед передачей их в scikit-learn может быть проблематичной по одной из следующих причин:
- Включение статистических данных из тестовых данных в предварительные обработчики делает оценки перекрестной проверки ненадежными (известно как утечка данных), например, в случае масштабирования или заполнения пропущенных значений.
- Вы можете захотеть включить параметры предварительных обработчиков в поиске параметров.
Класс ColumnTransformer помогает выполнять различные преобразования для разных столбцов данных в рамках Pipeline, который защищен от утечки данных и может быть параметризован. ColumnTransformer работает с массивами, разреженными матрицами и pandas DataFrame.
Для каждого столбца можно применить разное преобразование, такое как предварительная обработка или определённый метод извлечения признаков:
>>> 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'. Поскольку мы можем использовать несколько методов извлечения признаков для одного и того же столбца, каждому преобразователю даётся уникальное имя, например, '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 ожидает одномерный массив в качестве входных данных, поэтому столбцы были указаны как строка ('title'). Однако OneHotEncoder, как и большинство других преобразователей, ожидает двумерные данные, поэтому в этом случае вам нужно указать столбец как список строк (['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, целые числа всегда интерпретируются как позиционные столбцы.
Мы можем сохранить оставшиеся столбцы рейтингов, установив 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. В частности, имена будут присваиваться автоматически. Эквивалент для приведенного выше примера будет:
>>> 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 подгоняется под DataFrame, и в DataFrame есть только строковые имена столбцов, то преобразование DataFrame будет использовать имена столбцов для выбора столбцов:
>>> 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...]])
6.1.5. Визуализация составных оценщиков
Оценщики отображаются с помощью HTML-представления при отображении в jupyter notebook. Это полезно для диагностики или визуализации Pipeline со многими оценщиками. Эта визуализация включена по умолчанию:
>>> 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-представление Pipeline в Column 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))
Примеры
© 2007–2025 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.6/modules/compose.html