XGBoost - классификатор, регрессор, ранжирование...

Jun 21, 2020 23:44

Если вы не знаете, что такое классификатор или регрессия, то дальше вам будет не интересно.

Далее будут несколько интересных особенностей, с которыми я столкнулся при внедрении и обучении.

XGBoost - проект, который позволит вам создавать свои классификаторы. Берем Python, ставим XGBoost. Подгружаем обучающие данные (я заблаговременно сохраняю их в csv формат, но можно пользоваться и БД). Для манипуляций и подготовки данных вам пригодится pandas, но иногда лучше взять dask (ту часть, что отвечает за dataframe).

На 500к строках и порядка 1,5к колонках построение классификатора может занять от часа до 12 в зависимости от мощности вашего компьютера. При этом точность будет на уровне нейросетей, а скорость несколько десятков тысяч предсказаний в секунду. Думаю я вас заинтересовал :-)

Основная сложность, с которой я столкнулся - недостаток RAM. При загрузке в память (к примеру, через pandas) можно задать тип данных. Данные влезают в память при использовании компактных типов (к примеру 8 бит целое), но при передаче в XGBoost все данные принудительно конвертируются во float. Как этого избежать я не нашел. В результате на моих данных питон тупо поедал всю память (рекомендую в настройках системы ограничить максимальный размер файла подкачки!).

Методы уменьшения данных с целью снижения требований к RAM
Стратегия "Выкидываем тривиальное"
Одной из возможных стратегий для уменьшения набора данных является выкидывание тривиальных строк.
1. Берем небольшое число случайных строк из нашего набора данных.
2. На небольшой подвыборке тренируем крошечную модель, которая будет служить как предварительный классификатор.
3. При помощи классификатора пробегаемся по всем нашим данным (по полному набору) и смотрим, в каких местах модель часто дает сбой - эти строки обязательно оставляем. А дальше отбираем столько строк, сколько влезет в память при обучении XGBoost (подбирается экспериментально). Т.е. из нашего огромного dataset мы выкидываем те строки, на которых модель (даже маленькая) не дает ошибки - тривиальные объекты. Будьте осторожны - не следует выкидывать слишком много, если после записи вы видите, что данных мало, значит нужно вернуть часть тривиальных объектов.
4. На уменьшенном наборе данных строим уже нормальный Большой классификатор. Считаем метрики (например F1).
5. Если считаем, что F1 можно улучшить, то переходим к пункту 3.

Этот подход работает, но pandas плохо работает с большими объемами и я часто получал вылеты питона без расшифровки ошибки ("Exit code -1").

Стратегия "Уменьшение размерности"
PCA - метод главных компонент. Почитать тут. По сути мы хитрым образом "выкидываем" (уменьшаем) число столбцов. При этом "выкидываем" не совсем верно. Мы используем линейные преобразования таким образом, что бы наши 1000 столбцов превратить в 100. При этом с некоторой ошибкой мы можем сделать обратное преобразование из 100 в 1000. Т.е. в уменьшенном наборе колонок будет сохраняться 95% информации.

А можно уменьшить используя вариационный автоэнкодер (VAE)

Стратегия дообучения
В XGBoost есть пара вариантов, как можно дообучать существующую сеть.

Первый делается примерно так:
params = {'objective': 'reg:linear', 'verbose': False}
model_1 = xgb.train(params, xg_train_1, 30)
params.update({'process_type': 'update', 'updater' : 'refresh', 'refresh_leaf': True})
model_2_update = xgb.train(params, xg_train_2, 30, xgb_model=model_1)

Этот метод ищется через запрос "incremental training for xgboost". Имеет свои ограничения: похоже не работает с 'objective': 'binary:logistic', плюс ко всему в данном варианте не добавляются и не меняются деревья. В случае с update происходит только более тонкая подстройка ветвлений (без сущесвенной перестройки деревьев)!

Второй вариант основан на том, что мы можем обучать классификатор на базе уже существующего. В этом случае число деревьев с каждым вызовом model.fit() будет увеличиваться в арифметической прогрессии.
Именно этот подход я и выбрал:
1. Делим все данные на тестовую выборку и обучающую. При желании ещё можно сделать валидационную выборку.
2. Создаем пустой классификатор model = xgboost.XGBClassifier(n_estimators=10, max_depth=11, min_child_weight=9.0, gamma=3)
2. Берем часть обучающей выборки (я беру X случайных строк) так, что бы не было проблем с RAM при обучении.
3. В случае, если модель пустая обучаю model.fit(df_train_X, df_train_Y), если модель уже была сохранена запускаю model.fit(df_train_X, df_train_Y, xgb_model='1.bst.tmp')
4. Сохраняем модель model.save_model('1.bst.tmp')
5. Вычисляем F1 score на тестовых данных. В случае, если думаем, что F1 можно улучшить переходим на пункт 2.

Для хранения данных я использую dask и в моем случае это существенный тормоз.
Наже графики. Зеленый - загрузка процессора (красным показана загрузка ядра - побочный эффект при чтении с диска). Желтый - загруженность памяти. Синий показывает насколько активно работаем с HDD. Легко видеть, что сначала идет долгий процесс чтения данных с диска, только потом мы начинаем обучение и загрузка CPU 100%.



Выше график загрузки моей машины при использовании dask с параметрами по умолчанию. В максимуме загрузка памяти больше, чем не хотелось бы.



А это график с использованием fastparquet. Во втором случае размер данных больше, но максимальная загрузка памяти существенно ниже! За это платим более длительным временем чтения с диска.

Как использовать в своих проектах
Есть неплохая библиотека на питоне m2cgen, которая позволяет экспортировать в C, C#, Dart, Go, Haskell, Java, JavaScript, PHP, PowerShell, Python, R, Ruby, Visual Basic. На выходе вы получаете готовый модуль, который может быть скомпилирован вашим любимым компилятором (т.е. без использования каких-либо dll!). С m2cgen есть некоторые ограничения на сложность (к примеру C# может уткнуться в ограничение 64 тысячи локальных переменных, можно попытаться обойти ограничение путем создания нескольких небольших процедур в замен одной большой).

Пример экспорта модели в C#
import m2cgen
code = m2cgen.export_to_c_sharp(model)
text_file = open(filename, "w")
n = text_file.write(code)
text_file.close()

В XGBoost достаточно простой формат экспорта в текстовый вид. Вот фрагмент моего дерева:


Для экспорта на диск воспользуйтесь командой:
model.get_booster().dump_model('xgb_txt.dmp')

Как правило модель получается довольно компактной. В моем случае это порядка 75к строк объемом 3,5 Мегабайт. При этом скорость выдачи результата порядка 60 тысяч/сек на процессоре i5 (4 потока). Т.е. в однопоточном режиме можно рассчитывать на 10-20 тысяч результатов в секунду! По моему это очень-очень неплохой результат.

Для развертывания моделей почитать про onnx (google). Возможно вам пригодится PMML. В частности загляните сюда https://nyoka-pmml.github.io/nyoka/xgboost_to_pmml.html.

Нет вашего любимого языка? Не страшно. Можно попытаться при помощи m2cgen экспортировать (к примеру) в JavaScript и путем серии find-replace преобразовать к нужному вам формату. Или можно распарсить текстовый дамп модели и самостоятельно преобразовать его в исходный код вашей программы.

Исходный код на https://github.com/Imageman/xgb_dask.

машинное обучение, программирование

Previous post Next post
Up