7.1. Конвейеры и составные оценщики#
Для построения составного оценщика трансформеры обычно комбинируются с другими
трансформерами или с предикторы (такие как классификаторы или регрессоры). Наиболее распространенный инструмент для композиции оценщиков - это Pipeline. Конвейеры требуют, чтобы все шаги, кроме последнего, были
преобразователь. Последний шаг может быть любым: преобразователем,
предиктора, или кластеризационный оценщик, который может иметь или не иметь
.predict(...) метод. Конвейер предоставляет все методы, предоставляемые последним
оценщиком: если последний шаг предоставляет transform метод, тогда конвейер
будет иметь transform метод и ведут себя как преобразователь. Если последний шаг
предоставляет predict метод, тогда конвейер будет предоставлять этот метод, и при наличии данных X, используйте все шаги, кроме последнего, для преобразования данных,
а затем передайте эти преобразованные данные в predict метод последнего шага конвейера. Класс Pipeline часто используется в сочетании с
ColumnTransformer или
FeatureUnion которые объединяют выходные данные преобразователей в составное пространство признаков.
#27137
занимается преобразованием цель (т.е. логарифмическое преобразование y).
7.1.1. Конвейер: объединение оценщиков#
Pipeline может использоваться для объединения нескольких оценщиков в один. Это полезно, так как часто существует фиксированная последовательность шагов в обработке данных, например, выбор признаков, нормализация и классификация. Pipeline служит нескольким целям здесь:
- Удобство и инкапсуляция
Вам нужно только вызвать fit и predict один раз на ваших данных, чтобы обучить всю последовательность оценщиков.
- Совместный выбор параметров
Вы можете grid search по параметрам всех оценщиков в конвейере одновременно.
- Безопасность
Конвейеры помогают избежать утечки статистики из тестовых данных в обученную модель при перекрестной проверке, гарантируя, что одни и те же образцы используются для обучения преобразователей и предикторов.
Все оценщики в конвейере, кроме последнего, должны быть преобразователями (т.е. должны иметь преобразовать метод). Последний оценщик может быть любого типа (преобразователь, классификатор и т.д.).
Примечание
Вызов fit на конвейере эквивалентно вызову fit на каждом оценщике по очереди, transform входные данные и передать их на следующий шаг.
Конвейер имеет все методы, которые есть у последнего оценщика в конвейере,
т.е. если последний оценщик является классификатором, то Pipeline может использоваться
в качестве классификатора. Если последний оценщик — трансформер, то и конвейер
тоже является трансформером.
7.1.1.1. Использование#
7.1.1.1.1. Построить конвейер#
The Pipeline построен с использованием списка (key, value) пары, где
key valgrind-python.supp 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())])
7.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’s named_steps атрибут позволяет обращаться к шагам по имени с автодополнением в интерактивных средах:
>>> pipe.named_steps.reduce_dim is pipe['reduce_dim']
True
7.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)'], ...)
7.1.1.1.4. Доступ к вложенным параметрам#
Часто требуется настраивать параметры оценщика внутри конвейера. Этот параметр
является вложенным, поскольку принадлежит определённому подэтапу. Параметры
оценщиков в конвейере доступны с использованием
синтаксис:
>>> 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)
Смотрите также
Примеры
7.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)
Примеры
7.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
Для простых преобразований вместо объекта 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(f"R2 score: {regr.score(X_test, y_test):.2f}")
R2 score: 0.67
По умолчанию предоставленные функции проверяются на каждой подгонке, чтобы быть обратными друг другу. Однако можно обойти эту проверку, установив
check_inverse to 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. Однако установка обоих параметров вызовет ошибку.
Примеры
7.1.3. FeatureUnion: составные пространства признаков#
FeatureUnion объединяет несколько объектов-преобразователей в новый
преобразователь, который объединяет их выходные данные. FeatureUnion принимает
список объектов-трансформеров. При обучении каждый из них
обучается на данных независимо. Трансформеры применяются параллельно,
и выходные матрицы признаков объединяются бок о бок в
большую матрицу.
Когда вы хотите применить различные преобразования к каждому полю данных,
см. связанный класс ColumnTransformer
(см. руководство пользователя).
FeatureUnion служит тем же целям, что и Pipeline -
удобство и совместная оценка параметров и валидация.
FeatureUnion и Pipeline могут быть объединены для
создания сложных моделей.
(A FeatureUnion не имеет возможности проверить, могут ли два трансформера производить идентичные признаки. Он создаёт объединение только тогда, когда наборы признаков не пересекаются, и обеспечение этого — ответственность вызывающей стороны.)
7.1.3.1. Использование#
A 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')])
Примеры
7.1.4. ColumnTransformer для разнородных данных#
Многие наборы данных содержат признаки разных типов, например, текст, числа с плавающей точкой и даты, где каждый тип признака требует отдельных шагов предварительной обработки или извлечения признаков. Часто проще всего предварительно обработать данные перед применением методов scikit-learn, например, используя pandas. Обработка ваших данных перед передачей в scikit-learn может быть проблематичной по одной из следующих причин:
Включение статистики из тестовых данных в препроцессоры делает оценки кросс-валидации ненадежными (известно как утечка данных), например, в случае масштабирования или заполнения пропущенных значений.
Вы можете захотеть включить параметры препроцессоров в поиск параметров.
The ColumnTransformer помогает выполнять различные преобразования для разных столбцов данных в рамках
Pipeline который защищен от утечки данных и может
быть параметризован. 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]})
Для этих данных мы можем захотеть закодировать '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, "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]]...)
The 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. ]])
The 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]])
7.1.5. Визуализация композитных оценщиков#
Оценщики отображаются с HTML-представлением при показе в jupyter notebook. Это полезно для диагностики или визуализации Pipeline с многими оценщиками. Эта визуализация активирована по умолчанию:
>>> 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 раздел
Трансформер столбцов со смешанными типами. В качестве альтернативы, 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))
Примеры