8.1. Конвейеры и составные оцениватели
Для создания составного оценивателя трансформаторы обычно объединяют с другими трансформаторами или с предикторами (например, классификаторами или регрессорами). Наиболее распространённый инструмент для объединения оценивателей — это конвейер. Все шаги конвейера, кроме последнего, должны быть трансформаторами. Последний шаг может быть любым: трансформатором, предиктором или оценивателем кластеризации, у которого может быть или не быть метод .predict(...). Конвейер предоставляет все методы последнего оценивателя: если последний шаг предоставляет метод transform, конвейер будет иметь метод transform и вести себя как трансформатор. Если последний шаг предоставляет метод predict, конвейер предоставит этот метод и, получив данные X, преобразует их с помощью всех шагов, кроме последнего, а затем передаст преобразованные данные методу predict последнего шага конвейера. Класс Pipeline часто используется в сочетании с ColumnTransformer или FeatureUnion, которые объединяют выходные данные трансформаторов в единое пространство признаков. TransformedTargetRegressor выполняет преобразование целевой переменной (например, логарифмическое преобразование y).
8.1.1. Pipeline: объединение оценивателей в цепочку
Pipeline можно использовать для объединения нескольких оценивателей в один. Это удобно, поскольку обработка данных часто состоит из фиксированной последовательности шагов, например отбора признаков, нормализации и классификации. В этом случае Pipeline выполняет несколько задач:
- Удобство и инкапсуляция
-
Чтобы обучить всю последовательность оценивателей, достаточно один раз вызвать для данных методы fit и predict.
- Совместный подбор параметров
-
Можно одновременно выполнить поиск по сетке параметров всех оценивателей в конвейере.
- Безопасность
-
Конвейеры помогают избежать утечки статистик из тестовых данных в обученную модель при перекрёстной проверке, гарантируя, что для обучения трансформаторов и предикторов используются одни и те же выборки.
Все оцениватели конвейера, кроме последнего, должны быть трансформаторами (то есть иметь метод transform). Последний оцениватель может быть любого типа (трансформатор, классификатор и т. д.).
Примечание
Вызов fit для конвейера равносилен последовательному вызову fit для каждого оценивателя: transform входные данные и передать их следующему шагу. Конвейер имеет все методы последнего оценивателя, то есть если последний оцениватель — классификатор, Pipeline можно использовать как классификатор. Если последний оцениватель — трансформатор, то и конвейер будет трансформатором.
8.1.1.1. Использование
8.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())])
8.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()
Атрибут named_steps объекта Pipeline позволяет обращаться к шагам по имени с автодополнением в интерактивных средах:
>>> pipe.named_steps.reduce_dim is pipe['reduce_dim'] True
8.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)'], ...)
8.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
- Отображение конвейеров
8.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)
Примеры
8.1.2. Преобразование целевой переменной в регрессии
TransformedTargetRegressor преобразует целевые значения y перед обучением регрессионной модели. Прогнозы возвращаются в исходное пространство с помощью обратного преобразования. В качестве аргументов принимаются регрессор, используемый для прогнозирования, и трансформатор, применяемый к целевой переменной:
>>> import numpy as np
>>> from sklearn.datasets import make_regression
>>> from sklearn.compose import TransformedTargetRegressor
>>> from sklearn.preprocessing import QuantileTransformer
>>> from sklearn.linear_model import LinearRegression
>>> from sklearn.model_selection import train_test_split
>>> # create a synthetic dataset
>>> X, y = make_regression(n_samples=20640,
... n_features=8,
... noise=100.0,
... random_state=0)
>>> y = np.exp( 1 + (y - y.min()) * (4 / (y.max() - y.min())))
>>> 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(f"R2 score: {regr.score(X_test, y_test):.2f}")
R2 score: 0.67
>>> raw_target_regr = LinearRegression().fit(X_train, y_train)
>>> print(f"R2 score: {raw_target_regr.score(X_test, y_test):.2f}")
R2 score: 0.64
Для простых преобразований вместо объекта-трансформатора можно передать пару функций, задающих преобразование и обратное отображение:
>>> 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(f"R2 score: {regr.score(X_test, y_test):.2f}")
R2 score: 0.67
По умолчанию при каждом обучении проверяется, являются ли переданные функции обратными друг другу. Эту проверку можно отключить, задав 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(f"R2 score: {regr.score(X_test, y_test):.2f}")
R2 score: -3.02
Примечание
Преобразование можно задать либо параметром transformer, либо парой функций func и inverse_func. Однако одновременная настройка обоих вариантов вызовет ошибку.
Примеры
8.1.3. FeatureUnion: составные пространства признаков
FeatureUnion объединяет несколько объектов-трансформаторов в новый трансформатор, объединяющий их выходные данные. FeatureUnion принимает список объектов-трансформаторов. При обучении каждый из них независимо обучается на данных. Трансформаторы применяются параллельно, а полученные ими матрицы признаков объединяются по столбцам в одну большую матрицу.
Если требуется применять разные преобразования к каждому полю данных, см. связанный класс ColumnTransformer (см. руководство пользователя).
FeatureUnion решает те же задачи, что и Pipeline: обеспечивает удобство, совместный подбор параметров и проверку.
FeatureUnion и Pipeline можно объединять для создания сложных моделей.
(У FeatureUnion нет способа проверить, могут ли два трансформатора создавать одинаковые признаки. Класс объединяет наборы признаков, а ответственность за то, чтобы они не пересекались, лежит на вызывающем коде.)
8.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')])
Примеры
8.1.4. ColumnTransformer для разнородных данных
Во многих наборах данных есть признаки разных типов, например текст, числа с плавающей точкой и даты, и каждый тип признаков требует отдельных шагов предварительной обработки или извлечения признаков. Часто проще всего обработать данные до применения методов scikit-learn, например с помощью pandas. Предварительная обработка данных до передачи их в scikit-learn может быть проблематичной по следующим причинам:
- Использование статистик тестовых данных при предварительной обработке делает оценки перекрёстной проверки ненадёжными (это явление называется утечкой данных), например при масштабировании или заполнении пропусков.
- Может потребоваться включить параметры средств предварительной обработки в поиск параметров.
ColumnTransformer позволяет применять разные преобразования к разным столбцам данных в рамках Pipeline, защищённого от утечки данных и допускающего настройку параметров. ColumnTransformer работает с массивами, разреженными матрицами и DataFrame из pandas.
К каждому столбцу можно применить отдельное преобразование, например предварительную обработку или конкретный метод извлечения признаков:
>>> 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, а к столбцу 'title' применить CountVectorizer. Поскольку к одному столбцу можно применять несколько методов извлечения признаков, каждому трансформатору присваивается уникальное имя, например '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, "string"]))])
>>> 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. ]])
Для упрощённого создания объекта ColumnTransformer доступна функция make_column_transformer. В частности, имена будут заданы автоматически. Эквивалент приведённого выше примера:
>>> 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 для выбора столбцов будут использоваться их имена:
>>> 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]])
8.1.5. Визуализация составных оценивателей
При отображении в блокноте Jupyter оцениватели представляются в формате HTML. Это удобно для диагностики или визуализации конвейера, состоящего из множества оценивателей. По умолчанию визуализация включена:
>>> column_trans
Её можно отключить, задав параметру display в set_config значение «text»:
>>> 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–2026 The scikit-learn developers
Licensed under the 3-clause BSD License.
https://scikit-learn.org/1.9/modules/compose.html