Кейс 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'],
];

Даже визуально видно, что данные уже частично разделимы. В данном примере признаки частично коррелированы между собой, поэтому дерево может выбрать любой из них для первого разбиения – это нормальное поведение алгоритма. Задача дерева – найти такие пороги по признакам, которые сделают это разделение формальным.

Энтропия как мера неопределённости

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

Если в узле все пользователи относятся к одному классу, энтропия равна нулю – неопределённости нет. Если классы перемешаны, энтропия растёт. Это ровно та интуиция, которая нужна дереву для выбора вопросов.

circle-exclamation

Information gain – польза от вопроса

Теперь мы можем оценить, насколько полезен конкретный вопрос к данным. Для этого используется information gain – уменьшение энтропии после разбиения.

Если information gain равен нулю, вопрос не уменьшает неопределённость данных и, с точки зрения критерия энтропии, не улучшает разбиение. Чем больше это значение, тем лучше вопрос.

Разбиение по признаку

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

В этом учебном примере мы не фиксируем конкретный способ выбора порогов. На практике их обычно берут между соседними уникальными значениями признака и перебирают все такие варианты. Это самый распространённый вариант для числовых признаков.

Перебирая возможные пороги и вычисляя information gain, дерево фактически перебирает возможные вопросы и выбирает самый информативный.

Пример использования кода

Теперь посмотрим, как этот код применяется на практике. Наша цель – найти лучший первый вопрос дерева решений, то есть такое разбиение данных, которое максимальнее всего уменьшает неопределённость.

Для простоты:

  • возьмём один признак – visits

  • переберём возможные пороги

  • выберем разбиение с максимальным information gain

Шаг 1. Выбор признака и возможных порогов

Мы будем использовать признак visits (индекс 0). В качестве кандидатов на пороги возьмём все уникальные значения этого признака.

Идея простая: дерево перебирает возможные вопросы вида "visits < threshold?" и смотрит, какой из них лучше всего упорядочивает данные.

Шаг 2. Перебор разбиений и подсчёт information gain

Теперь для каждого порога:

  1. разбиваем данные на две части

  2. считаем information gain

  3. запоминаем лучшее разбиение

На этом этапе уже хорошо видно ключевую идею decision tree: алгоритм не подбирает непрерывные коэффициенты и не использует градиентную оптимизацию, а систематически перебирает возможные вопросы к данным и оценивает их пользу.

Шаг 3. Результат — первый вопрос дерева

Выведем результат:

Интерпретация результата будет выглядеть так:

Если visits < threshold — идём в левую ветку, иначе в правую.

Это и есть первый узел дерева решений.

chevron-rightКейс 1. Полный пример кода на чистом PHPhashtag

Как из этого вырастает дерево

В полном алгоритме этот процесс выполняется рекурсивно. После выбора лучшего разбиения мы повторяем тот же самый шаг для каждого дочернего узла, пока:

  • данные не станут полностью однородными

  • или не будет достигнута максимальная глубина

  • или в узле не останется слишком мало объектов

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

Что важно понять из кейса

Этот кейс не про скорость и не про масштабируемость. Он про понимание.

Если свести его к сути, дерево решений:

  • измеряет неопределённость

  • выбирает вопрос, который уменьшает эту неопределённость

  • повторяет процесс, пока это имеет смысл

Если вы можете мысленно пройти этот путь – от исходных данных до выбранного порога – значит вы понимаете decision tree на уровне механики. Всё остальное, включая оптимизации и библиотеки, является лишь надстройкой над этой базовой логикой.

circle-info

Чтобы самостоятельно протестировать этот код, установите примеры из официального репозитория GitHubarrow-up-right или воспользуйтесь онлайн-демонстрациейarrow-up-right для его запуска.

Last updated