Почти все, что вам нужно знать о размере раздела Dask Dataframes

Всё, что вам нужно знать о размере раздела Dask Dataframes

И как эффективно использовать его в модели XGBoost

Изображение предварительного просмотра (от автора)

Недавно мои коллеги и я работали над большой высоконагруженной службой, которая использует модель машинного обучения Xgboost и Dask в качестве инструмента для обработки распределенных данных и создания прогнозов. Здесь я хотел бы поделиться результатами, которые позволили нам максимально использовать Dask для подготовки данных и подгонки модели ML.

Что такое Dask?

Dask – это библиотека для распределенной обработки больших объемов данных. Основная идея заключается в разделении больших массивов на маленькие части (партиции).

Вот как хранятся и обрабатываются Dask Dataframes: таблицы могут быть разделены на маленькие фреймы данных (рассматривайте их как pandas DataFrames), чтобы не хранить всю таблицу в оперативной памяти. Вся исходная таблица может быть слишком большой для загрузки в память, но отдельные партиции могут быть загружены. Кроме того, такое хранение данных позволяет эффективно использовать несколько ядер процессора для параллельных вычислений.

В то же время, размер этих партиций (чанков) определяется разработчиком. Таким образом, один и тот же датафрейм может быть разделен на несколько партиций с использованием, например, “Split 1” или “Split 2” (Рисунок 1).

Рисунок 1. Разделение Dask dataframe на партиции (изображение от автора)

Выбор оптимального размера партиции важен, поскольку неправильный выбор может замедлить обработку данных. Оптимальный размер партиции зависит от размера всего набора данных, а также от ресурсов сервера (или ноутбука) – количества процессорных ядер и доступной оперативной памяти.

Отказ от ответственности: В дальнейшем для удобства мы будем измерять размер набора данных по количеству строк. Все таблицы будут состоять из 4 столбцов (3 признака + 1 целевая переменная). При реализации алгоритма в системе мы строим все зависимости не от количества строк в таблицах, а от общего количества элементов (строк x столбцов).

Проблема

Dask может использоваться для вычисления простых статистических показателей и агрегаций, но с помощью Dask можно также строить модели машинного обучения на больших данных. Например, XGBoost. Поскольку разрабатываемая нами служба могла потребовать обучения модели на 2-10 миллионов записей с использованием только 8-16 ГБ оперативной памяти (в случае маленьких виртуальных машин), мы решили провести эксперименты.

Даже в случае вычисления простых статистик размер партиций имеет большое значение, поскольку он может существенно замедлить алгоритм вычислений в двух случаях:

  • Партиции слишком большие, и поэтому их обработка в оперативной памяти занимает слишком много времени и ресурсов
  • Партиции слишком маленькие, и поэтому Dask должен часто загружать эти таблицы в оперативную память – на время синхронизации и загрузки/выгрузки тратится больше времени, чем на сами вычисления

Таким образом, использование одних и тех же вычислительных ресурсов может значительно снизить производительность программы при выборе неподходящего размера партиции (Рисунок 2). Рисунок 2 показывает время подгонки модели XGBoost на Dask dataframes с разными размерами партиций. Представлено среднее время выполнения за 5 запусков.

Рисунок 2. Влияние размера партиции на скорость подгонки модели XGBoost. Исходный датафрейм для этих экспериментов содержит 500 000 строк и 4 столбца (изображение от автора)

В этом посте обсуждается алгоритм поиска оптимального размера разделов для Dask Dataframes. Все таблицы, показанные в этом посте, используются для подгонки модели машинного обучения Dask Xgboost. Мы также поделимся некоторыми полезными советами, которые могут быть вам полезны.

Советы по документации

В официальной документации Dask есть веб-страницы с советами о том, как правильно обращаться с объектами Dask (датафреймы, массивы и т. д.), такие как Лучшие практики работы с Dask DataFrames.

На этой странице в частности указан такой совет:

Вы должны стремиться к разделам, в которых приходится около 100 мегабайт данных.

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

Настройка эксперимента

Как уже упоминалось выше, предполагается, что оптимальный размер раздела зависит от следующих трех условий:

  • Размер полного набора данных;
  • Ресурсы ЦП (количество процессов), которые могут использовать XGBoost и Dask;
  • Доступная оперативная память (RAM).

Таким образом, во время экспериментов менялось количество вычислительных ресурсов, а также размер исходного набора данных. Рассматриваемые случаи:

  • Размер раздела, тысячи строк: [5, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000] (13 случаев)
  • Размер полного набора данных, тысячи строк: [100, 200, 300, 400, 500, 600, 1000, 2000, 3000, 4000] (10 случаев)
  • Ресурсы ЦП (воркеры): [2, 3, 4] (3 случая)
  • Доступная оперативная память (RAM) на каждого воркера: [1 ГБ, 2 ГБ, 4 ГБ] (3 случая)

Примечание: Воркер в Dask – это процесс на компьютере (на сервере), который использует выделенные ему вычислительные ресурсы и работает в изоляции и параллельно относительно других воркеров.

Таким образом, было проанализировано 1170 случаев (13 х 10 х 3 х 3). Чтобы получить более надежные оценки времени выполнения, каждый случай был запущен 5 раз. Затем метрики (время выполнения) были усреднены.

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

Результаты

Сначала рассмотрим общие визуализации из экспериментов. Мы выполняли запуски с разным количеством ядер ЦП и объемом оперативной памяти, а также меняли размер исходного набора данных и размер разделов. После завершения экспериментов мы подготовили таблицу, показывающую только оптимальные решения (размеры разделов). Оптимальными размерами разделов являются те, при которых время выполнения при заданных условиях (RAM, ЦП и размер исходного набора данных) было минимальным. Корреляционные матрицы собранных метрик показаны на рисунке 3.

Рисунок 3. Корреляционные матрицы результатов эксперимента (изображение автора)

Из графика видно, что наибольшее влияние на время выполнения оказывает, очевидно, размер исходного набора данных. Количество воркеров и объем RAM также существенно влияют на время подгонки. Размер чанка имеет относительно слабый эффект. Однако это может быть связано с тем, что зависимость между временем выполнения и размером раздела нелинейная, что подтверждается кривой на рисунке 2. Также рисунок 4 подтверждает правильность проведенных измерений, потому что результаты соответствуют нашим ожиданиям.

Давайте рассмотрим анимацию с 3D графиками (Анимация 1).

Анимация 1. График различных тестовых случаев, где каждый кадр является фиксированным размером оригинального набора данных. Показаны оптимальные поверхности для каждого случая (автором)

В анимации оптимальные случаи (для каждой комбинации количества процессов и ОЗУ на одного работника) выделены красным цветом. То есть показаны условия, при которых время выполнения алгоритма было минимальным для заданного размера данных, количества ядер, ОЗУ и размера разбиения. Графики также показывают кусочно-постоянные оптимальные поверхности серого цвета (прим. поверхность глобальная для всех случаев).

Из анимации видно, что на некоторых кадрах отсутствуют данные о экспериментах (нет точек) (Рисунок 4). Это означает, что предложенных вычислительных ресурсов оказалось недостаточно для запуска модели.

Рисунок 4. Результаты моделирования для набора данных из 4 миллионов строк. Нет результатов для ОЗУ менее 4 (автором)

С изображения можно наблюдать, что при таком размере набора данных, если количество ядер небольшое, то следует формировать большие разбиения. Обратите внимание, что эта зависимость не выполняется для всех случаев.

На основе результатов запусков с недостаточными вычислительными ресурсами была подготовлена следующая визуализация (Рисунок 5).

Рисунок 5. Предел объема данных, превышение которого не позволяет запустить модель обучения. Количество объектов рассчитывается как количество строк в таблице, умноженное на количество столбцов (автором)

Кроме того, на основе собранных статистических данных о неудачных запусках был сделан вывод (совет): если объем памяти ограничен, более надежно использовать маленький размер разбиения.

Обсуждение

На основе данного исследования были сформулированы несколько советов для более эффективной настройки системы на основе модели Dask XGBoost. Обратите внимание, что это исследование было проведено с целью более эффективного запуска Dask на относительно небольших серверах (не имеющих сотен гигабайт оперативной памяти и десятков процессоров).

Эксперимент выявил оптимальные гиперплоскости, которые моделируются с использованием гауссовых процессов. На основе этого алгоритма автоматически выбираются оптимальные размеры разбиения (Анимация 2).

Анимация 2. Оптимальная поверхность для различных условий (CPU, ОЗУ и размер исходного набора данных). Каждый кадр отображает условия для определенного размера исходного набора данных (автором)

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

Заключение (& советы)

Надеюсь, вам было интересно прочитать о том, какой размер разбиения оказался оптимальным для обучения модели XGBoost.

Я осознаю, что эту статью стало “технической”. Поэтому для тех, кто смог дочитать ее до конца, я дам несколько советов:

  • Если вы измеряете время выполнения, всегда запускайте вычисления несколько раз и усредняйте результаты, так как время выполнения случайно;
  • Если у вас есть сомнения относительно размера разделов, лучше ошибиться в меньшую сторону (иначе алгоритм не просто будет выполняться долгое время, но может завершиться с ошибкой);
  • Для инициализации локального кластера в Dask используются команды cluster = LocalCluster() и Client(cluster) подробнее здесь. Мы настоятельно рекомендуем инициализировать кластер только один раз в коде, используя шаблон Singleton. Вы можете посмотреть, как это можно реализовать на Python здесь. В противном случае вы будете инициализировать новый кластер при каждом запуске;
  • В среднем оптимальный размер раздела уменьшается с увеличением числа строк в исходном наборе данных

Историю о Dask и машинном обучении представил Михаил Сарафанов и команда Wiredhut