Деревья решений: алгоритм CART, критерии разбиения и практическое применение

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

Процесс построения такого дерева напоминает игру в «20 вопросов», где каждый узел дерева — это вопрос об особенностях объекта (например, «Возраст больше 30?»), а ответы «да» или «нет» определяют путь к следующему узлу. Алгоритм начинает с корневого узла, который содержит все данные, и рекурсивно разделяет их, выбирая на каждом шаге тот признак, который наилучшим образом разделяет данные по целевому показателю.

Этот процесс продолжается до тех пор, пока не будет выполнено условие остановки (например, достигнута максимальная глубина или в узле остались объекты только одного класса), после чего создается листовой узел, содержащий итоговый прогноз.

В отличие от нейронных сетей, дерево решений позволяет понять, какие именно признаки и пороговые значения привели к конкретному предсказанию. Это делает алгоритм востребованным инструментом для первичного анализа данных и построения базовых (baseline) моделей, после которых уже пробуют более мощные.

Алгоритм CART деревьев решений

Алгоритм CART (Classification and Regression Trees), разработанный Брейманом в 1984 году, стал де-факто стандартом построения деревьев решений и лег в основу современных ансамблевых методов — Random Forest и Gradient Boosting.

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

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

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

  • левый содержит объекты с x ≤ t,
  • правый — с x > t.

Для категориальных признаков CART преобразует задачу к бинарному виду: перебирает все возможные разбиения категорий на две группы. Если категорий k, количество вариантов составляет 2^(k-1) — 1. Это приводит к комбинаторному взрыву при большом числе категорий, поэтому на практике категориальные признаки кодируют через техники энкодинга: one-hot encoding или target encoding.

Процесс разбиения рекурсивно применяется к каждому дочернему узлу. Результат — иерархическая структура, где каждый внутренний узел содержит условие разбиения, а листовые узлы — финальные предсказания. Глубина дерева определяет сложность модели: мелкие деревья дают высокое смещение, глубокие — высокую дисперсию.

Визуализация работы алгоритма Decision Trees. Демонстрирует влияние глубины дерева на сложность разбиения пространства признаков: простое линейное разбиение при глубине 1, сбалансированное при глубине 3, и избыточно сложное без ограничений с признаками переобучения. Черные линии показывают границы решений, цветовые регионы — области

Рис. 1: Визуализация работы алгоритма Decision Trees. Демонстрирует влияние глубины дерева на сложность разбиения пространства признаков: простое линейное разбиение при глубине 1, сбалансированное при глубине 3, и избыточно сложное без ограничений с признаками переобучения. Черные линии показывают границы решений, цветовые регионы — области

Бинарная структура и критерии остановки

CART всегда создает бинарные деревья, в отличие от алгоритмов ID3 и C4.5, допускающих множественные разбиения. Бинарная структура упрощает реализацию и снижает риск переобучения: каждое разбиение максимально информативно, так как использует весь датасет текущего узла.

Критерии остановки предотвращают избыточный рост дерева:

  • Максимальная глубина (max_depth) ограничивает число уровней от корня до листьев;
  • Минимальное число объектов в узле (min_samples_split) запрещает разбиение малых подвыборок;
  • Минимальное число объектов в листе (min_samples_leaf) предотвращает создание узлов с единичными наблюдениями;
  • Минимальное улучшение критерия (min_impurity_decrease) останавливает разбиение при незначительном приросте качества.
👉🏻  Оптимизация цепочек поставок с помощью машинного обучения

Комбинация этих параметров определяет компромисс между точностью и обобщающей способностью модели. Эмпирически max_depth в диапазоне 5-10 обеспечивает баланс для большинства задач, хотя разумеется оптимальные значения зависят от объема данных, поставленной задачи, требований к качеству прогнозов и числа признаков.

Жадный алгоритм и его ограничения

Жадная стратегия CART оптимизирует каждое разбиение независимо, что гарантирует полиномиальную сложность построения дерева:

O(n × m × log n)

где:

  • n — число объектов;
  • m — число признаков.

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

Недостаток жадного подхода — локальные оптимумы. Разбиение, неоптимальное на текущем шаге, может привести к лучшему результату после нескольких последующих разбиений. CART не учитывает такие сценарии, что ограничивает качество модели на сложных зависимостях. Ансамблевые методы частично решают проблему через построение множества деревьев с различными локальными оптимумами.

Другая особенность Decision trees — неустойчивость к малым изменениям данных. Изменение нескольких наблюдений может радикально изменить структуру дерева, особенно в верхних узлах. Техника Bootstrap aggregating и алгоритм Random Forest снижает эту чувствительность через усреднение предсказаний независимых деревьев.

Критерии разбиения узлов

Выбор оптимального разбиения требует формализации понятия «качества» узла. CART использует различные критерии в зависимости от типа задачи:

  • меры неопределенности для классификации;
  • функции потерь для регрессии.

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

Gini impurity для классификации

Gini impurity измеряет вероятность ошибочной классификации случайно выбранного объекта, если его метку назначить согласно распределению классов в узле:

G = 1 — Σ(p_i)²

где:

  • p_i — доля объектов класса i в узле;
  • Σ — суммирование по всем классам;
  • G ∈ [0, 0.5] для бинарной классификации.

Критерий достигает минимума 0 когда все объекты в узле принадлежат одному классу (чистый узел), и максимума 0.5 при равномерном распределении классов. Для многоклассовой задачи максимум составляет (K-1)/K, где K — число классов.

При оценке разбиения CART вычисляет взвешенную сумму Gini impurity дочерних узлов:

G_split = (n_left/n) × G_left + (n_right/n) × G_right

где:

  • n_left и n_right — число объектов в левом и правом дочерних узлах;
  • n — общее число объектов.

Прирост качества (information gain) определяется как разность G родительского узла и G_split. Алгоритм выбирает разбиение с максимальным information gain.

Gini impurity быстро вычисляется и имеет гладкую производную, что делает его предпочтительным критерием в большинстве реализаций. Альтернативный критерий — энтропия — дает схожие результаты, но требует вычисления логарифмов, что увеличивает время построения дерева на 15-20%.

MSE и MAE для регрессии

В задачах регрессии CART минимизирует разброс целевой переменной в узлах. Стандартный критерий — среднеквадратичная ошибка (MSE):

MSE = (1/n) × Σ(y_i — ȳ)²

где:

  • y_i — значение целевой переменной для объекта i;
  • ȳ — среднее значение целевой переменной в узле;
  • n — число объектов в узле.

Предсказание в листовом узле равно среднему значению целевой переменной объектов, попавших в этот узел. MSE штрафует большие отклонения сильнее малых из-за квадратичной функции потерь, что делает модель чувствительной к выбросам.

Альтернатива — средняя абсолютная ошибка (MAE):

MAE = (1/n) × Σ|y_i — ỹ|

где ỹ — медиана целевой переменной в узле.

MAE более устойчива к выбросам: экстремальные значения влияют на критерий линейно, не доминируя в процессе разбиения. Предсказание в листе равно медиане, что обеспечивает устойчивость к аномалиям в данных.

Выбор между MSE и MAE зависит от распределения целевой переменной и бизнес-требований. Для данных с выбросами MAE предпочтительнее, для нормально распределенных переменных MSE обеспечивает лучшую точность. На практике MSE используется чаще из-за вычислительной эффективности: обновление среднего при добавлении объекта требует O(1), обновление медианы — O(log n).

👉🏻  Градиентный бустинг: концепция и механизм работы

Выбор оптимального разбиения

Для каждого признака алгоритм перебирает пороговые значения и вычисляет критерий качества. Вычислительная сложность этого этапа определяет скорость построения дерева. Оптимизация сводится к сортировке объектов по значению признака и последовательному вычислению критерия для каждого уникального значения.

  1. Пусть признак x принимает n_unique уникальных значений после сортировки;
  2. CART вычисляет критерий для n_unique — 1 возможных разбиений: между каждой парой соседних значений;
  3. При каждом сдвиге порога один объект перемещается из левого узла в правый, что позволяет обновлять статистики инкрементально без пересчета с нуля.

Для классификации при сдвиге объекта класса k обновление Gini impurity требует:

  • Вычитания p_k² из суммы левого узла;
  • Добавления обновленной p_k² после уменьшения числа объектов;
  • Аналогичного обновления правого узла.

Такой подход снижает сложность вычисления критерия с O(n) до O(1) для каждого порога, общая сложность составляет O(n × log n) на сортировку плюс O(n) на перебор порогов.

Для признаков с большим числом уникальных значений (например, непрерывные величины с высокой точностью) CART может использовать биннинг: разбиение диапазона значений на фиксированное число интервалов. Это снижает число проверяемых порогов с тысяч до десятков, ускоряя построение дерева в 10-50 раз с минимальной потерей качества.

Контроль глубины и переобучение

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

Параметры ограничения роста дерева

Библиотека scikit-learn предоставляет набор гиперпараметров для контроля сложности дерева. Ключевые параметры:

  • max_depth — максимальная глубина дерева. Ограничивает число последовательных разбиений от корня до листа. Значения 3-5 дают простые, легко интерпретируемые модели. Значения 10-15 обеспечивают высокую точность на сложных зависимостях. Unlimited depth приводит к переобучению на выборках размером более 1000 объектов.
  • min_samples_split — минимальное число объектов для разбиения узла. Значение 2 (по умолчанию) разрешает разбиение любого узла с двумя и более объектами. Увеличение до 20-50 предотвращает создание узлов на малых подвыборках, снижая дисперсию модели. Эффективно при наличии шума в данных.
  • min_samples_leaf — минимальное число объектов в листовом узле. Предотвращает создание листьев с единичными наблюдениями. Значения 5-10 обеспечивают статистическую значимость предсказаний в листьях. Критично для несбалансированных датасетов: без ограничения дерево создает множество листьев для редких классов.
  • max_features — число признаков для рассмотрения при поиске лучшего разбиения. Значение None использует все признаки, sqrt(n_features) и log2(n_features) вводят случайность, снижая корреляцию между деревьями в ансамбле. Для отдельного дерева рекомендуется None, для Random Forest — sqrt или log2.
  • min_impurity_decrease — минимальное улучшение критерия для выполнения разбиения. Абсолютное значение прироста качества, ниже которого разбиение не выполняется. Значения 0.0001-0.001 отсекают разбиения, незначительно улучшающие критерий. Эффективен на больших датасетах, где множество разбиений дают микроскопический прирост качества.

Оптимальные значения параметров зависят от размера и характеристик датасета. Grid search с кросс-валидацией находит комбинацию параметров, максимизирующую качество на валидационной выборке. Для датасетов размером 10000+ объектов типичные значения: max_depth=10, min_samples_split=20, min_samples_leaf=5.

Прунинг: пост-обработка дерева

Прунинг строит полное дерево, затем удаляет узлы, не улучшающие качество на валидационной выборке. Метод Cost complexity pruning (minimal cost-complexity pruning) минимизирует функцию. Его формула:

R_α(T) = R(T) + α × |T|

где:

  • R(T) — ошибка дерева T на обучающей выборке;
  • |T| — число листовых узлов;
  • α — параметр регуляризации.
👉🏻  Прогнозирование трафика и конверсий сайта с помощью XGBoost

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

Этапы алгоритма прунинга:

  1. Построить полное дерево на обучающей выборке;
  2. Для каждого внутреннего узла вычислить α, при котором поддерево становится невыгодным;
  3. Найти узел с минимальным α и заменить его поддерево листом;
  4. Повторять шаг 3 до получения корня;
  5. Выбрать дерево из последовательности с минимальной ошибкой на валидации.

Scikit-learn реализует cost complexity pruning через параметр ccp_alpha. Значения 0.001-0.01 дают умеренную обрезку, 0.1+ — агрессивную. Метод cost_complexity_pruning_path возвращает последовательность α и соответствующие показатели качества для выбора оптимального значения.

Прунинг эффективнее предварительного ограничения параметров на небольших датасетах (менее 5000 объектов), в случаях когда кросс-валидация гиперпараметров вычислительно затратна. На больших выборках предварительное ограничение через max_depth и min_samples_split дает сопоставимое качество при меньших затратах времени.

Валидация и выбор гиперпараметров

K-fold кросс-валидация оценивает обобщающую способность модели. Датасет делится на K фолдов, модель обучается на K-1 фолдах и валидируется на оставшемся. Процесс повторяется K раз, финальная метрика — среднее по фолдам. Значения K=5 или K=10 обеспечивают баланс между вычислительными затратами и качеством оценки.

Стратификация фолдов обычно применяется для несбалансированных данных: каждый фолд содержит пропорциональное представительство классов. Без стратификации отдельные фолды могут не содержать редкие классы, что искажает оценку качества. Scikit-learn автоматически применяет стратификацию в StratifiedKFold для задач классификации.

Grid search перебирает комбинации гиперпараметров, обучая модель для каждой комбинации с кросс-валидацией. Для дерева решений типичная сетка включает:

  • max_depth: [3, 5, 7, 10, 15, None]
  • min_samples_split: [2, 10, 20, 50]
  • min_samples_leaf: [1, 5, 10, 20]
  • criterion: [‘gini’, ‘entropy’] для классификации

Полный перебор 6 × 4 × 4 × 2 = 192 комбинаций с 5-fold валидацией требует построения 960 деревьев. Для датасетов размером 100000+ объектов это занимает десятки минут. В таких случаях прибегают к Random search; данный алгоритм случайно выбирает подмножество комбинаций, снижая время поиска в 5-10 раз с минимальной потерей качества найденного оптимума.

Интерпретируемость и важность признаков

Основное преимущество деревьев решений — прозрачность логики принятия решений. Путь от корня до листа представляет собой набор условий if-then, понятных без знания математического аппарата. Это крайне важный аспект в регулируемых индустриях: кредитование, страхование, медицина требуют объяснения отказов и решений.

Визуализация дерева решений

Графическое представление дерева показывает структуру разбиений, распределение классов в узлах и пути принятия решений. Библиотека scikit-learn предоставляет функции export_graphviz и export_text для визуализации. Первая генерирует dot-файл для рендеринга через Graphviz, вторая — текстовое представление дерева.

Узел дерева содержит:

  • Условие разбиения (признак и порог);
  • Значение критерия (Gini или MSE);
  • Число объектов в узле;
  • Распределение классов (для классификации) или среднее значение (для регрессии).

Структура дерева решений для задачи кредитного скоринга с двумя признаками: доход и возраст. Каждый узел отображает условие разбиения, значение Gini impurity, долю объектов от общей выборки и распределение классов. Цветовая насыщенность узлов отражает чистоту предсказаний: насыщенный синий соответствует высокой концентрации класса "Дефолт", насыщенный оранжевый — класса "Не дефолт". Корневой узел использует признак "Доход" как наиболее информативный для первого разбиения, что демонстрирует жадную стратегию алгоритма CART

Рис. 2: Структура дерева решений для задачи кредитного скоринга с двумя признаками: доход и возраст. Каждый узел отображает условие разбиения, значение Gini impurity, долю объектов от общей выборки и распределение классов. Цветовая насыщенность узлов отражает чистоту предсказаний: насыщенный синий соответствует высокой концентрации класса «Дефолт», насыщенный оранжевый — класса «Не дефолт». Корневой узел использует признак «Доход» как наиболее информативный для первого разбиения, что демонстрирует жадную стратегию алгоритма CART

Интерпретация начинается с анализа верхних уровней дерева: признаки в корневых узлах имеют наибольшее влияние на предсказания. Частое появление признака на разных уровнях указывает на его значимость. Глубокие ветви с малым числом объектов сигнализируют о переобучении.

👉🏻  Изучаем опционы на Netflix: комплексный анализ и стратегии

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

Важность признаков (Feature importance)

Встроенный в деревья алгоритм Feature importance количественно оценивает вклад каждого признака в качество модели. CART вычисляет важность как взвешенное уменьшение критерия разбиения. Формула расчета:

importance(f) = Σ (n_t / n) × (impurity_t — (n_left/n_t) × impurity_left — (n_right/n_t) × impurity_right)

где:

  • суммирование по всем узлам t, где используется признак f;
  • n_t — число объектов в узле t;
  • n — общее число объектов;
  • impurity — значение критерия (Gini, энтропия, MSE).

Важности обычно нормализуются к сумме 1.0. Признаки, не использованные в дереве, получают нулевую важность. Высокая важность указывает, что признак участвует в разбиениях, сильно снижающих критерий неопределенности или ошибки.

Метод feature_importances_ возвращает массив важностей, упорядоченный по исходному порядку признаков в датасете. Сортировка и визуализация топ-10 признаков выявляет ключевые факторы модели. Признаки с важностью менее 0.01-0.05 могут быть удалены без потери качества, что упрощает модель и снижает риск переобучения.

Визуализация важности признаков в дереве решений: левый график показывает вклад каждого признака (красным выделены малозначимые с важностью менее 0.05), правый график отображает накопленную важность для определения минимального набора признаков, обеспечивающих 90% информативности модели

Рис. 3: Визуализация важности признаков в дереве решений: левый график показывает вклад каждого признака (красным выделены малозначимые с важностью менее 0.05), правый график отображает накопленную важность для определения минимального набора признаков, обеспечивающих 90% информативности модели

При этом следует учитывать, что важность признаков в одиночном дереве неустойчива: малые изменения данных могут перераспределить важность между коррелированными признаками. Для стабильной оценки в таких случаях используется усреднение важностей по множеству деревьев Random Forest или анализ permutation importance, оценивающий падение качества при случайном перемешивании значений признака.

Практическое применение деревьев решений

Деревья решений находят применение в задачах, где требуется не только предсказание, но и понимание механизма.

К примеру, в бизнесе с помощью этого алгоритма часто проводят анализ оттока клиентов (churn prediction), который выявляет сегменты с высоким риском ухода. В таком анализе дерево показывает, какие комбинации признаков (низкая частота использования + отсутствие активности 30+ дней) наиболее важны.

В кредитном скоринге дерево решений генерирует прозрачные правила одобрения: доход более 50000, стаж работы более 2 лет, отсутствие просрочек за 12 месяцев. Регуляторы требуют объяснения отказов заемщикам, дерево предоставляет конкретный путь к решению. Это невозможно с black-box моделями типа градиентного бустинга или нейросетей без дополнительных методов объяснения.

Сегментация клиентов через дерево решений выделяет гомогенные группы по поведенческим паттернам. Листовые узлы представляют сегменты с характерными признаками: высокий LTV + частые покупки + использование премиум-функций. Маркетинговые кампании таргетируются на специфичные сегменты с персонализированными предложениями.

Диагностика производственных процессов использует деревья для выявления причин дефектов. Признаки включают параметры оборудования, характеристики сырья, условия производства. Дерево показывает, какие комбинации параметров приводят к браку: температура выше 180°C + влажность менее 40% + скорость линии более 100 единиц/час. Это позволяет оптимизировать процесс без дорогостоящих экспериментов.

Пример построения модели на Python

Как правило, для построения деревьев используется библиотека Scikit-learn. Она уже включает в себя нужные классы DecisionTreeClassifier и DecisionTreeRegressor со всем необходимым.

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

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.tree import DecisionTreeClassifier, plot_tree, export_text
from sklearn.metrics import classification_report, confusion_matrix
import matplotlib.pyplot as plt

# Генерация синтетических данных телеком компании
np.random.seed(42)
n_samples = 2000

data = pd.DataFrame({
    'tenure_months': np.random.randint(1, 72, n_samples),
    'monthly_charges': np.random.uniform(20, 120, n_samples),
    'total_charges': np.random.uniform(100, 8000, n_samples),
    'contract_type': np.random.choice(['month_to_month', 'one_year', 'two_year'], n_samples),
    'payment_method': np.random.choice(['electronic', 'mailed_check', 'bank_transfer', 'credit_card'], n_samples),
    'tech_support': np.random.choice([0, 1], n_samples),
    'online_security': np.random.choice([0, 1], n_samples),
    'num_services': np.random.randint(1, 8, n_samples),
    'customer_service_calls': np.random.poisson(2, n_samples)
})

# Генерация целевой переменной с логической зависимостью
churn_probability = (
    0.15 * (data['tenure_months'] < 12) + 0.12 * (data['contract_type'] == 'month_to_month') + 0.10 * (data['customer_service_calls'] > 4) +
    0.08 * (data['tech_support'] == 0) +
    0.05 * (data['monthly_charges'] > 80)
)
data['churn'] = (np.random.random(n_samples) < churn_probability).astype(int)

# Кодирование категориальных признаков
data_encoded = pd.get_dummies(data, columns=['contract_type', 'payment_method'], drop_first=True)

# Разделение на признаки и целевую переменную
X = data_encoded.drop('churn', axis=1)
y = data_encoded['churn']

# Разделение на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=42, stratify=y)

# Построение базовой модели
base_tree = DecisionTreeClassifier(random_state=42)
base_tree.fit(X_train, y_train)

print("Базовая модель (без ограничений):")
print(f"Глубина дерева: {base_tree.get_depth()}")
print(f"Число листьев: {base_tree.get_n_leaves()}")
print(f"Accuracy на обучающей выборке: {base_tree.score(X_train, y_train):.4f}")
print(f"Accuracy на тестовой выборке: {base_tree.score(X_test, y_test):.4f}")

# Подбор гиперпараметров через Grid Search
param_grid = {
    'max_depth': [3, 5, 7, 10],
    'min_samples_split': [10, 20, 50],
    'min_samples_leaf': [5, 10, 20],
    'criterion': ['gini', 'entropy']
}

grid_search = GridSearchCV(
    DecisionTreeClassifier(random_state=42),
    param_grid,
    cv=5,
    scoring='f1',
    n_jobs=-1
)

grid_search.fit(X_train, y_train)

print("\nЛучшие параметры:", grid_search.best_params_)
print(f"Лучший F1-score на кросс-валидации: {grid_search.best_score_:.4f}")

# Обучение оптимальной модели
optimal_tree = grid_search.best_estimator_
y_pred = optimal_tree.predict(X_test)

print("\nОптимальная модель:")
print(f"Глубина дерева: {optimal_tree.get_depth()}")
print(f"Число листьев: {optimal_tree.get_n_leaves()}")
print(f"Accuracy на тестовой выборке: {optimal_tree.score(X_test, y_test):.4f}")

print("\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=['No Churn', 'Churn']))

# Анализ важности признаков
feature_importance = pd.DataFrame({
    'feature': X.columns,
    'importance': optimal_tree.feature_importances_
}).sort_values('importance', ascending=False)

print("\nВажность признаков (топ-10):")
print(feature_importance.head(10))

# Визуализация дерева
fig, axes = plt.subplots(2, 2, figsize=(20, 16))

# График 1: Полное дерево (первые 3 уровня)
plot_tree(optimal_tree, 
          max_depth=3,
          feature_names=X.columns,
          class_names=['No Churn', 'Churn'],
          filled=True,
          ax=axes[0, 0],
          fontsize=9)
axes[0, 0].set_title('Структура дерева (первые 3 уровня)', fontsize=12, weight='bold')

# График 2: Важность признаков
top_features = feature_importance.head(10)
axes[0, 1].barh(range(len(top_features)), top_features['importance'], color='#2C3E50')
axes[0, 1].set_yticks(range(len(top_features)))
axes[0, 1].set_yticklabels(top_features['feature'])
axes[0, 1].set_xlabel('Importance', fontsize=10)
axes[0, 1].set_title('Важность признаков (топ-10)', fontsize=12, weight='bold')
axes[0, 1].invert_yaxis()

# График 3: Матрица ошибок
cm = confusion_matrix(y_test, y_pred)
im = axes[1, 0].imshow(cm, cmap='Blues')
axes[1, 0].set_xticks([0, 1])
axes[1, 0].set_yticks([0, 1])
axes[1, 0].set_xticklabels(['No Churn', 'Churn'])
axes[1, 0].set_yticklabels(['No Churn', 'Churn'])
axes[1, 0].set_xlabel('Predicted', fontsize=10)
axes[1, 0].set_ylabel('Actual', fontsize=10)
axes[1, 0].set_title('Confusion Matrix', fontsize=12, weight='bold')

for i in range(2):
    for j in range(2):
        text = axes[1, 0].text(j, i, cm[i, j], ha="center", va="center", color="black", fontsize=14)

plt.colorbar(im, ax=axes[1, 0])

# График 4: Сравнение качества на разных глубинах
depths = range(1, 16)
train_scores = []
test_scores = []

for depth in depths:
    tree = DecisionTreeClassifier(max_depth=depth, random_state=42)
    tree.fit(X_train, y_train)
    train_scores.append(tree.score(X_train, y_train))
    test_scores.append(tree.score(X_test, y_test))

axes[1, 1].plot(depths, train_scores, label='Train Accuracy', color='#2C3E50', linewidth=2)
axes[1, 1].plot(depths, test_scores, label='Test Accuracy', color='#E74C3C', linewidth=2)
axes[1, 1].axvline(x=optimal_tree.get_depth(), color='#27AE60', linestyle='--', linewidth=2, label=f'Optimal Depth ({optimal_tree.get_depth()})')
axes[1, 1].set_xlabel('Max Depth', fontsize=10)
axes[1, 1].set_ylabel('Accuracy', fontsize=10)
axes[1, 1].set_title('Accuracy vs Tree Depth', fontsize=12, weight='bold')
axes[1, 1].legend()
axes[1, 1].grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

# Текстовое представление дерева
tree_rules = export_text(optimal_tree, feature_names=list(X.columns), max_depth=3)
print("\nПравила дерева (первые 3 уровня):")
print(tree_rules)
Базовая модель (без ограничений):
Глубина дерева: 21
Число листьев: 233
Accuracy на обучающей выборке: 1.0000
Accuracy на тестовой выборке: 0.7920

Лучшие параметры: {'criterion': 'entropy', 'max_depth': 10, 'min_samples_leaf': 5, 'min_samples_split': 10}
Лучший F1-score на кросс-валидации: 0.1642

Оптимальная модель:
Глубина дерева: 10
Число листьев: 89
Accuracy на тестовой выборке: 0.8360

Комплексный анализ дерева решений для задачи прогноза оттока клиентов. Панель A: структура дерева с визуализацией разбиений и распределения классов в узлах. Панель B: важность признаков, где monthly_charges и tenure_months доминируют. Панель C: матрица ошибок с преобладанием корректных предсказаний класса No Churn. Панель D: динамика точности в зависимости от глубины дерева, демонстрирующая переобучение после 8 уровней. Оптимальная глубина 10 обеспечивает баланс между точностью и обобщающей способностью

Рис. 4: Комплексный анализ дерева решений для задачи прогноза оттока клиентов. Панель A: структура дерева с визуализацией разбиений и распределения классов в узлах. Панель B: важность признаков, где monthly_charges и tenure_months доминируют. Панель C: матрица ошибок с преобладанием корректных предсказаний класса No Churn. Панель D: динамика точности в зависимости от глубины дерева, демонстрирующая переобучение после 8 уровней. Оптимальная глубина 10 обеспечивает баланс между точностью и обобщающей способностью

Classification Report:
              precision    recall  f1-score   support

    No Churn       0.88      0.94      0.91       432
       Churn       0.33      0.21      0.25        68

    accuracy                           0.84       500
   macro avg       0.61      0.57      0.58       500
weighted avg       0.81      0.84      0.82       500


Важность признаков (топ-10):
                      feature  importance
1             monthly_charges    0.297364
0               tenure_months    0.186262
2               total_charges    0.182591
6      customer_service_calls    0.089506
8      contract_type_two_year    0.063956
3                tech_support    0.059896
4             online_security    0.042064
5                num_services    0.039216
7      contract_type_one_year    0.030980
9  payment_method_credit_card    0.008166

Представленный выше код реализует полный пайплайн анализа оттока клиентов. Синтетические данные имитируют реальный датасет телеком компании с признаками:

  • Ежемесячные платежи (monthly_charges);
  • Срок обслуживания в месяцах (tenure_months);
  • Общая сумма платежей (total_charges);
  • Количество обращений в службу поддержки (customer_service_calls);
  • Контракт на два года (признак) (contract_type_two_year);
  • Техническая поддержка (подключена/не подключена) (tech_support);
  • Онлайн-защита (подключена/не подключена) (online_security);
  • Количество подключенных услуг (num_services);
  • Контракт на один год (признак) (contract_type_one_year);
  • Оплата кредитной картой (признак) (payment_method_credit_card).
👉🏻  Прогнозирование трафика и конверсий сайта с помощью SVM, SVR (опорных векторов)

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

Базовая модель без ограничений демонстрирует переобучение: высокая точность на обучающей выборке при существенно меньшей на тестовой. Это происходит довольно часто с деревьями решений.

Для подбора оптимальных гиперпараметров используется Grid search, он находит оптимальную комбинацию через 5-fold кросс-валидацию с метрикой F1-score, сбалансированной для несбалансированных классов.

Оптимальная модель показывает глубину 5-7 уровней с минимальным числом объектов в листьях 10-20. Матрица ошибок показывает распределение предсказаний: модель лучше идентифицирует клиентов без оттока (специфичность выше чувствительности), что типично для несбалансированных датасетов.

Анализ важности признаков выявляет, что ежемесячные платежи (monthly_charges), срок обслуживания в месяцах (tenure_months) и общая сумма платежей (total_charges) доминируют в предсказаниях. Признаки с важностью менее 0.05 вносят минимальный вклад и могут быть удалены для упрощения модели. Визуализация первых уровней дерева показывает ключевые пороговые значения: клиенты с tenure_months < 12 и contract_type = month_to_month формируют сегмент высокого риска.

Правила дерева (первые 3 уровня):
|--- tenure_months <= 11.50
|   |--- monthly_charges <= 62.88
|   |   |--- monthly_charges <= 45.98
|   |   |   |--- num_services <= 3.50 | | | | |--- truncated branch of depth 2 | | | |--- num_services >  3.50
|   |   |   |   |--- truncated branch of depth 4
|   |   |--- monthly_charges >  45.98
|   |   |   |--- contract_type_two_year <= 0.50 | | | | |--- class: 0 | | | |--- contract_type_two_year >  0.50
|   |   |   |   |--- truncated branch of depth 2
|   |--- monthly_charges >  62.88
|   |   |--- tenure_months <= 7.50
|   |   |   |--- monthly_charges <= 65.79 | | | | |--- class: 1 | | | |--- monthly_charges >  65.79
|   |   |   |   |--- truncated branch of depth 7
|   |   |--- tenure_months >  7.50
|   |   |   |--- total_charges <= 5274.54 | | | | |--- truncated branch of depth 3 | | | |--- total_charges >  5274.54
|   |   |   |   |--- truncated branch of depth 4
|--- tenure_months >  11.50
|   |--- contract_type_one_year <= 0.50
|   |   |--- contract_type_two_year <= 0.50
|   |   |   |--- total_charges <= 640.92 | | | | |--- truncated branch of depth 4 | | | |--- total_charges >  640.92
|   |   |   |   |--- truncated branch of depth 7
|   |   |--- contract_type_two_year >  0.50
|   |   |   |--- monthly_charges <= 79.03 | | | | |--- truncated branch of depth 7 | | | |--- monthly_charges >  79.03
|   |   |   |   |--- truncated branch of depth 7
|   |--- contract_type_one_year >  0.50
|   |   |--- tech_support <= 0.50
|   |   |   |--- total_charges <= 7866.02 | | | | |--- truncated branch of depth 7 | | | |--- total_charges >  7866.02
|   |   |   |   |--- class: 0
|   |   |--- tech_support >  0.50
|   |   |   |--- customer_service_calls <= 4.50 | | | | |--- truncated branch of depth 5 | | | |--- customer_service_calls >  4.50
|   |   |   |   |--- truncated branch of depth 2

Заключение

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

👉🏻  Чем отличается финансовый ML от других видов машинного обучения

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

Однако сами по себе деревья часто склонны к переобучению, плюс чувствительны к шуму и небольшим изменениям данных, поэтому для повышения устойчивости и качества прогнозов на практике широко применяются ансамблевые методы, такие как Random Forest и Gradient Boosting, которые комбинируют множество деревьев и обеспечивают более высокую точность при сохранении объяснимости ключевых факторов.