машинное обучение
September 4, 2021

Настраиваем кондиции модели с Optuna

В этой статье я расскажу об эффективном способе прохождения одного из необходимых этапов создания модели машинного обучения - подборе ее гиперпараметров. При этом остановимся на гибком интерфейсе решения этой задачи (Ask-and-Tell), реализованном в библиотеке Optuna.

Демонстрацию будем проводить на примере тренировочного набора данных, созданного функцией make_regression библиотеки scikit-learn. В качестве алгоритма используется LightGBM, метрикой качества выступает mape на валидационной выборке. Интерфейс Ask-and-Tell предполагает создание объекта study вызовом функции optuna.create_study, который управляет оптимизацией и хранит ее результаты. Затем посредством вызовов методов ask и tell объекта study происходит инициализация каждой попытки (trial) одним из наборов параметров и возврат результата в study. Итоговые параметры модели становятся доступными по окончании оптимизации в атрибуте best_params. Ниже представлен код, реализующий описанную логику:

import optuna
import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_percentage_error
from sklearn.datasets import make_regression

data, target = make_regression(n_samples=10000, n_features=12, n_informative=12, n_targets=1,
                                random_state=1)
X_tr, X_val, y_tr, y_val = train_test_split(data, target, test_size=0.2)

study = optuna.create_study()
n_trials = 100
for _ in range(n_trials):
    trial = study.ask()
    params={'verbosity':-1,
            'lambda_l2':trial.suggest_float('lambda_l2', 0.01, 0.2),
            'max_depth':trial.suggest_int('max_depth', 5, 55, step=5),
            'num_leaves':trial.suggest_int('num_leaves', 5, 45, step=5),
            'learning_rate':trial.suggest_float('learning_rate',0.05, 0.25, step=0.05),
            'bagging_fraction': trial.suggest_float('bagging_fraction', 0.7, 0.9 , step=0.1)
            }

    reg = lgb.LGBMRegressor(**params)
    reg.fit(X_tr, y_tr)
    y_pr = reg.predict(X_val)
    mape = mean_absolute_percentage_error(y_val, y_pr)

    study.tell(trial, mape)

Теперь сделаем это же, но на примере кросс-валидации и сравним результаты на отложенной выборке:

import optuna
import lightgbm as lgb
from sklearn.model_selection import train_test_split, KFold
from sklearn.metrics import mean_absolute_percentage_error
import numpy as np
from sklearn.datasets import make_regression

data, target = make_regression(n_samples=10000, n_features=12, n_informative=12,
                               n_targets=1, random_state=1)

X, X_ts, y, y_ts = train_test_split(data, target, test_size=0.2)

n_trials=10
study = optuna.create_study()
for _ in range(n_trials):
    kf = KFold(n_splits=3)
    trial = study.ask()
    params={'verbosity':-1,
            'lambda_l2':trial.suggest_float('lambda_l2', 0.01, 0.2),
            'max_depth':trial.suggest_int('max_depth', 5, 55, step=5),
            'num_leaves':trial.suggest_int('num_leaves', 5, 45, step=5),
            'learning_rate':trial.suggest_float('learning_rate',0.05, 0.25, step=0.05),
            'bagging_fraction': trial.suggest_float('bagging_fraction', 0.7, 0.9 , step=0.1)
            }
    reg = lgb.LGBMRegressor(**params)
    mape_l = []
    for tr_idx, val_idx in kf.split(X):
        X_tr, X_val = X[tr_idx], X[val_idx] #X_tr, X_val = X.iloc[tr_idx], X.iloc[val_idx]
        y_tr, y_val = y[tr_idx], y[val_idx] # y_tr, y_val = y.iloc[tr_idx], y.iloc[val_idx]
        reg.fit(X_tr, y_tr)
        y_pr = reg.predict(X_val)
        mape_l.append(mean_absolute_percentage_error(y_val, y_pr))

    mape = np.mean(mape_l)

    study.tell(trial, mape)


reg = lgb.LGBMRegressor(**study.best_params)
reg.fit(X, y)
y_pr = reg.predict(X_ts)
mape = mean_absolute_percentage_error(y_ts, y_pr)


reg2 = lgb.LGBMRegressor()
reg2.fit(X, y)
y_pr2 = reg2.predict(X_ts)
mape2 = mean_absolute_percentage_error(y_ts, y_pr2)

print(f'mape - {mape}\nmape2 - {mape2}')

Результаты следующие:

Первое значение соответствует модели с подобранными параметрами, а второе - с параметрами по умолчанию.