Кейс 1. Учебное дерево решений на чистом PHP
В этом кейсе мы разберём дерево решений максимально приземлённо – без библиотек, без оптимизаций и без попыток написать универсальный алгоритм. Цель здесь учебная: увидеть, как идеи энтропии и information gain превращаются в конкретные шаги кода и последовательные решения.
Этот кейс стоит читать не как рецепт для продакшена, а как разбор механики. Если вы поймёте его целиком, любые реализации decision tree в библиотеках перестанут быть чем-то удивительным или магическим.
Цель кейса
Цель этого кейса – показать, как дерево решений:
измеряет неопределённость данных
выбирает лучший вопрос к данным
шаг за шагом уточняет решение
Мы сознательно работаем с маленьким набором данных и простыми условиями. Это позволяет сосредоточиться на логике алгоритма, а не на инженерных деталях.
Сценарий задачи
Представим простую бизнес-задачу. У нас есть пользователи сайта, и мы хотим разделить их на два класса: "активный" и "пассивный". Для каждого пользователя известны два числовых признака:
количество визитов в неделю
среднее время на сайте в минутах
Интуитивно понятно, что активные пользователи чаще заходят на сайт и проводят там больше времени. Дерево решений должно формализовать эту интуицию в виде последовательности вопросов.
Исходные данные
Каждый объект описывается тройкой значений: [visits, time, label]. Данные намеренно сделаны маленькими и наглядными.
$data = [
[5, 10, 'active'],
[7, 15, 'active'],
[1, 2, 'passive'],
[2, 3, 'passive'],
[6, 8, 'active'],
[3, 4, 'passive'],
];Даже визуально видно, что данные уже частично разделимы. В данном примере признаки частично коррелированы между собой, поэтому дерево может выбрать любой из них для первого разбиения – это нормальное поведение алгоритма. Задача дерева – найти такие пороги по признакам, которые сделают это разделение формальным.
Энтропия как мера неопределённости
Первое, что нужно дереву решений, – уметь измерять, насколько "чистыми" являются данные в узле. Для этого используется энтропия.
Если в узле все пользователи относятся к одному классу, энтропия равна нулю – неопределённости нет. Если классы перемешаны, энтропия растёт. Это ровно та интуиция, которая нужна дереву для выбора вопросов.
В вышеуказанном примере в логарифме используется основание 2, что является стандартом в теории информации. На практике основание логарифма не влияет на выбор разбиений – оно лишь масштабирует значение энтропии.
Information gain – польза от вопроса
Теперь мы можем оценить, насколько полезен конкретный вопрос к данным. Для этого используется information gain – уменьшение энтропии после разбиения.
Если information gain равен нулю, вопрос не уменьшает неопределённость данных и, с точки зрения критерия энтропии, не улучшает разбиение. Чем больше это значение, тем лучше вопрос.
Разбиение по признаку
Для простоты мы будем рассматривать только бинарные разбиения вида "признак < порог".
В этом учебном примере мы не фиксируем конкретный способ выбора порогов. На практике их обычно берут между соседними уникальными значениями признака и перебирают все такие варианты. Это самый распространённый вариант для числовых признаков.
Перебирая возможные пороги и вычисляя information gain, дерево фактически перебирает возможные вопросы и выбирает самый информативный.
Пример использования кода
Теперь посмотрим, как этот код применяется на практике. Наша цель – найти лучший первый вопрос дерева решений, то есть такое разбиение данных, которое максимальнее всего уменьшает неопределённость.
Для простоты:
возьмём один признак –
visitsпереберём возможные пороги
выберем разбиение с максимальным information gain
Шаг 1. Выбор признака и возможных порогов
Мы будем использовать признак visits (индекс 0).
В качестве кандидатов на пороги возьмём все уникальные значения этого признака.
Идея простая: дерево перебирает возможные вопросы вида "visits < threshold?" и смотрит, какой из них лучше всего упорядочивает данные.
Шаг 2. Перебор разбиений и подсчёт information gain
Теперь для каждого порога:
разбиваем данные на две части
считаем information gain
запоминаем лучшее разбиение
На этом этапе уже хорошо видно ключевую идею decision tree: алгоритм не подбирает непрерывные коэффициенты и не использует градиентную оптимизацию, а систематически перебирает возможные вопросы к данным и оценивает их пользу.
Шаг 3. Результат — первый вопрос дерева
Выведем результат:
Интерпретация результата будет выглядеть так:
Если visits < threshold — идём в левую ветку, иначе в правую.
Это и есть первый узел дерева решений.
Как из этого вырастает дерево
В полном алгоритме этот процесс выполняется рекурсивно. После выбора лучшего разбиения мы повторяем тот же самый шаг для каждого дочернего узла, пока:
данные не станут полностью однородными
или не будет достигнута максимальная глубина
или в узле не останется слишком мало объектов
Даже без полной рекурсивной реализации уже видно, как формируется структура дерева и почему оно выглядит именно так, а не иначе. В библиотечных реализациях всё это автоматизировано и оптимизировано, но логика остаётся ровно такой же, как в этом примере.
Что важно понять из кейса
Этот кейс не про скорость и не про масштабируемость. Он про понимание.
Если свести его к сути, дерево решений:
измеряет неопределённость
выбирает вопрос, который уменьшает эту неопределённость
повторяет процесс, пока это имеет смысл
Если вы можете мысленно пройти этот путь – от исходных данных до выбранного порога – значит вы понимаете decision tree на уровне механики. Всё остальное, включая оптимизации и библиотеки, является лишь надстройкой над этой базовой логикой.
Чтобы самостоятельно протестировать этот код, установите примеры из официального репозитория GitHub или воспользуйтесь онлайн-демонстрацией для его запуска.
Last updated