Статьи
December 12, 2022

Как создать пользовательский трансформер данных с помощью sklearn?

Sklearn — это библиотека машинного обучения для Python, которая в том числе предоставляет широкий функционал по преобразованию данных для разных задач.

В процессе очистки и подготовки данных нам часто приходится делать такие простые операции, как удаление столбцов и пр. Зачем для этого каждый раз писать код с нуля? sklearn предоставляет механизм стандартизации таких преобразований для любых данных и поможет нам создать унифицированный конвейер из нужных действий.

Кроме того, при оценке моделей с использованием кросс-валидации, преобразования данных в исходном датасете не должны приводить к утечке данных. И эту проблему решит создание пользовательского трансформера, а делать мы это будем при помощи класса FunctionTransformer. В нём пропишем сразу все необходимые действия над данными, а потом просто будем его использовать, как и любой другой трансформер в sklearn .

Создание пользовательского трансформера

Всё, что нам нужно сделать для разработки собственного трансформера — реализовать несколько шагов:

  • инициализировать класс transformer;
  • классы BaseEstimator и TransformerMixin из модулей sklearn.base должны быть дочерними по отношению к нему;
  • должны быть объявлены методы класса fit() и transform(). Чтобы они без проблем встраивались в пайплайн, у них обязательно должны быть параметры X и y, а функция transform() в качестве выходных данных должна возвращать pandas.DataFrame или массив Numpy.
from numpy.random import randint
from sklearn.base import BaseEstimator, TransformerMixin

class BasicTransformer(BaseEstimator, TransformerMixin):
    def fit(self, X, y=None):
        return self
    def transform(self, X, y=None):
        X["cust_num"] = randint(0, 10, X.shape[0])
        return X
 
df_basic = pd.DataFrame({"a": [1, 2, 3], "b": [4, 5, 6], "c": [7, 8, 9]})
pipe = Pipeline(
    steps=[
        ("use_custom_transformer", basicTransformer())
    ]
)
transformed_df = pipe.fit_transform(df_basic)

df_basic 

Вот и всё, в пайплайне будут реализованы все операции, прописанные в этом классе. Полученный датафрейм будет выглядеть следующим образом:

Давайте создадим ещё один пользовательский трансформер, применим его к набору данных и даже спрогнозируем некоторые значения. Начнём с импорта нужных библиотек. Они понадобятся нам для выполнения дальнейших операций.

import numpy as np
import pandas as pd
from sklearn.metrics import mean_squared_error,r2_score
from sklearn.pipeline import FeatureUnion, Pipeline, make_pipeline
from sklearn.base import BaseEstimator, TransformerMixin
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split

Чтение, подготовка и анализ данных

Будем работать с датасетом из области страхования. В качестве прогнозируемого показателя взята стоимость страховки в зависимости от различных фич.

df = pd.read_csv("/content/insurance.csv")
df_util = pd.get_dummies(data = df,
    columns=["sex", "smoker", "region"],
    drop_first = True)

Приведённый выше график отображает распределение величины страховых взносов в зависимости от индекса массы тела (ИМТ) клиентов с разделением клиентов по возрасту.

Разобьём данные на обучающие и тестовые в соответствии со стандартным соотношением 70:30.

X = df_util.drop(["charges"], axis = 1)
y = df_util["charges"]
X_train, X_test, y_train, y_test = train_test_split(X, y,
    test_size=0.30, random_state=42)

Теперь создадим непосредственно пользовательский трансформер и используем его для преобразования обучающей выборки.

class CustomTransformer(BaseEstimator, TransformerMixin):
  def __init__(self, feature_name, additional_param = "SM"):  
    print("\n...intializing\n")
    self.feature_name = feature_name
    self.additional_param = additional_param
 
  def fit(self, X, y = None):
    print("\nfiting data...\n")
    print(f"\n \U0001f600  {self.additional_param}\n")
    return self
 
  def transform(self, X, y = None):
    print("\n...transforming data \n")
    X_ = X.copy()
    X_[self.feature_name] = np.log(X_[self.feature_name])
    return X
    
print("creating second pipeline...")
pipe2 = Pipeline(steps=[
                       ("experimental_trans", CustomTransformer("bmi")),
                       ("linear_model", LinearRegression())
])
 
print("fiting pipeline 2")
pipe2.fit(X_train, y_train)
preds2 = pipe2.predict(X_test)
print(f"RMSE: {np.sqrt(mean_squared_error(y_test, preds2))}\n") 

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

В этом конкретном трансформере пользователь может указывать названия столбцов, над которыми необходимо выполнить преобразования через передачу аргументов.

Далее строим модель линейной регрессии с использованием этого трансформера. Еще он логарифмирует значения, что часто может повысить качество моделей линейной регрессии.

Выше представлен график, на котором отражены фактические и прогнозируемые страховые взносы. Можно заметить, что линия регрессии вполне адекватно объясняет их взаимосвязь.

Вот мы уже можем создавать пользовательские трансформеры и даже использовать их для прогнозирования данных. Но что, если мы захотим настроить трансформеры, предлагаемые sklearn? Давайте модифицируем OrdinalEncoder:

from sklearn.preprocessing import OrdinalEncoder

class CustEncoder(OrdinalEncoder):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
    def transform(self, X, y=None):
        transformed_data = super().transform(X)
        encoded_data = pd.DataFrame(transformed_data, 
        columns=self.feature_names_in_)
        return encoded_data
        
data = df[["sex","smoker","region"]]
enc = CustEncoder(dtype=int)
new_data = enc.fit_transform(data)
new_data[:8]

С помощью super() мы можем настраивать любые предопределённые трансформеры в соответствии с нашими потребностями.

Заключение

Пользовательские трансформеры позволяют более гибко и удобно реализовывать подготовку данных, в т.ч. инкапсулировать её в отдельную сущность. Такой подход делает код более понятным. А помимо создания новых трансформеров, можно модифицировать уже существующие из библиотеки sklearn. Попробуйте, пригодиться ^_^.

PythonTalk в Telegram

Чат PythonTalk в Telegram

Предложить материал | Поддержать канал

Источник: Analytics India Magazine