Como fazer pruning de árvores em Machine Learning: passo a passo
Explica-se como reduzir o overfitting em modelos de árvore através de pruning (poda) e porque isso melhora a generalização. Vamos ver técnicas de pré-poda e pós-poda com um exemplo prático em Python usando DecisionTreeClassifier. A ideia central é simples: árvores muito profundas ajustam-se em demasia ao ruído do treino; ao podar ramos irrelevantes reduz-se a variância e obtém-se um modelo mais simples e interpretável.
Pré-requisitos
- Python 3.8+ instalado
- Bibliotecas: scikit-learn, pandas, numpy, matplotlib (ex.: pip install scikit-learn pandas numpy matplotlib)
- Conhecimentos básicos de classificação e treino/teste (train_test_split, métricas como accuracy)
- Máquina com recursos básicos: Iris ocupa 150 linhas, CPUs modernos são suficientes
Passo 1: Porquê pruning em Machine Learning
Árvores de decisão costumam ajustar-se em demasia aos dados de treino (overfitting). O pruning remove ramos pouco relevantes para simplificar a árvore e melhorar a performance em dados novos. Existem duas abordagens comuns: pré-poda (limitando max_depth, min_samples_leaf, etc.) e pós-poda (cost-complexity pruning). A pré-poda atua durante o treino (impede o crescimento), enquanto a pós-poda faz crescer a árvore completa e depois corta ramos com baixo benefício. Na prática, quando a diferença entre a accuracy no treino e no teste é grande (por exemplo >10 pontos percentuais), é sinal para aplicar pruning.
Passo 2: Preparar um conjunto de dados de exemplo
Usa um dataset padrão para demonstrar: o conjunto Iris ou um dataset simulado. Aqui usamos o Iris para simplificar a reprodução. Iris tem 150 amostras (50 por classe), pelo que com test_size=0.3 obténs 105 amostras de treino e 45 de teste — suficiente para ver tendências de overfitting sem complicar.
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import pandas as pd
data = load_iris()
X = pd.DataFrame(data.data, columns=data.feature_names)
y = data.target
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)
# X_train.shape => (105, 4), X_test.shape => (45, 4)
Passo 3: Treinar uma árvore sem pruning (baseline)
Treina um DecisionTreeClassifier sem restrições para observar o overfitting. Avalia treino e teste. Tipicamente uma árvore sem restrições atinge 100% no treino (ajuste perfeito) e pode ter 95–99% no teste em Iris; a diferença revela o grau de overfitting.
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
clf = DecisionTreeClassifier(random_state=42)
clf.fit(X_train, y_train)
train_acc = accuracy_score(y_train, clf.predict(X_train))
test_acc = accuracy_score(y_test, clf.predict(X_test))
print(f"Baseline train acc: {train_acc:.3f}, test acc: {test_acc:.3f}")
Passo 4: Pré-poda — usar parâmetros para controlar a complexidade
Experimenta limitar max_depth, min_samples_leaf e min_samples_split. Estes parâmetros impedem que a árvore cresça arbitrariamente. Por exemplo, com max_depth=3 e min_samples_leaf=5 normalmente reduces a profundidade e o número de nós drasticamente; no Iris isso tende a manter a test accuracy próxima do baseline (p.ex. 0.93–0.97) mas com uma lacuna treino/teste muito menor.
clf_pre = DecisionTreeClassifier(random_state=42, max_depth=3, min_samples_leaf=5)
clf_pre.fit(X_train, y_train)
print("Pre-pruning train:", accuracy_score(y_train, clf_pre.predict(X_train)))
print("Pre-pruning test:", accuracy_score(y_test, clf_pre.predict(X_test)))
Passo 5: Pós-poda — cost-complexity pruning (ccp_alpha)
scikit-learn implementa cost-complexity pruning. Primeiro obtém-se uma sequência de ccp_alpha possíveis e depois escolhe-se o melhor pelo desempenho em validação. O algoritmo devolve tipicamente 5–30 valores de alpha, desde muito pequeno (quase sem poda) até grande (árvore reduzida ao nó raiz). Avalia cada alpha e escolhe aquele que maximiza a métrica no conjunto de validação ou teste. Atenção: evita escolher o alpha com base apenas no teste final; usa validação cruzada quando possível.
# obter caminho de poda
path = DecisionTreeClassifier(random_state=42).cost_complexity_pruning_path(X_train, y_train)
ccp_alphas = path.ccp_alphas
clfs = []
for ccp in ccp_alphas:
clf_tmp = DecisionTreeClassifier(random_state=42, ccp_alpha=ccp)
clf_tmp.fit(X_train, y_train)
clfs.append((ccp, clf_tmp))
# avaliar cada alpha
import numpy as np
train_scores = [accuracy_score(y_train, m.predict(X_train)) for _, m in clfs]
test_scores = [accuracy_score(y_test, m.predict(X_test)) for _, m in clfs]
best_idx = int(np.argmax(test_scores))
best_alpha, best_model = clfs[best_idx]
print(f"Melhor ccp_alpha: {best_alpha}, test acc: {test_scores[best_idx]:.3f}")
Passo 6: Escolher o modelo final e validar com cross-validation
Confirma a robustez do alpha escolhido usando cross-validation para evitar seleccionar por acaso no conjunto de teste. Por exemplo, faz 5-fold CV e compara a média e o desvio-padrão das accuracies. Valores consistentes (p.ex. mean=0.95, std<0.03) indicam estabilidade.
from sklearn.model_selection import cross_val_score
final_clf = DecisionTreeClassifier(random_state=42, ccp_alpha=best_alpha)
scores = cross_val_score(final_clf, X, y, cv=5)
print("CV accuracy:", scores.mean(), scores)
Verificar o resultado
Compara as métricas: se o modelo podado tem performance semelhante no teste mas com menor diferença treino-teste, o pruning funcionou. Verifica também a simplicidade da árvore (profundidade e número de nós) para confirmar redução da complexidade. Ex.: Baseline depth: 5, nodes: 35 vs Pruned depth: 3, nodes: 9 — isto traduz-se num modelo mais interpretável e menos sensível ao ruído.
print("Baseline depth:", clf.get_depth(), "nodes:", clf.tree_.node_count)
print("Pruned depth:", best_model.get_depth(), "nodes:", best_model.tree_.node_count)
Conclusão
O pruning (pré-poda e pós-poda) ajuda a reduzir o overfitting e a obter modelos de árvore mais interpretáveis e robustos. Próximos passos: testar com conjuntos mais complexos (p.ex. centenas de milhares de registos), usar validação em k-fold para seleccionar parâmetros e comparar com ensembles (RandomForest, GradientBoosting) que reduzem a variância sem tanto tuning manual. Dica prática: quando vires uma grande diferença entre treino e teste, experimenta primeiro max_depth e min_samples_leaf; se precisares de mais precisão usa cost-complexity pruning (ccp_alpha) com validação cruzada.