Что такое прунинг (обрезка) деревьев решений?

Ответ

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

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

Основные виды прунинга:

  1. Пре-прунинг (Pre-pruning, "ранняя остановка"): Рост дерева останавливается до того, как оно полностью подгонится под обучающие данные. Контролируется гиперпараметрами.

    • Примеры ограничений: max_depth (максимальная глубина), min_samples_split (минимальное количество образцов для разделения узла), min_samples_leaf (минимальное количество образцов в листе).
  2. Пост-прунинг (Post-pruning): Сначала строится полное, потенциально переобученное дерево, а затем его ветви рекурсивно заменяются на листовые узлы. Критерием для замены является сравнение ошибки на отдельной валидационной выборке или с использованием критерия минимальной стоимости-сложности.

Пример пост-прунинга с использованием Cost-Complexity Pruning в scikit-learn:

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

# Загрузка данных
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

# 1. Строим большое дерево (с минимальным пре-прунингом)
clf_full = DecisionTreeClassifier(random_state=42, min_samples_leaf=5)
clf_full.fit(X_train, y_train)

# 2. Вычисляем эффективные альфы для прунинга
path = clf_full.cost_complexity_pruning_path(X_train, y_train)
ccp_alphas, impurities = path.ccp_alphas, path.impurities

# 3. Обучаем серию деревьев с разными ccp_alpha
clfs = []
for ccp_alpha in ccp_alphas:
    clf = DecisionTreeClassifier(random_state=42, ccp_alpha=ccp_alpha)
    clf.fit(X_train, y_train)
    clfs.append(clf)

# 4. Выбираем дерево с лучшей точностью на валидации/тесте
# (На практике нужна отдельная валидационная выборка)
test_accuracies = [clf.score(X_test, y_test) for clf in clfs]
best_alpha = ccp_alphas[test_accuracies.index(max(test_accuracies))]

# 5. Финальная обрезанная модель
clf_pruned = DecisionTreeClassifier(random_state=42, ccp_alpha=best_alpha)
clf_pruned.fit(X_train, y_train)
print(f"Глубина полного дерева: {clf_full.tree_.max_depth}")
print(f"Глубина обрезанного дерева: {clf_pruned.tree_.max_depth}")

Преимущества: Снижение переобучения, уменьшение сложности модели (интерпретируемость), часто — повышение скорости предсказания. Недостатки: Пост-прунинг требует дополнительных вычислительных ресурсов для построения полного дерева, а агрессивная обрезка может привести к недообучению.

Ответ 18+ 🔞

А, прунинг, говоришь? Ну это ж классика, ёпта! Представь себе: ты вырастил дерево решений, а оно такое пышное, ветвистое, каждую травинку в данных запомнило. Красота, да? Только вот доверия ебать ноль к нему, потому что на новых данных оно начнёт нести такую дичь, что мама не горюй. Оно не обобщает, а тупо зубрит, как ботаник-задрот перед сессией.

Так вот, прунинг (обрезка) — это когда ты берёшь секатор и начинаешь эту манду с ушами подстригать. Цель простая: отпилить все эти хлипкие, заумные веточки, которые только шум создают и переобучают модель. Жертвуешь капельку точности на учебниках (ну, да похуй), зато получаешь модель, которая не обосрётся при виде реальных данных. Улучшаешь обобщающую способность, короче.

Как это бывает, ёбааать:

  1. Пре-прунинг (Pre-pruning, "ранняя остановка"). Это как строгий папаша, который с самого начала не даёт дереву буйствовать. «Куда полез, пидарас шерстяной? На пятый уровень — и хватит! (max_depth)». Или: «Не лезь в узел, если там меньше 10 образцов сидит (min_samples_split)!». Дерево растёт уже с оглядкой, скованное. Просто, но можно и недообучить, если пережать.

  2. Пост-прунинг (Post-pruning). А вот это уже по-взрослому, хитрая жопа. Сначала ты даёшь дереву волю: «Валяй, распиздяй, расти как знаешь, запомни всё!». Оно вырастает — овердохуища большое и переобученное. А потом ты садишься и начинаешь его взъёбывать — рекурсивно отрезать самые бесполезные ветки, заменяя их на простые листики. Смотришь, не выросла ли ошибка на валидационной выборке. Не выросла? Значит, ветка была говённая, хуй с горы. Отрезал и забыл.

Вот, смотри, как это в scikit-learn делается, на примере пост-прунинга (Cost-Complexity):

from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split

# Грузим данные
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42)

# 1. Сначала растим дерево-переростка (с минимумом ограничений)
clf_full = DecisionTreeClassifier(random_state=42, min_samples_leaf=5)
clf_full.fit(X_train, y_train)

# 2. Вычисляем, насколько сильно можно его резать (эффективные альфы)
path = clf_full.cost_complexity_pruning_path(X_train, y_train)
ccp_alphas, impurities = path.ccp_alphas, path.impurities

# 3. Треним целый зоопарк деревьев — от лысого пенька до того же переростка
clfs = []
for ccp_alpha in ccp_alphas:
    clf = DecisionTreeClassifier(random_state=42, ccp_alpha=ccp_alpha)
    clf.fit(X_train, y_train)
    clfs.append(clf)

# 4. Выбираем того золотого середнячка, который на тесте/валидации лучше всех
# (В жизни, ясное дело, нужна отдельная валидационная выборка, а не тест!)
test_accuracies = [clf.score(X_test, y_test) for clf in clfs]
best_alpha = ccp_alphas[test_accuracies.index(max(test_accuracies))]

# 5. И вот оно — наше идеально подстриженное деревце!
clf_pruned = DecisionTreeClassifier(random_state=42, ccp_alpha=best_alpha)
clf_pruned.fit(X_train, y_train)
print(f"Глубина полного дерева: {clf_full.tree_.max_depth}")
print(f"Глубина обрезанного дерева: {clf_pruned.tree_.max_depth}")

Что в итоге? Плюсы очевидны: модель меньше бздит на новых данных, проще (можно хоть начальнику показать, не охуев от её сложности), да и предсказывает быстрее. Минусы тоже есть: пост-прунинг — это дополнительные телодвижения и вычисления. А если с секатором перестараться, то можно так впендюрить, что от модели останется один полупидор-пенёк, который вообще нихрена не предсказывает. Баланс, чувак, везде нужен баланс.