Как реализовать обучение модели XGBoost при данных, не помещающихся в память
Что проверяют
Знаете ли вы, что у XGBoost есть собственный режим внешней памяти, и понимаете ли, чем он отличается от распределённого обучения. Следом спрашивают, почему нельзя просто обучать модель по кускам, как нейросеть мини-батчами.
Как отвечать
Сначала честная диагностика: часто данные не влезают только в сыром виде.
QuantileDMatrix: хранит признаки уже квантованными в бины гистограммы (по умолчаниюmax_bin=256), а не сырые значения, и заметно экономит память по сравнению сDMatrix. Работает с методомhist, который в актуальных версиях используется по умолчанию;- типы и отбор:
float32вместоfloat64ещё в pandas, категориальные признаки черезcategoryиenable_categorical=True, отказ от бесполезных колонок, даунсэмплинг мажоритарного класса с поправочными весами.
Если этого мало, путей три:
- внешняя память: наследуетесь от
xgboost.DataIter, реализуетеnext(), который отдаёт очередной кусок черезinput_data, иreset(), а из итератора строитеExtMemQuantileDMatrix(в свежих версиях) илиQuantileDMatrix. Куски читаются с диска, например из Parquet, скорость упирается в диск, и NVMe здесь заметно лучше; - Dask: модуль
xgboost.daskиDaskXGBClassifierпри запущенномdask.distributed.Client. Данные раскладываются по воркерам, но суммарно должны поместиться в память кластера; - Spark:
xgboost.spark.SparkXGBClassifierкак обычный estimator PySpark, если данные и так живут в Spark.
Для обучения на GPU задают device="cuda", внешняя память там тоже поддерживается.
Где спотыкаются
Предлагают мини-батчи с дообучением через xgb_model. Каждый новый кусок только добавляет деревья поверх старых, а старые построены на части данных и не пересматриваются. Это не эквивалент обучения на всём датасете, качество обычно хуже.
Считают, что Dask решает проблему на одной машине с малой памятью. Он масштабирует на кластер, а на одном узле без внешней памяти упадёт так же.
Забывают про копирование. При сборке матрицы из DataFrame на пике в памяти лежат и фрейм, и его копия внутри XGBoost, поэтому данные либо читают сразу итератором, либо освобождают фрейм сразу после построения матрицы.