Использование Optuna для поиска оптимальной комбинации гиперпараметров

Что такое настройка гиперпараметров?

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

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

Это может показаться ошеломляющим - как узнать, какая комбинация гиперпараметров приведет к наиболее точной модели? Ручная настройка (поиск наилучшей комбинации) может занять много времени и покрыть небольшое пространство для образца. Один из подходов, который будет рассмотрен здесь, - это использование Optuna для автоматизации некоторых из этих работ. Вместо того, чтобы вручную проверять комбинации, можно указать диапазоны гиперпараметров, и Optuna проводит исследование, чтобы определить оптимальную комбинацию с учетом временных ограничений.

Обзор набора данных

Чтобы продемонстрировать настройку Optuna и гиперпараметров, мы будем использовать набор данных, содержащий рейтинги вин и цены из Kaggle. Учитывая некоторые входные характеристики бутылки красного вина, такие как регион, точки и сорт, насколько точно мы можем предсказать цену вина, используя настройку гиперпараметров?

Несколько строк кода для загрузки в наши данные и разделения обучения / тестирования:

# Read in data from local csv
df = pd.read_csv('winemag-data-130k-v2.csv')

# Choose just a few features for demonstration, infer categorical features
feature_cols = ['country', 'points', 'province', 'region_1', 'region_2', 'taster_name', 'variety', 'winery']
cat_features = [col for col in feature_cols if df[col].dtype == 'object']
for col in cat_features:
    df[col] = df[col].fillna('Other')
target_col = 'price'

# Train test split
train_df, test_df = train_test_split(df, test_size=0.3, shuffle=False)

train_x = train_df.loc[:, feature_cols]
train_y = train_df.loc[:, target_col]

test_x = test_df.loc[:, feature_cols]
test_y = test_df.loc[:, target_col]

Модельное обучение

Базовые модели

Чтобы понять, поможет ли наша оптимизация гиперпараметров, мы обучим пару базовых моделей. Первый - это простая средняя цена. Использование этой методологии приводит к средней абсолютной процентной ошибке 79% - не очень хорошо, надеюсь, некоторое моделирование машинного обучения может улучшить наши прогнозы!

Второй базовый план - это обучение нашей модели (с использованием библиотеки Catboost) с параметрами по умолчанию. Ниже приведены несколько строк этого кода. Это превосходит наше базовое прогнозирование простого среднего, но можем ли мы добиться большего за счет дальнейшей оптимизации?

# Train a model with default parameters and score
model = CatBoostRegressor(loss_function = 'RMSE', eval_metric='RMSE', verbose=False, cat_features=cat_features, random_state=42)
default_train_score = np.mean(eda.cross_validate_custom(train_x, train_y, model, mean_absolute_percentage_error))
print('Training with default parameters results in a training score of {:.3f}.'.format(default_train_score))
Output: Training with default parameters results in a training score of 0.298.

Модель, оптимизированная для гиперпараметров

Настройка исследования оптимизации

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

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

def objective(trial):

    # Define parameter dictionary used to build catboost model
    params = {
        'loss_function': 'RMSE',
        'eval_metric': 'RMSE',
        'verbose': False,
        'cat_features': cat_features,
        'random_state': 42,
        'learning_rate': trial.suggest_float('learning_rate', 0.001, 0.2),
        'depth': trial.suggest_int('depth', 2, 12),
        'n_estimators': trial.suggest_int('n_estimators', 100, 1000, step=50)
    }
    
    # Build and score model
    clf = CatBoostRegressor(**params)
    score = np.mean(eda.cross_validate_custom(train_x, train_y, clf, mean_absolute_percentage_error))

    return score

Просмотр результатов

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

# Grab best trial from optuna study
best_trial_optuna = study.best_trial
print('Best score {:.3f}. Params {}'.format(best_trial_optuna.value, best_trial_optuna.params))
Output: Best score 0.288. Params {'learning_rate': 0.0888813729642258 'depth': 12 'n_estimators': 800}

По сравнению с параметрами по умолчанию

Быстрое сравнение результатов тренировки с нашим первоначальным запуском с параметрами по умолчанию показывает хорошие признаки. Вы увидите, что оптимизированная модель лучше подходит для обучения (в данном случае оценка - это процентная ошибка, поэтому меньше = лучше).

# Compare best trial vs. default parameters
print('Default parameters resulted in a score of {:.3f} vs. Optuna hyperparameter optimization score of {:.3f}.'.format(default_train_score, best_trial_optuna.value))
Output: Default parameters resulted in a score of 0.298 vs. Optuna hyperparameter optimization score of 0.288.

Анализ тенденций оптимизации

Одна интересная вещь - график параллельных координат. Это позволяет нам просматривать испытания и анализировать гиперпараметры на предмет потенциальных тенденций. Мы можем захотеть запустить новое исследование после просмотра результатов, если мы найдем интересные оптимизации, позволяющие искать дополнительные пространства гиперпараметров.

# Visualize results to spot any hyperparameter trends
plot_parallel_coordinate(study)

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

Сравнение результатов тестирования

Последний шаг - сравнение результатов тестирования. Первый шаг - посмотреть, как наша простая базовая линия среднего прогноза работает на тестовом наборе.

# Run baseline model (default predicting mean)
preds_baseline = np.zeros_like(test_y)
preds_baseline = np.mean(train_y) + preds_baseline
baseline_model_score = mean_absolute_percentage_error(test_y, preds_baseline)
print('Baseline score (mean) is {:.2f}.'.format(baseline_model_score))
Output: Baseline score (mean) is 0.79.

Следующим шагом является просмотр результатов тестирования нашей модели гиперпараметров по умолчанию:

# Rerun default model on full training set and score on test set
simple_model = model.fit(train_x, train_y)
simple_model_score = mean_absolute_percentage_error(test_y, model.predict(test_x))
print('Default parameter model score is {:.2f}.'.format(simple_model_score))
Output: Default parameter model score is 0.30.

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

# Rerun optimized model on full training set and score on test set
params = best_trial_optuna.params
params['loss_function'] = 'RMSE'
params['eval_metric'] ='RMSE'
params['verbose'] = False
params['cat_features'] = cat_features
params['random_state'] = 42
opt_model = CatBoostRegressor(**params)
opt_model.fit(train_x, train_y)
opt_model_score = mean_absolute_percentage_error(test_y, opt_model.predict(test_x))
print('Optimized model score is {:.2f}.'.format(opt_model_score))
Output: Optimized model score is 0.29.

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

Все примеры и файлы доступны на Github.

Первоначально опубликовано на https://datastud.dev.