Почему дерево решений так легко полюбить на старте
Дерево решений очень быстро становится понятным, потому что его логика близка к человеческому способу рассуждать. Мы и сами часто принимаем решения через цепочку вопросов: если доход выше порога, если клиент давно активен, если число жалоб мало. Именно поэтому дерево хорошо воспринимается как первая модель, в которой видно, как из признаков рождается итоговое решение.
Но за этой простотой скрывается важная инженерная проблема: дерево с готовностью подстраивается под детали выборки. И если не ограничить глубину, минимальный размер листа или критерий разбиения, оно быстро начинает запоминать шум.
Как дерево понимает, где разрезать данные
$$ Gini = 1 - \sum_{c=1}^{C} p_c^2 $$Критерий Джини измеряет неоднородность узла. Чем меньше значение, тем чище узел по классам. Дерево ищет такие разбиения, после которых дочерние узлы становятся более однородными.
Gini— индекс неоднородности узлаC— число классов в задачеp_c— доля объектов класса c внутри узла
Интуитивно дерево пытается задавать такие вопросы, после которых в ветвях остается меньше смешанных случаев. Если после разбиения почти все положительные объекты ушли в одну сторону, а отрицательные — в другую, дерево считает такой сплит удачным.
Почему дерево одновременно полезно и опасно
Полезно оно тем, что дает прозрачные правила. Можно буквально прочитать, при каких условиях модель пришла к решению. Это особенно хорошо для обучения, интерпретации и первых baseline-решений. Опасно дерево тем, что любит очень детально подгонять пространство признаков под текущую выборку. Из-за этого train-качество часто выглядит прекрасным, а validation уже заметно хуже.
Поэтому дерево почти всегда нужно рассматривать не только как модель, но и как объект контроля сложности.
Как посмотреть на дерево в Python
import pandas as pd # задаем компактный набор признаков для дерева решений
from sklearn.tree import DecisionTreeClassifier # берем модель, которая строит последовательность разбиений
frame = pd.DataFrame({ # создаем простой датасет с поведенческими признаками
'sessions': [1, 2, 3, 4, 5, 6, 7, 8], # число сессий пользователя
'avg_time': [2, 3, 3, 4, 6, 7, 8, 9], # средняя длительность активности
'target': [0, 0, 0, 0, 1, 1, 1, 1], # целевой класс для классификации
})
X = frame[['sessions', 'avg_time']] # выделяем признаки для дерева
y = frame['target'] # сохраняем target отдельно
tree = DecisionTreeClassifier(max_depth=2, random_state=42) # ограничиваем глубину, чтобы не переобучить пример
tree.fit(X, y) # обучаем дерево искать выгодные разбиения признаков
print(tree.predict(X).tolist()) # смотрим, как модель принимает решения на обучающих данных
print(tree.feature_importances_.round(3).tolist()) # читаем относительную важность каждого признакаДаже такой компактный пример показывает главное: дерево — это не магия, а последовательность разбиений пространства признаков. Чем лучше ты это видишь, тем легче потом понимать и случайный лес, и бустинг на деревьях.