Настройка гиперпараметров и выбор модели, как у кинозвезды

Кодируете, анализируете, выбираете и настраиваете, как если бы вы действительно знали, что делаете.

«Настройка гиперпараметров для оптимизации случайного классификатора леса» - одна из тех фраз, которые так же непринужденно звучали бы в сцене фильма, где хакеры агрессивно набирают «для получения доступа к мэйнфрейму», как это делается в статье Medium о данных. наука. На самом деле, однако, подобные фразы являются неудачным следствием объединения математических и вычислительных концепций в одной области и, что еще хуже, в одном имени. Хотя концепции в этой статье выиграют от твердого понимания фундаментального моделирования Python с использованием scikit-learn и того, как работают некоторые из этих моделей, я попытаюсь объяснить все снизу вверх, чтобы читатели всех уровней могли наслаждаться и изучать эти концепции. ; вы тоже можете звучать (и писать код) как голливудский хакер.

В этой статье я попытаюсь обратиться к:

  • Что такое гиперпараметр и чем он отличается от параметра?
  • Когда следует использовать гиперпараметры?
  • Что на самом деле делают гиперпараметры?
  • Как настроить гиперпараметры?
  • Что такое поиск по сетке?
  • Что такое конвейерная обработка?
  • Как определяются индивидуальные гиперпараметры?

Перейдите к концу, чтобы ознакомиться со всеми этими темами.

Что такое гиперпараметр?

Термин гиперпараметр появился из-за растущей распространенности машинного обучения в программировании и больших данных. Многие люди, которые начали свой путь в качестве специалистов по обработке данных или программистов, будут знать, что слово параметр определяется как значение, которое передается в функцию, так что функция выполняет операции и / или получает информацию от этих значений. Однако в машинном обучении и моделировании параметры не вводятся программистом, а разрабатываются моделью машинного обучения. Это связано с фундаментальными различиями между машинным обучением и традиционным программированием; в традиционном программировании правила и данные вводятся программистом для вывода результатов, тогда как в машинном обучении данные и результаты вводятся для вывода правил (обычно называемых параметрами в этом контексте). Этот доклад на Google I / O 2019 довольно лаконично рассматривает эту проблему в первые несколько минут.

Если модель сама генерирует параметры, было бы довольно запутанно называть то, что мы (программисты, специалисты по данным и т. Д.) Вводим, в параметры модели. Так родился термин гиперпараметр. Гиперпараметры вводятся в любую модель машинного обучения, которая генерирует свои собственные параметры, чтобы влиять на значения упомянутых сгенерированных параметров в надежде сделать модель более точной. Чуть позже в статье я покажу конкретные примеры, а также определю, что такое индивидуальные гиперпараметры.

Как определяются эти индивидуальные гиперпараметры и каковы их эффекты?

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

LogisticRegression(penalty=’l2’, dual=False, tol=0.0001, C=1.0, fit_intercept=True, intercept_scaling=1, class_weight=None, random_state=None, solver=’warn’, max_iter=100, multi_class=’warn’, verbose=0, warm_start=False, n_jobs=None, l1_ratio=None)

Как мы видим здесь, LogisticRegression() принимает 15 различных значений, которые, как мы теперь знаем, называются гиперпараметрами. Однако каждое из этих 15 значений определяется значением по умолчанию, что означает, что очень возможно, даже обычное, создать объект LogisticRegression() без указания каких-либо гиперпараметров. Это относится ко всем моделям в scikit-learn. Поэтому я потрачу время только на то, чтобы определить и объяснить некоторые из наиболее актуальных и часто изменяемых гиперпараметров для четырех распространенных методологий моделирования.

Логистическая регрессия:

  • Штраф: используется для указания метода наложения штрафов на коэффициенты не влияющих переменных.
  • Лассо (L1) выполняет выбор объекта, поскольку уменьшает коэффициент менее важного объекта до нуля.
  • Ridge (L2) все переменные включены в модель, хотя некоторые из них уменьшены. Менее затратно по вычислениям, чем лассо.
  • Оба значения штрафа ограничивают выбор решателя, как показано здесь.
  • C: обратное члену регуляризации (1 / лямбда). Он сообщает модели, насколько большие параметры штрафуются, меньшие значения приводят к большему штрафу; должно быть положительным числом с плавающей запятой.
  • Общие значения: [0,001,0,1… 10..100]
  • class_weight: позволяет сделать больший акцент на классе. Например, если распределение между классом 1 и классом 2 сильно несбалансировано, модель может соответствующим образом обработать два распределения.
  • По умолчанию все веса = 1. Веса классов можно указать в словаре.
  • «Сбалансированный» создаст веса классов, которые обратно пропорциональны частотам классов, придавая больший вес отдельным вхождениям меньших классов.

Линейная регрессия:

  • Подгонка пересечения: указывает, следует ли рассчитывать пересечение модели или установить его равным нулю.
  • Если false, перехват для линии регрессии будет 0.
  • Если это правда, модель рассчитает точку пересечения.
  • Нормализовать: указывает, следует ли нормализовать данные для модели с помощью нормы L2.

SVM

  • C: обратное члену регуляризации (1 / лямбда). Он сообщает модели, насколько большие параметры штрафуются, меньшие значения приводят к большему штрафу; должно быть положительным числом с плавающей запятой.
  • Более высокое значение C приведет к тому, что модель будет меньше ошибочно классифицироваться, но с гораздо большей вероятностью приведет к переобучению.
  • Хороший диапазон значений: [0,001, 0,01, 10, 100, 1000…]
  • class_weight: установите для параметра класса i значение class_weight [i] * C.
  • Это позволяет сделать больший акцент на классе. Например, если распределение между классом 1 и классом 2 сильно несбалансировано, модель может правильно обработать два распределения.
  • По умолчанию все веса = 1. Веса классов можно указать в словаре.
  • «Сбалансированный» создаст веса классов, которые обратно пропорциональны частотам классов, придавая больший вес отдельным вхождениям меньших классов.

K-Ближайшие соседи

  • n_neighbors: определяет количество соседей, используемых при вычислении алгоритма ближайших соседей.
  • Хороший диапазон значений: [2,4,8,16]
  • p: показатель мощности при вычислении метрики Минковского - довольно сложная с математической точки зрения тема. При оценке моделей обычно достаточно просто попробовать и 1, и два.
  • Используйте значение 1, чтобы рассчитать расстояние до Манхэттена.
  • Используйте значение 2 для вычисления евклидова расстояния (по умолчанию)

Случайный лес

  • n_estimators: устанавливает количество деревьев решений, которые будут использоваться в лесу.
  • По умолчанию 100
  • Хороший диапазон значений: [100, 120, 300, 500, 800, 1200].
  • max_depth: установите максимальную глубину дерева.
  • Если не установлен, то колпачок отсутствует. Дерево будет расти, пока все листья не станут чистыми.
  • Ограничение глубины хорошо подходит для обрезки деревьев, чтобы предотвратить чрезмерную подгонку зашумленных данных.
  • Хороший диапазон значений: [5, 8, 15, 25, 30, None].
  • min_samples_split: минимальное количество выборок, необходимое для разделения (дифференциации) во внутреннем узле.
  • По умолчанию 2
  • Хороший диапазон значений: [1,2,5,10,15,100]
  • min_samples_leaf: минимальное количество выборок, необходимое для создания листового (решающего) узла.
  • По умолчанию 1. Это означает, что точка разделения на любой глубине будет разрешена только в том случае, если есть по крайней мере 1 образец для каждого пути.
  • Хороший диапазон значений: [1,2,5,10]
  • max_features: укажите количество функций, которые следует учитывать для наилучшего разделения узлов.
  • По умолчанию установлено «авто», что означает, что квадратный корень из числа функций используется для каждого разбиения в дереве.
  • «Нет» означает, что для каждого разделения используются все функции.
  • Каждое дерево решений в случайном лесу обычно использует случайное подмножество функций для разделения.
  • Хороший диапазон значений: [log2, sqrt, auto, None].

Как можно настроить гиперпараметры и что они на самом деле делают?

Чтобы ответить на оба этих вопроса, давайте рассмотрим пример с использованием классического набора данных UC Irvine Iris.

Сначала мы загрузим набор данных и импортируем некоторые из используемых нами пакетов:

# import packages
import numpy as np
from sklearn import linear_model, datasets
from sklearn.model_selection import GridSearchCV
from sklearn.linear_model import LogisticRegression 
from sklearn.ensemble import RandomForestClassifier 
from sklearn.model_selection import GridSearchCV 
from sklearn.pipeline import Pipeline
# Loading dataset
iris = datasets.load_iris()
features = iris.data
target = iris.target

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

logistic.fit(features, target)
print(logistic.score(features, target))

Выход:

0.96

Теперь давайте попробуем некоторые методы настройки гиперпараметров, чтобы посмотреть, сможем ли мы повысить точность нашей модели.

Что такое поиск по сетке?

Сетевой поиск - это метод, с помощью которого мы создаем наборы возможных значений гиперпараметров для каждого гиперпараметра, а затем сравниваем их друг с другом в сетке. Например, если я хочу протестировать логистическую регрессию со значениями [L1, L2] и значениями C как [1,2], метод GridSearchCV() будет проверять L1 с C=1, затем L1 с C=2, затем L2 с обоими значениями C, создавая сетку 2x2 и всего четыре комбинации. Давайте рассмотрим пример без текущего набора данных. Параметр verbose указывает, будет ли функция печатать информацию по мере выполнения, а параметр cv относится к сверткам перекрестной проверки. Полную документацию для GridSearchCV() можно найти здесь.

# Create range of candidate penalty hyperparameter values
penalty = ['l1', 'l2']
# Create range of candidate regularization hyperparameter values C
# Choose 10 values, between 0 and 4
C = np.logspace(0, 4, 10)
# Create dictionary hyperparameter candidates
hyperparameters = dict(C=C, penalty=penalty)
# Create grid search, and pass in all defined values
gridsearch = GridSearchCV(logistic, hyperparameters, cv=5, verbose=1) 
# the verbose parameter above will give output updates as the calculations are complete. 
# select the best model and create a fit
best_model = gridsearch.fit(features, target)

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

print('Best Penalty:', best_model.best_estimator_.get_params(['penalty']) 
print('Best C:', best_model.best_estimator_.get_params()['C'])
print("The mean accuracy of the model is:",best_model.score(features, target))

Выход:

Best Penalty: l1 
Best C: 7.742636826811269
The mean accuracy of the model is: 0.98

Это повышение точности на 0,02 при использовании той же модели и небольшом изменении гиперпараметров. Попробуйте поэкспериментировать с разными наборами гиперпараметров, добавить их в dict гиперпараметров и снова запустить GridSearchCV(). Обратите внимание, как добавление большого количества гиперпараметров быстро увеличивает время вычислений.

Что такое конвейерная обработка?

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

# Create a pipeline
pipe = Pipeline([("classifier", RandomForestClassifier())])
# Create dictionary with candidate learning algorithms and their hyperparameters
search_space = [
                {"classifier": [LogisticRegression()],
                 "classifier__penalty": ['l2','l1'],
                 "classifier__C": np.logspace(0, 4, 10)
                 },
                {"classifier": [LogisticRegression()],
                 "classifier__penalty": ['l2'],
                 "classifier__C": np.logspace(0, 4, 10),
                 "classifier__solver":['newton-cg','saga','sag','liblinear'] ##This solvers don't allow L1 penalty
                 },
                {"classifier": [RandomForestClassifier()],
                 "classifier__n_estimators": [10, 100, 1000],
                 "classifier__max_depth":[5,8,15,25,30,None],
                 "classifier__min_samples_leaf":[1,2,5,10,15,100],
                 "classifier__max_leaf_nodes": [2, 5,10]}]
# create a gridsearch of the pipeline, the fit the best model
gridsearch = GridSearchCV(pipe, search_space, cv=5, verbose=0,n_jobs=-1) # Fit grid search
best_model = gridsearch.fit(features, target)

Обратите внимание, сколько времени требуется для выполнения этой функции. В другой статье я расскажу о том, как сократить время выполнения и выбрать эффективные гиперпараметры, а также о том, как комбинировать RandomizedSearchCV() с GridSearchCV. После запуска метода давайте проверим результаты.

print(best_model.best_estimator_)
print("The mean accuracy of the model is:",best_model.score(features, target))

Выход:

Pipeline(memory=None, steps=[('classifier', LogisticRegression(C=7.742636826811269, class_weight=None, dual=False, fit_intercept=True,intercept_scaling=1, l1_ratio=None,                                     max_iter=100, multi_class='warn', n_jobs=None, penalty='l1',                                     random_state=None, solver='warn', tol=0.0001, verbose=0, warm_start=False))], verbose=False) 
The mean accuracy of the model is: 0.98

Согласно нашему поиску по конвейеру, LogisticRegression() с указанными гиперпараметрами работает лучше, чем RandomForestClassifier() с любым из указанных гиперпараметров. Интересно!

Итак, мы использовали конвейерный метод, чтобы все это произошло, но что он на самом деле делает и почему мы передали RandomForestClassifier()?

Метод конвейера позволяет нам передавать методы предварительной обработки, а также алгоритм, который мы хотели бы использовать для создания модели с данными. В этом простом примере мы пропустили этап предварительной обработки, но по-прежнему вводим модель. Вводимый нами алгоритм - это просто алгоритм, используемый для создания экземпляра объекта конвейера, но он будет заменен содержимым нашей переменной search_space, которую мы создаем для передачи в нашу GridSearchCV() позже. Упрощенный пост, посвященный только конвейеру, можно найти здесь.

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

Резюме

Что такое гиперпараметр и чем он отличается от параметра?

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

Когда следует использовать гиперпараметры?

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

Что на самом деле делают гиперпараметры?

Просто они меняют подходы к поиску параметров модели. Отдельные определения можно найти в статье выше.

Как настроить гиперпараметры?

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

Что такое поиск по сетке?

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

Что такое конвейерная обработка?

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

Наконец, ниже приведены некоторые функции, которые могут выполнять несколько различных типов настройки гиперпараметров, просто передавая аргументы. Блокнот Google Colab, содержащий весь код, использованный в этой статье, также можно найти здесь. Используя эти функции, вы можете эффективно выполнить настройку гиперпараметров всего в одной строке!

# # # Hyperparameter tuning and model selection
import numpy as np
from sklearn import linear_model
from sklearn import datasets
from sklearn.linear_model import LogisticRegression 
from sklearn.ensemble import RandomForestClassifier 
from sklearn.model_selection import GridSearchCV 
from sklearn.pipeline import Pipeline
from sklearn.model_selection import RandomizedSearchCV
from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestRegressor
def perform_gridsearch_log(features, labels,
                       log_params = {'penalty': ['l1', 'l2'], 'C': np.logspace(0, 4, 10)},
                       cv=5, verbose = 1):
  import numpy as np
  from sklearn import linear_model, datasets
  from sklearn.model_selection import GridSearchCV
  
  global best_model
  logistic = linear_model.LogisticRegression()
  penalty = log_params['penalty']
  C = log_params['C']
  hyperparameters = dict(C=C, penalty=penalty)
gridsearch = GridSearchCV(logistic, hyperparameters, cv=cv, verbose=verbose) # Fit grid search
  best_model = gridsearch.fit(features, target)
  
  print(best_model.best_estimator_)
  print("The mean accuracy of the model is:",best_model.score(features, labels))
def rand_forest_rand_grid(features, labels, n_estimators = [int(x) for x in np.linspace(start = 200, stop = 2000, num = 10)],
                                           max_features = ['auto', 'sqrt'],
                                           max_depth = [int(x) for x in np.linspace(10, 110, num = 11)],
                                           min_samples_split = [2, 5, 10],
                                           min_samples_leaf = [1, 2, 4], bootstrap = [True, False]):
  
  max_depth.append(None)
  global best_model
 
  random_grid = {'n_estimators': n_estimators,
                 'max_features': max_features,
                 'max_depth': max_depth,
                 'min_samples_split': min_samples_split,
                 'min_samples_leaf': min_samples_leaf,
                 'bootstrap': bootstrap}
  
  rf = RandomForestRegressor()
  
  rf_random = RandomizedSearchCV(estimator = rf, param_distributions = random_grid, n_iter = 100, cv = 3, verbose=1, random_state=42, n_jobs = -1)
  
  best_model = rf_random.fit(features, labels)
  print(best_model.best_estimator_)
  print("The mean accuracy of the model is:",best_model.score(features, labels))
def rand_forest_grid_search(features, labels, n_estimators = [int(x) for x in np.linspace(start = 200, stop = 2000, num = 10)],
                                           max_features = ['auto', 'sqrt'],
                                           max_depth = [int(x) for x in np.linspace(10, 110, num = 11)],
                                           min_samples_split = [2, 5, 10],
                                           min_samples_leaf = [1, 2, 4], bootstrap = [True, False]):
  param_grid = {'n_estimators': n_estimators,
                 'max_features': max_features,
                 'max_depth': max_depth,
                 'min_samples_split': min_samples_split,
                 'min_samples_leaf': min_samples_leaf,
                 'bootstrap': bootstrap}
  
  global best_model
  rf = RandomForestRegressor()
  
  grid_search = GridSearchCV(estimator = rf, param_grid = param_grid, 
                          cv = 3, n_jobs = -1, verbose = 1)
best_model = grid_search.fit(train_features, train_labels)
  print(best_model.best_estimator_)
  print("The mean accuracy of the model is:",best_model.score(features, labels))
def execute_pipeline(features,labels, search_space=[
                {"classifier": [LogisticRegression()],
                 "classifier__penalty": ['l2','l1'],
                 "classifier__C": np.logspace(0, 4, 10)
                 },
                {"classifier": [LogisticRegression()],
                 "classifier__penalty": ['l2'],
                 "classifier__C": np.logspace(0, 4, 10),
                 "classifier__solver":['newton-cg','saga','sag','liblinear'] ##This solvers don't allow L1 penalty
                 },
                {"classifier": [RandomForestClassifier()],
                 "classifier__n_estimators": [10, 100, 1000],
                 "classifier__max_depth":[5,8,15,25,30,None],
                 "classifier__min_samples_leaf":[1,2,5,10,15,100],
                 "classifier__max_leaf_nodes": [2, 5,10]}], cv=5, verbose=0, n_jobs=-1):
global best_model
  
  pipe = Pipeline([("classifier", RandomForestClassifier())])
  
  gridsearch = GridSearchCV(pipe, search_space, cv=cv, verbose=verbose,n_jobs=n_jobs) # Fit grid search
  best_model = gridsearch.fit(features, labels)
  print(best_model.best_estimator_)
  print("The mean accuracy of the model is:",best_model.score(features, labels))

Спасибо за прочтение!