Тюнинг CatBoost - градиентного бустинга от Яндекс
Как настроить один из лучших инструментов градиентного бустинга - CatBoost. Рассмотрим на примере.
Для демонстрации работы будем использовать сгенерированный набор для предсказания расходов людей (код для генерации ниже), который зависит от их доходов и размера дотаций из городского бюджета:
При этом формула этой взаимосвязи следующая (data_m - имя датафрейма):
data_m['расходы'] = 2*np.sqrt(data_m['зарплата'])+0.5*data_m['сумма_помощи']
Задача - использовать CatBoost для предсказания расходов. Посмотрим, сможет ли алгоритм справиться с поставленной задачей.
Перед использованием рассмотрим параметры модели (подробнее читай здесь):
iterations (синонимы num_boost_round, n_estimators, num_trees) - максимальное количество деревьев, используемых в модели (по умолчанию 1000). Значение может быть ниже заданного, если используются другие параметры, накладывающие ограничение на количество деревьев;
learning_rate - скорость обучения;
random_seed - инициализирующее значение для используемого генератора случайных чисел, чтобы обеспечить повторямость эксперимента;
l2_leaf_reg (reg_lambda) - коэффициент регуляризации функции потерь;
depth (max_depth) - глубина дерева (максимальное значение - 16);
min_data_in_leaf (min_child_samples) - минимальное количество узлов в листе;
max_leaves (num_leaves) - максимальное количество листьев в дереве;
subsample - часть исходной выборки, отбираемая для обучения каждого дерева;
colsample_bylevel - доля признаков, используемая для отбора на каждом сплите;
class_weights - веса классов в моделях классификации. Используется для устранения несбалансированности (например, вес positive=количество_negative/количество_positive);
auto_class_weights - устраняет дисбаланс автоматически (значения - 'Balanced', 'SqrtBalanced');
scale_pos_weight - задает вес для положительного класса;
Среди параметров scale_pos_weight, auto_class_weights, class_weights одновременно используется только один.
early_stopping_rounds - устанавливает количество итераций для останова, если на их протяжении метрика качества не улучшалась по сравнению с оптимальной;
use_best_model - если истина, в результате обучения с ранней остановкой будет возвращаться модель, полученная на итерации (количеством деревьев) с лучшей метрикой на валидационной выборке;
loss_function - функция потерь, которую модель минимизирует в процессе обучения (подмножество из списка);
eval_metric - валидационная метрика, используемая для ранней остановки (в зависимости от задачи ссылки здесь);
custom_metric - отслеживаемые метрики (список доступен здесь). Лучшие метрики в процессе обучения можно вернуть методом get_best_score;
classes_count - количество классов для многоклассовой классификации;
task_type - используемое для вычислений устройство ('CPU,'GPU');
thread_count - число используемых для обучения потоков (по умолчанию = -1, что означает - все ядра процессора);
cat_features - список наименований категориальных признаков;
verbose - объем выводимой информации (False - подавляем сообщения).
Теперь разделим данные на обучающую и тестовую выборки, обучим регрессор с параметрами по умолчанию, применим для предсказания тестовой выборки и подсчитаем метрики качества:
X_tr, X_ts, y_tr, y_ts = train_test_split(data_m[['зарплата', 'сумма_помощи']], data_m['расходы'], test_size=0.2) reg = CatBoostRegressor(verbose = False) reg.fit(X_tr, y_tr) y_pr = reg.predict(X_ts) metrics = {} metrics['mape'] = mean_absolute_percentage_error(y_ts, y_pr) metrics['mae'] = mean_absolute_error(y_ts, y_pr)
Сравните тестовые данные и предсказанные моделью:
Сохранение и загрузка модели
Модель можно сохранять в файл и загружать из файла:
Сохранение
reg.save_model('model')
Загрузка
reg = CatBoostRegressor() reg.load_model("model")
Ниже представлен код для генерации использованного в статье набора данных:
import pandas as pd import numpy as np import string from catboost import CatBoostRegressor from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error, mean_absolute_percentage_error 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['сумма_помощи']