Вступление

RAPIDS 0.9 представил библиотеку логических выводов леса, известную как FIL. FIL значительно ускоряет вывод (прогноз) для моделей на основе деревьев, включая модели деревьев решений с градиентным усилением (например, из XGBoost и LightGBM) и случайных лесов. (Чтобы глубже погрузиться в библиотеку в целом, ознакомьтесь с оригинальным блогом FIL.)

Модели в исходном файле FIL хранятся в виде плотных двоичных деревьев. То есть хранение дерева предполагает, что все листовые узлы находятся на одной и той же глубине. Это приводит к простому, эффективному во время выполнения макету для неглубоких деревьев. Но для глубоких деревьев также требуется много памяти графического процессора - 2 ** (d + 1) -1 узлов для дерева глубины d .

Для поддержки даже самых густых лесов, RAPIDS 0.11, FIL добавляет поддержку разреженного хранилища деревьев. Если ветвь разреженного дерева заканчивается раньше, чем максимальная глубина d, для потенциальных дочерних элементов этой ветки не выделяется память. Это может обеспечить значительную экономию памяти. В то время как плотное дерево глубины 30 всегда требует более 2 миллиардов узлов, для самого тонкого из возможных разреженных деревьев глубины 30 потребуется всего 61 узел.

Использование разреженных лесов с FIL

Использование разреженных лесов в FIL не сложнее, чем использование густых лесов. Тип создаваемого леса управляется новым параметром storage_type для ForestInference.load(). Возможные значения:

  • 'DENSE' чтобы создать густой лес,
  • 'SPARSE' для создания редкого леса,
  • 'AUTO' (по умолчанию), чтобы позволить FIL решать, который в настоящее время всегда создает густой лес.

Нет необходимости изменять формат входного файла, входных данных или вывода прогноза. Первоначальную модель можно обучить с помощью scikit-learn, cuML, XGBoost или LightGBM. Ниже приведен пример использования FIL с разреженными лесами.

from cuml import ForestInference
import sklearn.datasets
# Load the classifier previously saved with xgboost model_save()
model_path = 'xgb.model'
fm = ForestInference.load(model_path, output_class=True,
                          storage_type='SPARSE')
# Generate random sample data
X_test, y_test = sklearn.datasets.make_classification()
# Generate predictions (as a gpu array)
fil_preds_gpu = fm.predict(X_test.astype('float32'))

Реализация

На рисунке 1 показано, как разреженные леса хранятся в FIL. Все узлы хранятся в одном большом массиве nodes. Для каждого дерева индекс его корня в массиве узлов хранится в массиве trees. Каждый разреженный узел в дополнение к информации, хранящейся в плотном узле, хранит индекс своего левого дочернего элемента. Поскольку у каждого узла всегда есть два дочерних узла, левый и правый узлы хранятся рядом. Следовательно, индекс правого дочернего элемента всегда можно получить, прибавив 1 к индексу левого дочернего элемента. Внутри FIL продолжает поддерживать как плотные, так и разреженные узлы, причем оба подхода являются производными от базового класса леса.

По сравнению с внутренними изменениями, изменения в API Python сведены к минимуму. Новый параметр storage_type указывает, следует ли создавать густой или разреженный лес. Кроме того, новое значение 'AUTO' было сделано новым значением по умолчанию для параметра алгоритма вывода; он позволяет FIL сам выбирать алгоритм вывода. Для разреженных лесов он в настоящее время использует алгоритм 'NAIVE', который является единственным поддерживаемым. Для густых лесов используется алгоритм 'BATCH_TREE_REORG'.

Контрольные точки

Чтобы протестировать разреженные деревья, мы обучаем случайный лес с помощью scikit-learn, а именно sklearn.ensemble.RandomForestClassifier. Затем мы конвертируем полученную модель в лес FIL и оцениваем производительность логического вывода. Данные генерируются с использованием sklearn.datasets.make_classification() и содержат 2 миллиона строк, поровну разделенных между набором данных для обучения и проверки, и 32 столбца. Для сравнительного анализа вывод выполняется для 1 миллиона строк.

Мы используем два набора параметров для сравнительного анализа.

  1. Для ограничения глубины установите значение 10 или 20; в этом случае в память графического процессора может поместиться либо плотный, либо разреженный лес FIL.
  2. Без ограничения глубины; в этом случае модель, обученная SKLearn, содержит действительно глубокие деревья. В наших тестах деревья обычно имеют глубину от 30 до 50. При попытке создать плотный лес FIL не хватает памяти, но разреженный лес можно создать плавно.

В обоих случаях размер самого леса остается относительно небольшим, поскольку количество листовых узлов в дереве ограничено 2048, а лес состоит из 100 деревьев. Мы измеряем время вывода CPU и вывода GPU. Логический вывод GPU был выполнен на V100, а логический вывод CPU был выполнен в системе с 2 сокетами, каждый с 16 ядрами с 2-сторонней гиперпоточностью. Результаты тестов представлены на рисунке 2.

Как разреженные, так и плотные предикторы FIL (если последний доступен) примерно в 34–60 раз быстрее, чем предиктор SKLearn CPU. Предиктор разреженного FIL медленнее по сравнению с густым для неглубоких лесов, но может быть быстрее для более глубоких лесов; точная разница в производительности варьируется. Например, на рисунке 2 с max_depth = 10 плотный предиктор примерно в 1,14 раза быстрее разреженного предиктора, но с max_depth = 20 он медленнее, достигая только 0,75-кратной скорости разреженного предиктора. Следовательно, для неглубоких лесов следует использовать предсказатель плотного FIL.

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

Заключение

С добавлением поддержки разреженного леса FIL применяется для решения более широкого круга проблем, чем когда-либо прежде. Независимо от того, строите ли вы деревья решений с градиентным усилением с помощью XGBoost или случайные леса с помощью cuML или scikit-learn, FIL должен быть легким вариантом для ускорения вывода. Как всегда, если у вас возникнут какие-либо проблемы, не стесняйтесь сообщать о проблемах на GitHub или задавать вопросы в нашем общедоступном канале Slack!