магистерская диссертация / ВКРМ
.pdfНефункциональные требования, в отличие от функциональных,
представляют собой качественные атрибуты решения в целом и не делятся между алгоритмом и библиотекой: такие свойства, как производительность,
кроссплатформенность и воспроизводимость, являются сквозными. Поэтому они приведены единым списком в таблице 2.3 с указанием класса по модели качества
ISO/IEC 25010 и уровня, к которому требование относится в первую очередь.
Сформулированные требования становятся основой для выбора инструментов и архитектурных решений, описанных в следующем подразделе.
2.1.3 Выбор инструментов, технологий и архитектурная концепция
Выбор |
инструментов |
разработки |
производится |
исходя |
из |
||||
сформулированных |
требований |
– |
прежде |
всего |
требований |
к |
|||
производительности, кроссплатформенности и удобству использования.
Выбор языка программирования для алгоритма. Нефункциональное требование о высокой скорости вычислений предопределяет необходимость применения компилируемого языка. В данной работе использован язык C++
стандарта C++17. Его выбор обоснован рядом факторов. Во-первых, C++
обеспечивает максимальную вычислительную производительность: алгоритмы поиска оптимальных порогов разбиения выполняют интенсивные числовые операции над большими массивами данных, где разница в скорости между компилируемым и интерпретируемым кодом может составлять десятки раз. Во-
вторых, именно на C++ реализовано ядро библиотеки XGBoost, что делает архитектуру разрабатываемого решения непосредственно сопоставимой с эталонной. В-третьих, стандарт C++17 поддерживается всеми современными компиляторами – GCC, Clang и MSVC, – что отвечает требованию кроссплатформенности.
Выбор интерфейса библиотеки. Требование удобства использования и совместимости со стандартными процедурами машинного обучения (кросс-
валидация, перебор гиперпараметров) указывает на необходимость Python46
интерфейса: именно Python является стандартной платформой для прикладного анализа данных. При этом для выполнения требования высокой скорости вычислений Python-интерфейс должен не реализовывать алгоритм заново, а
вызывать уже скомпилированное C++-ядро. Такая схема – вычисления на C++
под Python-интерфейсом – реализована в промышленных библиотеках XGBoost, LightGBM и scikit-learn.
Для соединения C++ и Python существует несколько технологий.
Сравнительный анализ рассмотренных вариантов приведён в таблице 2.4.
Таблица 2.4 – Сравнение технологий интеграции C++ и Python
|
Технология |
|
|
Внешние |
|
|
Сложность |
|
|
Передача данных |
|
|
|
|
|
|
|
|
|
||||
|
|
|
зависимости |
|
|
настройки |
|
|
|
||
|
|
|
|
|
|
|
|
|
|
||
ctypes |
|
Нет |
|
Низкая |
|
Указатели на массивы |
|||||
|
|
|
|
|
|
|
|
|
|
|
|
pybind11 |
|
Да |
|
Средняя |
|
Объекты C++ |
|||||
|
|
|
напрямую |
||||||||
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
|
|||||
SWIG |
|
Да |
|
Высокая |
|
Автогенерация обёрток |
|||||
|
|
|
|
|
|
|
|
|
|
|
|
Cython |
|
Да |
|
Средняя |
|
Требует |
|||||
|
|
|
переписывания |
||||||||
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|||||||
|
Выбрана технология ctypes: она входит в стандартную библиотеку Python, |
||||||||||
не требует дополнительных зависимостей и позволяет вызывать функции скомпилированной разделяемой библиотеки, передавая данные через указатели на непрерывные массивы. NumPy предоставляет встроенный механизм получения такого указателя, что позволяет передавать матрицы признаков в C++
без копирования данных.
Выбор базового класса для Python-интерфейса. Требование
совместимости с процедурами кросс-валидации и перебора гиперпараметров накладывает конкретное техническое ограничение: начиная со scikit-learn версии
1.6, инструменты cross_val_score, GridSearchCV и интеграция с Optuna требуют от класса модели атрибута __sklearn_tags__. По этой причине класс TernaryGBM
наследуется от sklearn.base.BaseEstimator, который предоставляет этот атрибут автоматически, а также реализует методы get_params и set_params, необходимые для корректной передачи параметров при переборе.
47
Итоговая архитектурная концепция. Разработанное решение состоит из двух уровней: вычислительного ядра (реализация алгоритма) и
пользовательского интерфейса (библиотека). Ядро компилируется в разделяемую библиотеку (libtgbm.so на Linux и macOS, tgbm.dll на Windows), к
которой Python-библиотека обращается через ctypes. Такая архитектура обеспечивает выполнение всех сформулированных требований: высокую скорость – за счёт C++-ядра, удобство использования – за счёт Python-
интерфейса, кроссплатформенность – за счёт стандартных инструментов сборки и отсутствия нестандартных зависимостей.
2.2 Математическая модель троичного ветвления
Ключевая математическая проблема данного исследования заключается в следующем: как обобщить критерий оценки качества разбиения узла,
используемый в алгоритме XGBoost, на случай трёх дочерних подузлов? Ответ требует последовательного прохождения от исходного регуляризованного функционала через аппроксимацию второго порядка к оптимальным весам листьев и, наконец, к формулам выигрыша для бинарного и троичного случаев.
Именно такой путь описан в данном подразделе.
2.2.1 Регуляризованный целевой функционал и аппроксимация второго
порядка
Алгоритм XGBoost строит ансамбль итеративно: на каждом шаге t к уже построенному ансамблю Ft−1(x) добавляется новое дерево ft(x), минимизирующее суммарные потери. Совокупные потери после добавления ft записываются как:
L(t) = Σi=1n L( yi, Ft−1(xi) + η ft(xi) ) + Ω(ft) (2.4)
Прямая минимизация (2.4) затруднена из-за нелинейности произвольной функции потерь L. Ключевая идея алгоритма XGBoost состоит в том, чтобы заменить L её квадратичным приближением по Тейлору, разложив в окрестности текущего предсказания Ft−1(xi):
48
L( yi, Ft−1(xi) + η ft(xi) ) ≈ L( yi, Ft−1(xi) ) + gi · η ft(xi) + ½ hi · η² ft²(xi),
где gi и hi – производные первого и второго порядка функции потерь по предсказанию, вычисленные в точке Ft−1(xi) по формуле (2.5).
gi = ∂L(yi, ŷ)/∂ŷ |ŷ=F_{t−1(xi)} , hi = ∂²L(yi, ŷ)/∂ŷ² |ŷ=F_{t−1(xi)} (2.5)
Величина gi называется градиентом и показывает, насколько и в каком направлении следует скорректировать предсказание для i-го объекта. Величина hi называется гессианом и характеризует кривизну функции потерь в данной точке: чем больше hi, тем более «стабильна» квадратичная аппроксимация.
Поскольку слагаемое L(yi, Ft−1(xi)) не зависит от оптимизируемого дерева ft
и при минимизации выступает константой, его можно опустить. Шаг обучения η включается в определение ft. Приближённый целевой функционал выражается по формуле (2.6):
(t) |
= Σi=1 |
n |
[ gi ft(xi) + ½ hi ft²(xi) ] + Ω(ft) |
(2.6) |
L̃ |
|
|||
Слагаемое регуляризации Ω(ft) штрафует за сложность дерева и в |
||||
алгоритме XGBoost записывается по формуле (2.7): |
|
|||
|
Ω(ft) = γ T + (λ/2) Σj=1T wj² |
(2.7) |
||
где T – количество листьев дерева ft, wj – вес (предсказание) j-го листа, γ ≥
0 – штраф за каждый новый лист, контролирующий структурную сложность дерева, λ ≥ 0 – коэффициент L2-регуляризации весов листьев, ограничивающий
амплитуду предсказаний.
Совместное действие двух штрафов существенно снижает склонность модели к переобучению по сравнению со стандартным градиентным бустингом.
Для задачи регрессии используется среднеквадратичная функция потерь
L = ½ (yi − ŷ)², при которой гессиан равен единице для всех объектов, что
упрощает вычисления (формула (2.8)): |
|
gi = ŷi − yi = Ft−1(xi) − yi , hi = 1 |
(2.8) |
Для задачи бинарной классификации используется логистическая функция потерь L = −yi ln pi − (1 − yi) ln(1 − pi), где pi = σ(Ft−1(xi)), σ(z) = 1/(1 + e−z) –
сигмоид-функция. Градиент и гессиан принимают вид:
49
gi = pi − yi , hi = pi(1 − pi) |
(2.9) |
Гессиан hi = pi(1 − pi) достигает максимума при pi = 0.5 и стремится к нулю при pi → 0 и pi → 1. Это означает, что объекты, по которым модель уже
«уверена», вносят меньший вклад в построение нового дерева, – математически
обоснованный механизм снижения внимания к уже хорошо классифицированным объектам.
2.2.2 Оптимальный вес листа
Пусть = : попадает в лист – множество объектов, направляемых в j-й лист деревом ft. Перегруппируем сумму в функционале (2.6) по листьям и введём обозначения для суммарных градиентов и гессианов по листу j:
Gj = Σi I_j gi , Hj = Σi I_j hi
Тогда функционал (2.6) принимает вид суммы независимых квадратичных
функций от весов wj:
(t) |
T |
[ Gj wj + ½ (Hj + λ) wj² ] + γ T |
(2.10) |
|
L̃ = Σj=1 |
|
|
||
Это представление позволяет оптимизировать вес каждого листа |
||||
независимо. Минимум по wj |
|
|
находится из условия обращения в нуль первой |
|
производной: |
|
|
|
|
(t) |
/∂wj = Gj + (Hj + λ) wj = 0 |
|
||
∂L̃ |
|
|
||
Откуда немедленно получается формула оптимального веса j-го листа: |
||||
|
|
|
wj* = − Gj / (Hj + λ) |
(2.11) |
Знак минус означает, что если суммарный градиент по листу отрицателен |
||||
(модель в среднем завышает предсказание для объектов этого листа), то оптимальная поправка положительна, и наоборот. Слагаемое λ в знаменателе выполняет регуляризующую функцию: при λ > 0 абсолютная величина wj*
ограничена сверху, что предотвращает чрезмерные поправки.
Убедимся, что найденная точка является минимумом, а не максимумом:
вторая производная ∂²L̃(t)/∂wj² = Hj + λ > 0 (гессианы неотрицательны, λ > 0),
50
следовательно, функционал выпукл по wj, и точка (2.11) является глобальным минимумом.
Подставляя wj* в (2.10), получаем значение функционала при оптимальных весах листьев:
L̃(t)* = − ½ Σj=1T Gj²/(Hj + λ) + γ T
Введём функцию оценки листа Score, характеризующую вклад одного листа в значение функционала:
Score(G, H) = − ½ · G²/(H + λ) (2.12)
Чем больше |G| при фиксированном H + λ, тем меньше (отрицательнее)
значение Score, следовательно, лист становится более эффективным в снижении функционала потерь, то есть более полезным.
При G = 0 оценка равна нулю: объекты с взаимно компенсирующими градиентами не дают выигрыша от разбиения.
Формула (2.12) является атомарным строительным блоком для всех дальнейших выкладок. Принципиально важно отметить: вывод (2.11) и (2.12)
опирается исключительно на структуру регуляризованного функционала и не зависит от числа потомков узла. Это означает, что при переходе к троичному ветвлению формула оптимального веса листа не изменяется, меняется лишь состав множества Ij.
2.2.3 Формула выигрыша для бинарного разбиения
Прежде чем выводить формулу для троичного случая, рассмотрим стандартную формулу выигрыша от бинарного разбиения. Это необходимо,
чтобы троичный случай воспринимался не как отдельная конструкция, а как закономерное обобщение.
Пусть узел I разбивается на левую часть IL и правую IR. Обозначим суммарные градиент и гессиан по всему узлу: G = GL + GR, H = HL + HR.
До разбиения узел является одним листом с вкладом Score(G, H) − γ (один лист, штраф γ за его существование). После разбиения появляются два листа с
51
суммарным вкладом Score(GL, HL) + Score(GR, HR) − 2γ. Выигрыш от разбиения определяется как уменьшение значения функционала:
Gain2 = − [ Score(GL, HL) + Score(GR, HR) − Score(G, H) ] − γ
Раскрывая через определение (2.12):
Gain2 = ½ [ GL²/(HL+λ) + GR²/(HR+λ) − G²/(H+λ) ] − γ (2.13)
Слагаемое в квадратных скобках всегда неотрицательно (что следует из неравенства Коши–Буняковского для сумм). Штраф γ смещает порог: разбиение выполняется только при Gain2 > 0, то есть лишь тогда, когда улучшение функционала превосходит «стоимость» нового листа. При γ = 0 принимается любое разбиение, улучшающее аппроксимацию.
Обратим внимание на геометрический смысл: выражение G²/(H+λ) можно трактовать как меру «расслоённости» градиентов в узле Р. Разделение градиентов разного знака проиллюстрировано на рисунке 2.2.
Рисунок 2.2 – Геометрический смысл выигрыша
Если градиенты объектов узла имеют одинаковый знак, то их разделение по признаку даёт малый выигрыш. Если же часть объектов имеет большой положительный градиент (модель недооценивает yi), а другая часть – большой отрицательный (модель переоценивает), то разделение их в разные листы даёт существенный прирост.
52
2.2.4 Вывод формулы выигрыша для троичного разбиения
Рассмотрим разбиение узла I на три подмножества IL, IM, IR в соответствии с формулами (2.1)–(2.3). Введём суммарные градиенты и гессианы по каждой ветви:
GL = Σi I_L gi , |
GM = Σi I_M gi , |
GR = Σi I_R gi |
HL = Σi I_L hi , |
HM = Σi I_M hi , |
HR = Σi I_R hi |
Из условия разбиения следует: G = GL + GM + GR и H = HL + HM + HR.
До разбиения узел является одним листом с вкладом Score(G, H) − γ. После
троичного разбиения появляются три листа. Формула оптимального веса (2.11)
применяется к каждому из них независимо, а суммарный вклад трёх листьев равен:
Score(GL, HL) + Score(GM, HM) + Score(GR, HR) − 3γ
Выигрыш от троичного разбиения – разность вкладов до и после:
Gain3 = − [ Score(GL, HL) + Score(GM, HM) + Score(GR, HR) − Score(G, H) ] − 2γ
Коэффициент при γ равен 2, а не 3, потому что слагаемое −3γ от трёх новых листьев и слагаемое +γ от исходного нераздельного узла дают в сумме −2γ.
Иными словами, штраф отражает прирост числа листьев: переход от одного
листа к трём увеличивает T на 2, следовательно, штраф γT возрастает на 2γ.
Раскрывая функцию Score через (2.12), получаем ключевую формулу:
Gain3 = ½ [ GL²/(HL+λ) + GM²/(HM+λ) + GR²/(HR+λ) − G²/(H+λ) ] − 2γ (2.15)
Формула (2.15) является ключевым математическим результатом
настоящей работы. Она представляет собой прямое обобщение бинарной формулы (2.13): к двум слагаемым в скобках добавляется третье,
соответствующее средней ветви, а штраф удваивается с γ до 2γ.
Полезно записать соотношение между двумя формулами. Обозначим через Gain2(LM,R) выигрыш от бинарного разбиения узла I на объединение IL IM и IR.
Тогда можно показать, что: |
|
Gain3 = Gain2(LM,R) + ½ · GM²/(HM+λ) − γ |
(2.16) |
53 |
|
Формула (2.16) имеет прозрачную интерпретацию: троичное разбиение выгоднее соответствующего бинарного тогда и только тогда, когда дополнительный вклад от выделения средней ветви ½ · GM²/(HM+λ) превышает дополнительный штраф γ за новый лист. Иными словами, выгода от троичного разбиения возникает именно тогда, когда средняя группа объектов является
«существенной» – когда её суммарный градиент велик по модулю относительно суммарного гессиана.
2.2.5 Предельный переход: свойство корректности вырождения
Важное теоретическое требование к разработанной модификации – корректность вырождения: при исчезновении средней ветви троичное разбиение должно «вырождаться» в бинарное предсказуемым образом. Формализуем это утверждение.
Рассмотрим предельный переход IM → , то есть случай, когда пара порогов выбрана так, что ни один объект не попадает в среднюю ветвь. Тогда GM
→ 0 и HM → 0, следовательно GM²/(HM+λ) → 0. Кроме того, GL + GR → G и HL + HR → H. Подставляя в (2.15), получаем выражение, структурно идентичное бинарному выигрышу (2.13), однако вместо штрафа γ в нём стоит 2γ. Это означает:
Gain3 (при IM → ) = Gain2 − γ |
(2.17) |
Следствие из (2.17). При γ > 0 вырожденное троичное разбиение (с пустой
средней ветвью) всегда строго менее выгодно, чем соответствующее бинарное:
Gain3 (при IM → ) < Gain2 |
(2.18) |
Это свойство принципиально важно для корректности |
адаптивного |
режима. Оно гарантирует, что алгоритм никогда не предпочтёт «пустое» троичное разбиение (с формально тремя потомками, один из которых не содержит ни одного объекта) корректному бинарному. Данное свойство обеспечивается математической структурой формулы и не требует специальных проверок в программной реализации.
54
При γ = 0 выигрыш вырожденного троичного совпадает с бинарным. В
этом случае предпочтение отдаётся бинарному разбиению как более простому,
что реализуется в алгоритме через условие строго положительного выигрыша.
2.2.6 Алгоритм поиска оптимальной пары порогов
Для заданного признака xj задача поиска оптимального троичного разбиения формально записывается как (θ1, θ2) = arg max Gain3(j, θ1, θ2) при θ1 <
θ2 и ограничениях HL ≥ minW, HM ≥ minW, HR ≥ minW, где minW – параметр минимальной суммы гессианов в ветви. Эти ограничения предотвращают появление ветвей с недостаточным числом объектов, для которых оптимальный вес листа по формуле (2.11) оказался бы нестабильным.
Прямой перебор всех пар (θ1, θ2) требует рассмотрения O(n²/2) вариантов для каждого из d признаков. При n = 5000 объектах и d = 10 признаках это составляет порядка 1.25 · 108 операций на один узел – неприемлемо с точки зрения времени вычислений.
Для ускорения поиска применяется гистограммный приближённый алгоритм, аналогичный используемому в библиотеках LightGBM и XGBoost.
Диапазон значений признака j разбивается на B равноширинных корзин шириной δj = (xmax,j − xmin,j) / B, где xmax,j и xmin,j – максимальное и минимальное значения признака j среди объектов текущего узла. Если xmax,j = xmin,j, признак константен и пропускается. Каждый объект назначается в корзину bi = min( (xij
− xmin,j)/δj , B−1 ).
По каждой корзине суммируются градиенты и гессианы всех попавших в неё объектов: Gb = Σ gi, Hb = Σ hi по i : bi = b. Для эффективного вычисления сумм по диапазонам строятся префиксные суммы CGk = Σb=0k−1 Gb и CHk = Σb=0k−1 Hb, с помощью которых сумма по любому диапазону корзин вычисляется за O(1).
Поиск оптимальной пары порогов сводится к перебору всех пар (b1, b2), 0
≤b1 < b2 ≤ B−1:
–GL = G(0, b1+1), HL = H(0, b1+1) – объекты в корзинах 0, …, b ;
55
