магистерская диссертация / ВКРМ
.pdfФункция MakeSynthetic генерирует датасет по формуле y = sin(x0) + x1² +
0.5 x2 + ε, где ε N(0, 0.01). Нелинейность целевой зависимости
(тригонометрическая и квадратичная компоненты) делает задачу достаточно сложной для различения алгоритмов. Остальные признаки являются шумовыми и не несут информации о целевой переменной.
2.3.5 C API и разделяемая библиотека
C API – связующее звено между C++-ядром и Python-библиотекой. Он реализован в файлах tgbm_api.h (объявления) и tgbm_api.cpp (реализация) и
компилируется совместно с модулями tree и gbm в разделяемую библиотеку.
Компилятор C++ применяет к именам функций искажение имён (name mangling), добавляя к имени информацию о типах параметров. В результате имя функции в скомпилированной библиотеке становится нечитаемым, и Python не может вызвать её по исходному имени. Квалификатор extern "C" отключает искажение имён: функции с этим квалификатором экспортируются под своими именами и вызываются из Python через ctypes. Это стандартная техника создания
C-совместимых библиотек из C++-кода.
C API использует паттерн непрозрачного указателя: функция tgbm_create
создаёт объект C++ и возвращает указатель void*. Python хранит этот указатель и передаёт его во все последующие вызовы, не имея доступа к внутренним полям объекта. Внутри tgbm_api.cpp определена структура TgbmHandle, хранящая указатель на объект GBM и дополнительные данные (например,
зафиксированное время обучения). Такой подход гарантирует, что детали C++-
реализации не «протекают» на уровень вызывающего кода. Поток вызовов между Python и C++ через ctypes проиллюстрирован на рисунке 2.9.
Набор экспортируемых функций соответствует требованиям подраздела
2.1.2 и включает:
66
– tgbm_create – создаёт объект модели с заданными параметрами;
принимает все параметры как отдельные скалярные аргументы и
возвращает void*;
–tgbm_destroy – освобождает память объекта; вызывается из деструктора
Python-класса;
–tgbm_fit – запускает обучение; принимает указатель на модель, указатель на матрицу признаков в формате row-major, число объектов, число признаков и указатель на вектор целевых значений;
–tgbm_predict – вычисляет предсказания; принимает те же аргументы плюс выходной буфер;
–tgbm_avg_depth, tgbm_max_depth, tgbm_avg_leaves, tgbm_ternary_frac, tgbm_train_time – возвращают статистику ансамбля.
Поскольку C++ хранит матрицу как vector<vector<double>>, а NumPy
передаёт данные как плоский массив в формате row-major, в функциях tgbm_fit и tgbm_predict выполняется явная конвертация за O(n·d) операций. Это не является узким местом: шаг конвертации выполняется один раз при вызове метода, тогда как само обучение занимает на несколько порядков больше времени.
67
Рисунок 2.9 – Поток вызовов между Python и C++ через ctypes
Для корректного экспорта символов на разных платформах используется макрос TGBM_API. На Windows он разворачивается в __declspec(dllexport); на
Linux и macOS – в __attribute__((visibility("default"))). Без этих атрибутов функции могут отсутствовать в таблице экспорта скомпилированной библиотеки и быть недоступными при вызове из Python.
2.3.6 Python-библиотека TernaryGBM
Python-библиотека реализована в файле tgbm.py и состоит из вспомогательной функции загрузки библиотеки, объявлений ctypes-сигнатур и класса TernaryGBM.
При первом импорте модуля вызывается функция _find_library(), ищущая файл библиотеки в папке, где расположен сам tgbm.py. При работе на Windows (Python 3.8+) функция предварительно вызывает os.add_dll_directory с явным указанием пути. Это требование появилось в Python 3.8 из соображений безопасности: загрузчик DLL более не ищет зависимости в текущей папке без явного разрешения. Без данного вызова ctypes.CDLL завершается исключением
FileNotFoundError, даже если tgbm.dll физически находится в той же директории.
После загрузки библиотеки для каждой C-функции объявляются типы аргументов (argtypes) и тип возвращаемого значения (restype). Это обязательный шаг при использовании ctypes на 64-битных платформах: без явного указания ctypes по умолчанию трактует все аргументы как 32-битные целые, что приводит к передаче некорректных значений в C++-функции.
Функция принимает массив Python и возвращает пару: непрерывный массив float64 и ctypes-указатель на него. Возврат именно пары обязателен: если
Python-объект массива не удерживается переменной, сборщик мусора может освободить память до завершения работы C++-функции, что приводит к обращению по недействительному адресу.
68
Класс TernaryGBM наследуется от sklearn.base.BaseEstimator. Начиная со scikit-learn версии 1.6, инструменты cross_val_score, GridSearchCV и интеграция с Optuna требуют у класса модели атрибута __sklearn_tags__, который
BaseEstimator предоставляет автоматически. Помимо этого, наследование обеспечивает автоматическую реализацию методов get_params() и set_params()
на основе параметров конструктора, что необходимо для перебора гиперпараметров.
Конструктор __init__ принимает одиннадцать именованных параметров,
проверяет корректность значений branching и task, создаёт C++-объект через tgbm_create и сохраняет дескриптор. Метод fit(X, y) через np.asarray прозрачно принимает любой array-like формат (numpy array, pandas DataFrame, список списков), передаёт данные в C++-функцию и возвращает self – стандартное соглашение scikit-learn. Метод predict(X) создаёт выходной буфер нужного размера и передаёт его указатель в tgbm_predict; C++-функция записывает предсказания напрямую в этот буфер без промежуточного копирования.
Метод score(X, y) вычисляет R² для регрессии и accuracy для классификации. При классификации метки y приводятся к бинарному виду:
значения ≥ 0.5 считаются классом 1, остальные – классом 0. Метод tree_stats()
последовательно вызывает пять статистических функций C API и возвращает словарь с ключами avg_leaf_depth, max_tree_depth, avg_leaf_count, ternary_nodes_% и train_time_sec. Деструктор __del__ вызывает tgbm_destroy для освобождения памяти C++-объекта.
Стоит отметить, что при подборе гиперпараметров с помощью Optuna или
GridSearchCV словарь best_params не содержит ключа task, поскольку тип задачи обычно не включается в пространство поиска. При вызове
TernaryGBM(best_params) без явного указания task модель инициализируется со значением task='regression' по умолчанию, что для задачи классификации приводит к некорректным предсказаниям. Решение: всегда передавать параметр явно – TernaryGBM(best_params, task='classification').
69
2.4 Выводы по главе
В настоящей главе решены задачи постановки, математического обоснования и программной реализации модифицированного алгоритма
градиентного бустинга с троичным ветвлением. Полученные результаты состоят
вследующем.
1.Формализована задача обобщения алгоритма XGBoost на троичное и адаптивное ветвление узлов дерева; сформулированы функциональные и нефункциональные требования – раздельно к вычислительному алгоритму и к библиотеке – и обоснован выбор инструментов: вычислительное ядро
на C++ стандарта C++17, Python-интерфейс, совместимый с соглашениями
scikit-learn, и связующий механизм ctypes без внешних зависимостей.
2.Выведен критерий качества троичного разбиения – формула выигрыша
(2.15), являющаяся главным теоретическим результатом работы. Она представляет собой прямое обобщение бинарной формулы XGBoost (2.13):
к сумме оценок ветвей добавляется слагаемое средней ветви, а штраф за разбиение удваивается с γ до 2γ. Показано, что формула оптимального веса листа (2.11) и оценка листа (2.12) не зависят от числа потомков узла, что и делает такое обобщение корректным.
3.Доказано свойство корректности вырождения (2.17)–(2.18): при пустой средней ветви троичное разбиение никогда не оказывается выгоднее соответствующего бинарного. Это свойство обеспечивается самой структурой формулы выигрыша и не требует дополнительных проверок в коде, чем гарантируется корректность адаптивного режима.
4.Предложен гистограммный алгоритм поиска оптимальной пары порогов со сложностью O(n + B²) на один признак; проведён сравнительный анализ вычислительной сложности бинарного, троичного и адаптивного режимов и сформулирована практическая рекомендация по снижению максимальной глубины дерева при троичном ветвлении.
70
5.Разработана кроссплатформенная библиотека с модульной архитектурой:
вычислительное C++-ядро, компилируемое в разделяемую библиотеку, и
Python-обёртка, обращающаяся к нему через ctypes. Решение удовлетворяет всем сформулированным требованиям – по производительности, удобству использования, переносимости и
детерминированности.
Таким образом, основным результатом главы является математически обоснованный и программно реализованный механизм троичного и адаптивного ветвления как обобщение регуляризованного градиентного бустинга.
Полученные алгоритм и библиотека служат основой для экспериментальной оценки влияния режима ветвления на структурную сложность модели, качество предсказания и вычислительные затраты, которая проводится в следующей главе.
71
3 Экспериментальное исследование разработанного алгоритма
3.1 Цель, задачи и методика эксперимента
Цель экспериментального исследования – проверить основную гипотезу работы о том, что троичное и адаптивное ветвление в алгоритме градиентного бустинга позволяет строить более компактные деревья без потери качества, а в определённых условиях – повышать и точность прогнозирования. Исследование построено в три этапа:
1.Контролируемая проверка механизма на синтетических данных с известной структурой;
2.Оценка на реальных наборах данных при честном индивидуальном подборе гиперпараметров;
3.Поиск области данных и режимов, в которой метод даёт преимущество по точности.
4.
3.1.1 Сравниваемые методы
Ядром сравнения является внутренний ablation – три режима одного и того же разработанного алгоритма: бинарное, троичное и адаптивное ветвление. Это позволяет изолировать вклад именно стратегии ветвления, исключив влияние различий в реализации. В качестве внешних эталонов использованы зрелые промышленные реализации градиентного бустинга: sklearn HistGradientBoosting, XGBoost, LightGBM и CatBoost.
3.1.2 Используемые метрики
Для всесторонней оценки применялись три группы метрик (таблица 3.1).
•Качество прогнозирования: для регрессии – RMSE (основная), MAE и коэффициент детерминации R²; для классификации – AUC-
ROC (основная), AUC-PR, F1-мера, точность (Accuracy) и
логарифмическая функция потерь (LogLoss). Метрики AUC-PR и F1
72
включены как устойчивые к дисбалансу классов, а LogLoss
совпадает с оптимизируемой функцией потерь.
•Сложность модели: средняя и максимальная глубина листьев,
среднее число листьев и доля троичных разбиений – именно эти величины отражают компактность.
•Вычислительные затраты: время обучения и время вывода в расчёте на один объект.
Таблица 3.1 – Используемые метрики оценки
|
Группа |
|
|
Метрика |
|
|
Лучше |
|
|
|
|
|
|
|
|||
|
Качество (регрессия) |
|
RMSE, MAE |
|
меньше |
|||
|
|
|
|
|
|
|||
|
Качество (регрессия) |
|
R² |
|
больше |
|||
|
|
|
|
|
|
|||
|
Качество (классиф.) |
|
AUC-ROC, AUC-PR, F1, Accuracy |
|
больше |
|||
|
|
|
|
|
|
|||
|
Качество (классиф.) |
|
LogLoss |
|
меньше |
|||
|
|
|
|
|
|
|||
|
Сложность модели |
|
средняя/макс. глубина, число листьев |
|
меньше |
|||
|
|
|
|
|
|
|||
|
Диагностика |
|
доля троичных разбиений |
|
– |
|||
|
|
|
|
|
|
|||
|
Затраты |
|
время обучения и вывода |
|
меньше |
|||
|
|
|
|
|
|
|
|
|
3.1.3 Протокол оценки
Каждый набор данных разбивался на обучающую и тестовую части в соотношении 75/25; для устойчивости оценок эксперименты повторялись при нескольких начальных значениях генератора случайных чисел, результаты приводятся как «среднее ± стандартное отклонение». При сравнении на множестве наборов данных метрики сводились к средним рангам (усреднять метрики с разными шкалами недопустимо), а значимость различий проверялась статистическими критериями (п. 3.1.4). При индивидуальном подборе гиперпараметров на реальных данных применялся вложенный ресэмплинг:
гиперпараметры подбирались только на внутренней валидации внутри обучающей части, а итоговая оценка проводилась на нетронутом тесте, что исключает утечку информации и завышение результатов.
73
3.1.4 Методы статистической обработки результатов
Метрики качества на разных наборах данных имеют разные шкалы,
поэтому усреднять их напрямую некорректно – результаты сводятся к рангам.
Средний ранг. На каждом наборе данных модели упорядочиваются по основной метрике: лучшей присваивается ранг 1, следующей – 2 и так далее (при равенстве берётся средний ранг). Средний ранг модели – это её ранг,
усреднённый по всем наборам данных; чем он меньше, тем лучше модель в среднем. Ранги безразмерны и сопоставимы между наборами.
Критерий Фридмана. Непараметрический аналог дисперсионного анализа с повторными измерениями, работающий с рангами. Проверяет нулевую гипотезу о том, что все сравниваемые модели в среднем равноценны (различия средних рангов случайны). Статистика критерия распределена приблизительно как χ² с k−1 степенями свободы (k – число моделей); если достигаемый уровень значимости p < 0,05, хотя бы одна модель значимо отличается от остальных.
Критерий выбран потому, что не требует предположения о нормальности распределений и корректно работает с несопоставимыми по шкале метриками.
Апостериорный критерий Неменьи. Применяется после того, как критерий Фридмана выявил наличие различий, и указывает, какие именно пары моделей различаются значимо. Две модели считаются значимо различными, если их средние ранги отличаются больше чем на критическую разность CD:
CD = qα · √( k(k+1) / (6N) ),
где k – число моделей, N – число наборов данных, qα – табличное критическое значение для уровня значимости α = 0,05.
Чем больше моделей и меньше наборов данных, тем выше порог CD, то есть тем труднее признать различие значимым.
Диаграмма критических различий визуализирует этот результат:
модели размещаются на оси среднего ранга, а горизонтальная планка соединяет группы моделей, средние ранги которых отличаются менее чем на CD, то есть
74
статистически неразличимых. Модели, не соединённые общей планкой,
различаются значимо.
Парный критерий Уилкоксона. Используется для прицельного сравнения двух конкретных моделей по всем наборам данных (в данном случае
– троичного и адаптивного против бинарного). Это непараметрический критерий для связанных выборок. Проверяет гипотезу о равенстве нулю медианы попарных разностей метрик. Применяется как более чувствительный инструмент для одного целевого сравнения, тогда как критерии Фридмана и Неменьи оценивают всю группу моделей одновременно.
3.1.5 Мера немонотонности данных
Гипотеза работы предполагает преимущество троичного ветвления на немонотонных данных, где целевая величина зависит от попадания признака в некоторый интервал (оптимум «по середине»). Чтобы отбирать такие данные формально, введена мера немонотонности – «изотонический разрыв».
Для каждого признака его значения разбиваются на интервалы по квантилям, и в каждом вычисляется среднее значение целевой переменной – это
«свободная» форма зависимости, способная быть немонотонной; точность описания ею данных оценивается коэффициентом детерминации R² свободной формы. Затем те же интервальные средние аппроксимируются наилучшей монотонной функцией (изотонической регрессией – отдельно для возрастающего и убывающего вариантов, берётся лучший), что даёт R²
монотонной формы. Немонотонность признака – это разность коэффициентов детерминации свободной и монотонной форм: она показывает, насколько немонотонная зависимость точнее наилучшей монотонной. Немонотонность набора данных – среднее по пяти наиболее немонотонным признакам.
Изотонический разрыв выбран вместо сравнения квадратичной и линейной моделей потому, что улавливает немонотонность любой формы – одногорбую,
многогорбую, ступенчатую, – а не только симметричную параболическую.
75
