Кросс-валидация и отбор количества итераций в LightGBM
Рассмотрим вопрос на примере датасета о прогнозе выживаемости пассажиров "Титаника". Загрузим набор данных:
import pandas as pd import numpy as np SEED=0 np.random.seed(SEED) df = pd.read_csv("https://s3.amazonaws.com/h2o-public-test-data/smalldata/gbm_test/titanic.csv") df = df.astype({it:'category' for it in df.select_dtypes(include='object').columns}) df = df.sample(frac=1).reset_index(drop=True) df.head()
Сначала вспомним, как осуществить ранний останов без кросс-валидации. Для этого следует сформировать два датасета (Dataset) и вызвать метод train с параметрами, включающими измеряемые метрики, тренировочный и валидационный датасеты (valid_sets), функцию раннего останова (добавляется в список в параметре callbacks) с указанием количества итераций, в течение которых отслеживается факт улучшения метрики (stopping_rounds). Если задано несколько валидационных метрик, то параметр first_metric_only=True в callback функции указывает, что выбор итерации будет происходит не с учетом их всех, а только первой:
import lightgbm as lgb tr_idx, val_idx = np.split(np.arange(df.shape[0]), [round(0.8*df.shape[0])]) train_data = lgb.Dataset(df.drop(columns='survived').iloc[tr_idx], label = df['survived'].iloc[tr_idx]) valid_data = lgb.Dataset(df.drop(columns='survived').iloc[val_idx], label = df['survived'].iloc[val_idx], reference=train_data) params = {'metric':['auc', 'binary_logloss'], 'objective': 'binary'} clf = lgb.train(params, train_data, num_boost_round=100, valid_sets=[valid_data], callbacks=[lgb.early_stopping(stopping_rounds=5, first_metric_only=True)]) clf
Лучшая итерация и значения метрик хранятся в атрибутах best_iteration, best_score объекта lightgbm.basic.Booster:
clf.best_iteration, clf.best_score
Для кросс-валидации используется функция cv, которая принимает аналогичные параметры, только вместо двух датасетов один (так как сама осуществляет разбиение) и стратегию разбиений (количество фолдов или их индексы), дополнительные параметры здесь:
from sklearn.model_selection import KFold sp = KFold(n_splits=3, shuffle=True, random_state=SEED) train_data = lgb.Dataset(df.drop(columns='survived'), label = df['survived']) cv_res = lgb.cv(params, train_data, 300, folds=sp, shuffle=False, seed = None, callbacks=[lgb.early_stopping(stopping_rounds=5, first_metric_only=True)], return_cvbooster=True)
Лучшую итерацию можно получить из атрибута best_iteration CVBooster:
cv_res['cvbooster'].best_iteration
либо из результатов кросс-валидации:
print(f"Номер лучшей итерации (с 0) - {np.array(cv_res['auc-mean']).argmax()}, \ максимальное качество - {np.array(cv_res['auc-mean']).max()}")
Задав номер лучшей итерации, проведем кросс-валидацию вручную и сверим качество:
from sklearn.metrics import roc_auc_score auc_l = [] data_all = lgb.Dataset(df.drop(columns='survived'), label = df['survived']) for idx_tr, idx_val in sp.split(df): X_tr, X_val = df.drop(columns='survived').iloc[idx_tr], df.drop(columns='survived').iloc[idx_val] y_tr, y_val = df['survived'].iloc[idx_tr], df['survived'].iloc[idx_val] train_data = lgb.Dataset(X_tr, label = y_tr, reference=data_all) params = {'metric':['auc', 'binary_logloss'], 'objective': 'binary'} clf = lgb.train(params, train_data, num_boost_round=cv_res['cvbooster'].best_iteration) auc_l.append(roc_auc_score(y_val, clf.predict(X_val)))
print(np.mean(auc_l))
Обратите внимание на параметр reference=data_all при формировании train_data. Он нужен для того, чтобы бинаризация переменных при поиске оптимальной границы осуществлялась по всему массиву данных. Именно так происходит в функции cv. В противном случае бинаризация идет только по тренировочному датасету, и результаты совпадать не будут.
Не пропустите ничего интересного и подписывайтесь на страницы канала в других социальных сетях: