Внедрение модели машинного обучения в производственную среду иногда бывает сложной задачей. Особенно, когда у вас нет определенной инфраструктуры. Если в вашей компании нет сервера Python и вы хотите внедрить прогнозную модель машинного обучения на любом компьютере с базой данных, эта статья для вас. На самом деле, делая это в системе баз данных, вы на самом деле можете сэкономить деньги и ресурсы (конечно, некоторые люди могут не согласиться со мной). Пока вы не планируете использовать модель для прогнозирования в реальном времени, все в порядке.

В этой статье я объясню, как преобразовать модель LightGBM JSON в файл SQL. Это грязный вопрос, и метод здесь работает только для модели LightGBM, обученной с использованием всех числовых функций. Если вы часто полагаетесь на алгоритм LightGBM для кодирования категориального признака, вам следует добавить дополнительные шаги, прежде чем переводить деревья решений в скрипт SQL.

Почему модель LightGBM

LightGBM — это древовидная ансамблевая модель с градиентным усилением, обладающая хорошей производительностью. У него есть особая особенность, которая мне нравится, называемая разделением силы. То есть вы можете принудительно разбить любые переменные в верхней части каждого дерева решений до того, как начнется обучение по принципу наилучший первый (XGBoost пропускает эту функцию). Просто поместите спецификацию разделения в JSON-файл (файл-пример), введите параметр и запустите обучение.

Анализ файла LightGBM JSON немного сложен, потому что дерево решений LightGBM растет по листам, а не по уровням (читайте эту документацию). Как следствие, дерево несимметрично. Сгенерированный SQL-скрипт также может быть запутанным, особенно если вы, как и я, помешаны на отступах табуляции.

Структура JSON

Файл JSON состоит из трех основных сведений: модели (версия, целевая функция и т. д.), функций (имена, индекс, минимум, максимум) и структуры дерева решений (тип решения, функция разделения и т. д.). Я просто использую необходимую информацию для формирования деревьев решений. Если вам нужна большая картинка, вы можете перейти по этой ссылке. Нам нужно

  • Имена функций: название функций. Мы используем его, чтобы определить, какая функция разделена в узле.
  • Функция разделения: Индекс функции, используемой в узле для разделения. Это значение используется вместе со списком имен функций.
  • Тип решения: Направление разделения, например «≤» или «≥»
  • По умолчанию слева: включено ли значение признака «NaN» в левый дочерний элемент или в правый дочерний элемент.
  • Значения листа: возвращаемые значения листа

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

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

Разбор JSON

С древовидной структурой, как показано ранее, лучше создать класс, определяющий объект узла. Объект узла должен содержать необходимую информацию об узле, такую ​​как функция, используемая для разделения, пороговое значение и тип решения. Кроме того, нам будет проще, если мы сможем понять, является ли ветвь (потомок) узла другим узлом или листом, чтобы мы знали, когда поставить «END» в «CASE WHEN» в нашем файле SQL.

со следующими методами для хранения информации об узле.

Кроме того, нам также нужны некоторые методы для проверки того, содержит ли левая и правая ветви узла другой узел или лист.

и методы написания сценариев SQL:

Построение SQL

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

Список tree_info содержит отдельное дерево решений, генерирующее оценку, которую мы должны суммировать вместе. Поэтому нам нужна итерация для каждого члена списка tree_info, как показано ниже.

и получать информацию о дереве для каждой итерации, создавая объект из созданного нами класса.

Теперь самое сложное: построить вложенную структуру CASE WHEN SQL из дерева решений. Предположим, мы хотим построить простое дерево решений, как показано ниже.

Структура CASE WHEN будет примерно такой, как показано ниже.

предполагая, что наша переменная default_left в файле JSON равна «True». Если «default_left» равно «False», то в нашей структуре CASE WHEN не будет утверждения «или a is Null». Вы также заметите, что SQL становится все более неприятным по мере того, как дерево дисбаланса становится глубже.

Из приведенной выше иллюстрации мы знаем, что мы должны «сканировать» дерево, чтобы сканировать каждый узел, пока не останется возвращаемого конечного значения. Мой подход заключается в использовании цикла while с флагом пребывания. Значение этого флага всегда равно «Истина», пока сканер не достиг последнего узла.

На каждой итерации сканер будет проверять, прибыл ли он в узел или нет. Если это действительно узел, то он будет сканировать, написан ли уже SQL-скрипт для левого потомка или нет. Если нет, сканер напишет сценарий SQL для этого разделения узла.

Кроме того, сканер снова проверит, является ли левый дочерний элемент узла листом или другим узлом. Если левый дочерний элемент является листом, сканер записывает значение листа в SQL:

Если левый дочерний узел содержит узел, нам нужно сохранить информацию о текущем узле в контейнере стека и создать объект узла из левого дочернего узла:

Отмечено, что я использую контейнер стека (tree_bank), потому что нам все еще нужно написать сценарий SQL для правого дочернего элемента, поэтому определенно нам нужно вернуться к родительскому узлу после того, как сценарий SQL для левого дочернего элемента будет полностью написан. Используя контейнер стека, мы можем вернуться к родительскому узлу, извлекая последний сохраненный узел (первый в последнем). Я также использую переменную с именем s_output для хранения сценария SQL, содержащего дерево.

Если дочерний SQL для листа уже написан, пришло время написать SQL для правого листа. В случае-когда правый лист запускается командой «ELSE».

Подобно тому, что мы сделали с левым дочерним элементом, мы также должны проверить, содержит ли правый дочерний элемент лист или другой узел. Если он содержит лист, сканер записывает значение листа и команду «END» в сценарий SQL.

Если правый дочерний узел содержит другой узел, мы должны сохранить информацию об узле в tree_bank и создать другой объект узла из правого дочернего узла.

Теперь у нас есть все методы для обхода и написания SQL-скриптов для левого и правого потомков узла, включая разделение узла. Чего не хватает, так это того, как вернуться к родительскому узлу. Мы можем сделать это, сначала проверив, имели ли мы дело как с левыми, так и с правыми дочерними элементами. Если это условие выполнено, то нам нужно проверить размер tree_bank. Если tree_bank не пуст, значит, нам еще предстоит разобраться с родительским узлом (в том числе написать «END» в SQL-скрипт case-when). В противном случае мы можем безопасно выйти из цикла while, так как все узлы в дереве полностью просканированы.

Не забываем, что между деревом решений нам нужно добавить знак «+».

В конце программы мы объединяем сценарии SQL, которые возвращают показатель де-логита (а не показатель вероятности).