машинное обучение
May 30, 2021

Тюнинг градиентного бустинга от Microsoft - LightGBM

Рассмотрим основные параметры LightGBM, путем настройки которых можно повысить качество модели. Продолжим работу с данными о расходах и доходах людей, использованными в ходе демонстрационного запуска алгоритма ранее (код в конце статьи):

Ключевыми параметрами являются следующие:

n_estimators - число деревьев (псевдоним num_iteration) или итераций в бустинге.

learning_rate - скорость обучения. Рекомендуется перебирать вместе с num_iteration, так как в общем случае при снижении количества итераций лучше увеличивать скорость обучения.

num_leaves - определяет максимальное количество листьев, разрешенное для каждого дерева.

max_depth - максимальная разрешенная для деревьев глубина. По умолчанию не ограничена (-1).

min_data_in_leaf - минимальное количество дочерних элементов в листе.

num_class - количество классов для задачи классификации.

is_unbalance - логический параметр, который используется при классификации и указывает на несбалансированность данных. Если установить в True, модель устраняет дисбаланс автоматически.

scale_pos_weight - альтернативно для этого можно использовать данный параметр, например, со значением - sample_pos_weight = количество_нулевых_классов/количество_единичных_классов.

early_stopping_rounds - число, определяющее условие останова - если алгоритм не улучшается на протяжении данного количества итераций.

bagging_fraction - доля данных, отбираемая для обучения каждого дерева.

feature_fraction - доля признаков, отбираемых для обучения каждого дерева.

lambda_l2, lambda_l1 - параметры регуляризации деревьев.

device_type - тип используемого для обучения оборудования ('cpu', 'gpu', 'cuda')

num_threads - число потоков для обучения.

seed - инициализирующее значение для случайных чисел. Посредством его задания можно повторять результаты экспериментов.

Еще больше параметров на официальной странице.

Подбор параметров

Для поиска оптимальных параметров можно воспользоваться сеточным поиском (рассказывал ранее), реализованным с помощью классов GridSearchCV и RandomizedSearchCV библиотеки scikit-learn:

reg = lgb.LGBMRegressor(verbosity= -1)
params = {'num_leaves': [10,20,40], 'num_iterations': [10,50,200],
    'max_depth': [5, 10,20],'lambda_l2':[1,3,6],'learning_rate':[0.001, 0.01,0.1], 'bagging_fraction':[0.5,0.8] }
X_tr, X_ts, y_tr, y_ts = train_test_split(data_m[['зарплата', 'сумма_помощи']], data_m['расходы'], test_size=0.2)
cv = RandomizedSearchCV(estimator=reg, param_distributions=params, cv=3, n_iter=15)
cv.fit(X_tr, y_tr)

Извлечение настроенного объекта

Осуществляется путем обращения к свойству best_estimator_ объекта поиска:

reg_best = cv.best_estimator_

Использование и оценка

y_pr = reg_best.predict(X_ts)
metrics = {}
metrics['mape'] = mean_absolute_percentage_error(y_ts, y_pr)
metrics['mae'] = mean_absolute_error(y_ts, y_pr)

Сохранение и загрузка модели

Сохранение

reg_best.booster_.save_model('model')

Загрузка

model = lgb.Booster(model_file='model')

Ниже представлен код для генерирования использованного набора данных:

import pandas as pd
import numpy as np
import string
import lightgbm as lgb
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error
from sklearn.model_selection import RandomizedSearchCV

np.random.seed(0)
letters = np.array(list(string.ascii_uppercase))
digits = np.arange(0,10)
people_num = 10000
towns_num = 200 # количество городов, нацело делит people_num для удобства
# людям соответствуют по три случайные буквы алфавита (имя, фамилия)
names = np.random.choice(letters, size=(people_num,3))
# зарплата от 20 до 50 тыс. рублей, случайное равномерное распределение
salary = np.random.uniform(20,500, size=people_num)
# индекс города - по семь цифр из алфавита, их меньше чем людей,
# так как предполагаем одинаковые города для разных людей
loc_ind_p = np.random.choice(digits, size=(towns_num,7))
loc_ind = np.tile(loc_ind_p, (people_num//towns_num,1))
# склеим буквы имен и фамилий, а также
# индексы городов
names = pd.DataFrame(names, dtype='str').apply(lambda x:' '.join(x),axis=1)
loc_ind = pd.DataFrame(loc_ind, dtype='str').apply(lambda x:''.join(x),axis=1)
data = pd.DataFrame({'ФИО':names, 'индекс_города':loc_ind,
                         'зарплата':salary})
data['зарплата'] = np.round(data['зарплата'],2)
helps = np.random.uniform(5,10, size = towns_num)
town_help = pd.DataFrame({'индекс_города':pd.DataFrame(loc_ind_p, dtype='str')
                                          .apply(lambda x:''.join(x), axis=1),
                          'сумма_помощи':helps})
town_help['сумма_помощи'] = np.round(town_help['сумма_помощи'],2)
data_m = pd.merge(data, town_help, left_on = 'индекс_города',
                    right_on='индекс_города')
# перемешиваем данные
data_m = data_m.sample(frac=1).reset_index(drop=True)
data_m['расходы'] = 2*np.sqrt(data_m['зарплата'])+\
                        0.5*data_m['сумма_помощи']