Библиотеки и сервисы AutoML вошли в мир машинного обучения. Для дата-сайентиста это очень полезные инструменты, но иногда они должны быть адаптированы к потребностям бизнес-контекста, в котором работает дата-сайентист. Вот почему вам нужно создать свою собственную библиотеку AutoML. В преддверии старта нового потока курса «Машинное обучение» мы делимся материалом, в котором описано, как это сделать на Python.





Что должна делать библиотека AutoML?


Библиотека AutoML — это любая часть программного обеспечения, автоматизирующая некоторые самые сложные (и скучные) части конвейера машинного обучения. Применение AutoML ускорит процесс машинного обучения и поможет и��бежать ошибок. Библиотека AutoML должна автоматизировать такие действия:

  • Заполнение пустыми значениями.
  • Кодирование категориальных переменных.
  • Масштабирование числовых переменных.
  • Подбор признаков.
  • Выбор модели.
  • Настройка гиперпараметров.

Идея заключается в том, что библиотека AutoML пробует все комбинации параметров, измеряя среднюю производительность модели с помощью k-кратной кросс-валидации и выбирая лучший набор значений. Итак, это процедура оптимизации в сетке настроек.

Подход


Здесь я расскажу о конвейере классификации с такой сеткой настроек:

  • Заполнение пустых числовых переменных средним или медианным значением.
  • Заполнение категориальных переменных наиболее часто встречающимся значением.
  • Масштабирование: нормализация, стандартизация или над ёжное масштабирование.
  • Подбор признаков на основе фильтров с помощью ANOVA.
  • Используемые модели: логистическая регрессия, KNN, случайный лес, градиентный бустинг, бинарное дерево решений, SVM с линейным ядром.

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

Мы перевели нашу задачу в задачу гиперпараметрической оптимизации, которую мы мож��м решить. Пространство гиперпараметров конвейера очень велико, поэтому воспользуемся случайным поиском, чтобы найти лучший набор значений таких гиперпараметров. Наш объект будет принимать фрейм данных Pandas на вход для обучения, а метод “обучения” выполнит необходимую оптимизацию, чтобы найти лучшую модель и лучшие настройки для фазы предварительной обработки. Посмотрим на код.

Код


Мы создадим объект под названием MyAutoMLClassifier и будем обучать и тестировать его на наборе данных о раке молочной железы. Вы можете найти весь код в моём репозитории на GitHub.
Импортируем библиотеки:

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer, make_column_selector
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler, OneHotEncoder
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.model_selection import RandomizedSearchCV
from sklearn.pipeline import Pipeline
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.svm import LinearSVC,SVC
from sklearn.datasets import load_breast_cancer
from sklearn.metrics import balanced_accuracy_score

Теперь мы можем приступить к определению класса MyAutoMLClassifier. Его конструктор будет принимать скоринговую функцию, которая будет использоваться в k-кратной кросс-валидации и в количестве итераций случайного поиска. В этом примере их значениями по умолчанию будут «balanced accuracy» — сбалансированная точность и 50.

class MyAutoMLClassifier:
  def __init__(self, 
    scoring_function = 'balanced_accuracy', 
    n_iter = 50):
    self.scoring_function = scoring_function
    self.n_iter = n_iter

Теперь мы можем начать писать метод «обучения» — самый важный метод. Во-первых, мы должны определить различные значения категориальных переменных, чтобы применить унитарное кодирование.

def fit(self,X,y):
    X_train = X
    y_train = y

    categorical_values = []

    cat_subset = X_train.select_dtypes(include = ['object','category','bool'])

    for i in range(cat_subset.shape[1]):
      categorical_values.append(list(cat_subset.iloc[:,i].dropna().unique()))

Теперь для категориальных переменных нужно определить конвейер предварительной обработки. Этот конвейер заменит пустые значения, используя наиболее часто встречающееся значение, и унитарно закодирует новые значения. В то же время мы собираемся определить конвейер для числовых переменных, который будет очищен в соответствии с определяемым позже и масштабирован в соответствии с преобразователем масштаба, который мы установим в части случайного поиска. Всё это, наконец, включается в настройки ColumnTransformer, который выполнит всю предварительную обработку.

num_pipeline = Pipeline([
   ('cleaner',SimpleImputer()),
   ('scaler',StandardScaler())
])

cat_pipeline = Pipeline([
    ('cleaner',SimpleImputer(strategy = 'most_frequent')),
    ('encoder',OneHotEncoder(sparse = False, categories=categorical_values))
])


preprocessor = ColumnTransformer([
  ('numerical', num_pipeline, make_column_selector(dtype_exclude=['object','category','bool'])),
  ('categorical', cat_pipeline, make_column_selector(dtype_include=['object','category','bool']))
])

Наконец, мы должны определить конвейер ML, который строится на этапе предварительной обработки, подбора признаков и самой модели. Сейчас мы можем установить модель в LogisticRegression, она будет изменена позже случайным поиском.

model_pipeline_steps = []
model_pipeline_steps.append(('preprocessor',preprocessor))
model_pipeline_steps.append(('feature_selector',SelectKBest(f_classif,k='all')))
model_pipeline_steps.append(('estimator',LogisticRegression()))
model_pipeline = Pipeline(model_pipeline_steps)

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

total_features = preprocessor.fit_transform(X_train).shape[1]

optimization_grid = []

Теперь мы можем начать добавлять модели в нашу сетку оптимизации. Начнём с логистической регрессии:

# Logistic regression
optimization_grid.append({
    'preprocessor__numerical__scaler':[RobustScaler(),StandardScaler(),MinMaxScaler()],
    'preprocessor__numerical__cleaner__strategy':['mean','median'],
    'feature_selector__k': list(np.arange(1,total_features,5)) + ['all'],
    'estimator':[LogisticRegression()]
})


Как мы видим, создаётся объект, который изменит масштабирование между RobustScaler, StandardScaler �� MinMaxscaler. Затем будет изменена стратегия очистки между средним и медианным значениями и выбраны объекты от 1 до общего числа объектов с шагом 5. Наконец, сама модель установлена. Случайный поиск будет проверять случайные комбинации этой сетки, в поиске той, которая максимизирует показатели производительности при кросс-валидации.

Теперь мы можем добавить другие модели с их собственными гиперпараметрами и нуждами в смысле предварительной обработки. Например, деревья не требуют никакого масштабирования, но SVM — да. Мы можем добавить столько моделей, сколько захотим, оптимизируя их гиперпараметры в одной сетке.

 # K-nearest neighbors
optimization_grid.append({
    'preprocessor__numerical__scaler':[RobustScaler(),StandardScaler(),MinMaxScaler()],
    'preprocessor__numerical__cleaner__strategy':['mean','median'],
    'feature_selector__k': list(np.arange(1,total_features,5)) + ['all'],
    'estimator':[KNeighborsClassifier()],
    'estimator__weights':['uniform','distance'],
    'estimator__n_neighbors':np.arange(1,20,1)
})

# Random Forest
optimization_grid.append({
    'preprocessor__numerical__scaler':[None],
    'preprocessor__numerical__cleaner__strategy':['mean','median'],
    'feature_selector__k': list(np.arange(1,total_features,5)) + ['all'],
    'estimator':[RandomForestClassifier(random_state=0)],
    'estimator__n_estimators':np.arange(5,500,10),
    'estimator__criterion':['gini','entropy']
})


# Gradient boosting
optimization_grid.append({
    'preprocessor__numerical__scaler':[None],
    'preprocessor__numerical__cleaner__strategy':['mean','median'],
    'feature_selector__k': list(np.arange(1,total_features,5)) + ['all'],
    'estimator':[GradientBoostingClassifier(random_state=0)],
    'estimator__n_estimators':np.arange(5,500,10),
    'estimator__learning_rate':np.linspace(0.1,0.9,20),
})



# Decision tree
optimization_grid.append({
    'preprocessor__numerical__scaler':[None],
    'preprocessor__numerical__cleaner__strategy':['mean','median'],
    'feature_selector__k': list(np.arange(1,total_features,5)) + ['all'],
    'estimator':[DecisionTreeClassifier(random_state=0)],
    'estimator__criterion':['gini','entropy']
})

# Linear SVM
optimization_grid.append({
    'preprocessor__numerical__scaler':[RobustScaler(),StandardScaler(),MinMaxScaler()],
    'preprocessor__numerical__cleaner__strategy':['mean','median'],
    'feature_selector__k': list(np.arange(1,total_features,5)) + ['all'],
    'estimator':[LinearSVC(random_state = 0)],
    'estimator__C': np.arange(0.1,1,0.1),

})

Итак, мы ищем наилучшее сочетание стратегии очистки, процедуры масштабирования, набора признаков, значений модели и гиперпараметров — всё в одной и той же процедуре поиска. Это ядро любой библиотеки AutoML и может быть расширено по нашему желанию.

Теперь у нас есть завершённая сетка оптимизации, так что мы можем, наконец, применить случайный поиск, чтобы найти лучшие параметры конвейера и сохранить результаты в свойствах нашего объекта. Случайный поиск будет применять 5-кратную кросс-валидацию с помощью функции скоринга и количества итераций, выбранных в конструкторе класса.

search = RandomizedSearchCV(
      model_pipeline,
      optimization_grid,
      n_iter=self.n_iter,
      scoring = self.scoring_function, 
      n_jobs = -1, 
      random_state = 0, 
      verbose = 3,
      cv = 5
    )

search.fit(X_train, y_train)
self.best_estimator_ = search.best_estimator_
self.best_pipeline = search.best_params_

Метод обучения завершён. Теперь мы можем добавить методы «predic» и «predict_proba», как и любую другую модель sklearn, и наш MyAutoMLClassifier закончен.

def predict(self,X,y = None):
  return self.best_estimator_.predict(X)

def predict_proba(self,X,y = None):
  return self.best_estimator_.predict_proba(X)

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

d = load_breast_cancer()
y = d['target']
X = pd.DataFrame(d['data'],columns = d['feature_names'])

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=42)

model = MyAutoMLClassifier()
model.fit(X_train,y_train)

С помощью всего лишь одной строки кода мы делаем все сложные вещи с помощью AutoML. После обучения модели мы можем рассчитать сбалансированную точность в тестовом наборе и посмотреть, какие параметры модели и предварительной обработки были выбраны:



Мы используем классификатор дерева градиентного бустинга со всеми функциями и численную стратегию очистки, основанную на медианном значении. Скорость обучения модели равна 0,1, а число обучающихся на данных объектов — 125. Это результат применения AutoML.

Заключение


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

image



Рекомендуемые статьи