6.1. Пайплайны и составные оценщики

scikit-learn предоставляет библиотеку преобразователей, которые могут очищать (см. Предварительная обработка данных ), уменьшать (см. Неконтролируемое уменьшение размерности ), расширять (см. Аппроксимация ядра ) или генерировать (см. Извлечение функций ) представления функций.

Как и другие оценщики, они представлены классами с fit методом, который изучает параметры модели (например, среднее значение и стандартное отклонение для нормализации) из обучающего набора, и transform методом, который применяет эту модель преобразования к невидимым данным. fit_transform может быть более удобным и эффективным для одновременного моделирования и преобразования обучающих данных.

Объединение таких трансформаторов в параллельном или последовательном режиме рассматривается в разделе «Пайплайны и составные оценщики» . Парные метрики, сходства и ядра охватывают преобразование пространств признаков в матрицы сходства, в то время как преобразование цели прогнозирования (y) рассматривает преобразования целевого пространства (например, категориальные метки) для использования в scikit-learn.

Преобразователи обычно комбинируются с классификаторами, регрессорами или другими оценщиками для построения составного оценщика. Самый распространенный инструмент — пайплайн (конвейер). Конвейер часто используется в сочетании с FeatureUnion, который объединяет выходные данные преобразователей в составное пространство функций. TransformedTargetRegressor занимается преобразованием цели (т. Е. Логарифмическим преобразованием y ). Напротив, конвейеры преобразуют только наблюдаемые данные ( X ).

6.1.1. Конвейер: объединение оценок

Pipeline можно использовать для объединения нескольких оценщиков в одну. Это полезно, поскольку часто существует фиксированная последовательность шагов при обработке данных, например, выбор функций, нормализация и классификация. Pipeline здесь служит нескольким целям:Удобство и герметичность

Вам нужно только один раз вызвать fit и predict свои данные, чтобы они соответствовали всей последовательности оценщиков.Совместный выбор параметров

Вы можете выполнять поиск по сетке сразу по параметрам всех оценщиков в конвейере.Безопасность

Конвейеры помогают избежать утечки статистики из ваших тестовых данных в обученную модель при перекрестной проверке, гарантируя, что одни и те же образцы используются для обучения преобразователей и предикторов.

Все оценщики в конвейере, кроме последнего, должны быть преобразователями (т. Е. Должны иметь метод transform ). Последний оценщик может быть любого типа (преобразователь, классификатор и т. д.).

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 — это сокращение для построения конвейеров; он принимает переменное количество оценщиков и возвращает конвейер, автоматически заполняя имена:

>>> 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())])

6.1.1.1.2. Доступ к шагам

Оценщики конвейера хранятся в виде списка в steps атрибуте, но могут быть доступны по индексу или имени путем индексации (с [idx]) конвейера:

>>> pipe.steps[0]
('reduce_dim', PCA())
>>> pipe[0]
PCA()
>>> pipe['reduce_dim']
PCA()

Атрибут named_steps конвейера позволяет получить доступ к шагам по имени с завершением табуляции в интерактивных средах:

>>> pipe.named_steps.reduce_dim is pipe['reduce_dim']
True

Подконвейер также может быть извлечен с использованием нотации срезов, обычно используемой для последовательностей Python, таких как списки или строки (хотя разрешен только шаг 1). Это удобно для выполнения только некоторых преобразований (или их обратных):

>>> pipe[:1]
Pipeline(steps=[('reduce_dim', PCA())])
>>> pipe[-1:]
Pipeline(steps=[('clf', SVC())])

6.1.1.1.3. Вложенные параметры

Доступ к параметрам оценщиков в конвейере можно получить с помощью <estimator>__<parameter> синтаксиса:

>>> 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()

6.1.1.2. Примечания

Вызов fit конвейера — это то же самое, что вызов fit каждого оценщика по очереди, transform ввод и передача его следующему шагу. В конвейере есть все методы, которые есть у последнего оценщика в конвейере, т. е. Если последний оценщик является классификатором, его Pipeline можно использовать в качестве классификатора. Если последний оценщик — трансформатор, опять же, конвейер.

6.1.1.3. Кэширующие трансформаторы: избегайте повторных вычислений

Установка трансформаторов может быть дорогостоящей в вычислительном отношении. С его 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()
>>> 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]]

Включение кэширования запускает клонирование трансформаторов перед установкой. Следовательно, экземпляр трансформатора, передаваемый в трубопровод, не может быть проверен напрямую. В следующем примере при доступе к PCA экземпляру pca2 будет подниматься, AttributeError поскольку pca2 трансформатор будет непригодным. Вместо этого используйте атрибут 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)

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 может быть проблематичной по одной из следующих причин:

  1. Включение статистики из тестовых данных в препроцессоры делает оценки перекрестной проверки ненадежными (известными как утечка данных ), например, в случае масштабаторов или вменения пропущенных значений.
  2. Вы можете захотеть включить параметры препроцессоров в поиск параметров .

Помогает ColumnTransformer выполнять различные преобразования для различных столбцов данных, в пределах , Pipeline что является безопасным от утечки данных и которые могут быть параметризованы. ColumnTransformer работает с массивами, разреженными матрицами и пандами 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(
...     [('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 ожидается , что на входе будет одномерный массив, поэтому столбцы были указаны как строка ('title'). Однако, OneHotEncoder поскольку большинство других преобразователей ожидают 2D-данных, в этом случае вам необходимо указать столбец как список строк (['city']).

Помимо скаляра или списка отдельных элементов, выбор столбца может быть указан как список из нескольких элементов, целочисленный массив, срез, логическая маска или с make_column_selectormake_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')])

6.1.5. Визуализация составных оценщиков

Оценщики могут отображаться в формате HTML при отображении в записной книжке jupyter. Это может быть полезно для диагностики или визуализации конвейера с помощью множества оценщиков. Эта визуализация активируется установкой display параметра в set_config:

>>> from sklearn import set_config
>>> set_config(display='diagram')   
>>> # diplays HTML representation in a jupyter context
>>> column_trans  

Пример вывода HTML можно увидеть в представлении HTML раздела конвейера преобразователя столбцов со смешанными типами. В качестве альтернативы 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))