Machine Learning: Árvores de Decisão e Florestas Aleatórias
Última atualização: 2026-08-26
Uma árvore de decisão é como uma árvore de perguntas — cada nó faz uma pergunta, e você segue a resposta até uma folha, que é a sua previsão.
1. O que você vai aprender
- Fundamentos de árvores de decisão: ganho de informação / razão de ganho / índice Gini, e a família de algoritmos ID3 / C4.5 / CART
- Poda de árvores: pré-poda (max_depth / min_samples_leaf) e pós-poda
- Floresta Aleatória: a ideia de Bagging, seleção aleatória de features e estimativa OOB
- Análise de importância de features: diminuição média de impureza vs. importância por permutação
- A classificação de categorias de produto do Bob: usando uma Floresta Aleatória para prever se um produto será um sucesso de vendas
2. A história real de um gerente de produto de e-commerce
(1) O problema: previsão de produtos de sucesso dependia totalmente de intuição, com uma taxa de acerto inferior a 30%
O Bob precisava escolher produtos em potencial para serem sucesso de vendas entre 1 mil novos itens. No passado, isso era puro julgamento, e sua taxa de acerto era de apenas 30%. Cada produto de sucesso traz em média 500 mil USD de receita mensal, então perder um é uma perda enorme. A intuição não pode ser quantificada, reutilizada ou iterada.
(2) A solução com Floresta Aleatória
Uma Floresta Aleatória pode descobrir automaticamente padrões de produtos de sucesso a partir de dados históricos de produtos — faixa de preço, características da categoria, tendências de vendas da primeira semana — e naturalmente fornece um ranking de importância das features.
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_estimators=100, oob_score=True, random_state=42)
rf.fit(X_train, y_train)
print(f"OOB Accuracy: {rf.oob_score_:.3f}")
print(f"Feature mais importante: {feature_names[rf.feature_importances_.argmax()]}")
(3) O resultado: a precisão na previsão de produtos de sucesso subiu de 30% para 65%
O Bob substituiu a intuição por uma Floresta Aleatória, elevando a precisão da previsão de produtos de sucesso de 30% para 65%. Ele agora identifica 5 produtos de sucesso extras por mês, acrescentando cerca de 3 milhões de USD em receita anual.
3. Como funcionam as árvores de decisão
(1) Critérios de divisão
Em cada nó, uma árvore de decisão escolhe a melhor feature e limiar para fazer a divisão. Existem três critérios comuns:
graph TB
ROOT[Nó Raiz<br/>Todos os Dados] --> SPLIT1{Feature: preco<br/>Limiar: 50 USD}
SPLIT1 -->|≤ 50| LEFT[Filho Esquerdo<br/>60% produtos de sucesso]
SPLIT1 -->|> 50| RIGHT[Filho Direito<br/>10% produtos de sucesso]
LEFT --> SPLIT2{Feature: vendas_primeira_semana}
SPLIT2 -->|> 500| LEAF1[Folha: SUCESSO<br/>90% confiança]
SPLIT2 -->|≤ 500| LEAF2[Folha: NÃO SUCESSO<br/>40% confiança]
RIGHT --> LEAF3[Folha: NÃO SUCESSO<br/>5% confiança]
| Critério | Fórmula central | Preferência | Família de algoritmos |
|---|---|---|---|
| Ganho de informação | $H(D) - H(D | A)$ | Favorece features com muitos valores |
| Razão de ganho | Ganho de informação / Valor intrínseco | Corrige o viés de multi-valor | C4.5 |
| Impureza de Gini | $1 - \sum p_i^2$ | Favorece pureza de classes grandes | CART |
▶ Exemplo: Classificação com árvore de decisão na Iris
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
import matplotlib.pyplot as plt
iris = load_iris()
X_train, X_test, y_train, y_test = train_test_split(
iris.data, iris.target, test_size=0.3, random_state=42, stratify=iris.target
)
# Treinar com pré-poda
tree = DecisionTreeClassifier(max_depth=3, min_samples_leaf=5, random_state=42)
tree.fit(X_train, y_train)
y_pred = tree.predict(X_test)
print(f"Acurácia: {accuracy_score(y_test, y_pred):.4f}")
print(f"Profundidade da árvore: {tree.get_depth()}")
print(f"Número de folhas: {tree.get_n_leaves()}")
# Visualizar a árvore
fig, ax = plt.subplots(figsize=(14, 8))
plot_tree(tree, feature_names=iris.feature_names,
class_names=iris.target_names, filled=True, rounded=True, ax=ax)
plt.title("Árvore de Decisão (max_depth=3)")
plt.tight_layout()
plt.savefig("decision_tree.png", dpi=150)
Saída:
# Executa com sucesso
(2) Estratégias de poda
▶ Exemplo: Comparando parâmetros de pré-poda
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
X, y = load_iris(return_X_y=True)
configs = {
"Sem poda": DecisionTreeClassifier(random_state=42),
"max_depth=2": DecisionTreeClassifier(max_depth=2, random_state=42),
"max_depth=3": DecisionTreeClassifier(max_depth=3, random_state=42),
"min_samples_leaf=5": DecisionTreeClassifier(min_samples_leaf=5, random_state=42),
"max_depth=3 + min_samples_leaf=5": DecisionTreeClassifier(
max_depth=3, min_samples_leaf=5, random_state=42),
}
for name, model in configs.items():
scores = cross_val_score(model, X, y, cv=5, scoring="accuracy")
model.fit(X, y)
print(f"{name:35s}: Acurácia={scores.mean():.3f}, Folhas={model.get_n_leaves()}")
Saída:
# Executa com sucesso
| Tipo de poda | Parâmetro | Efeito | Valor recomendado |
|---|---|---|---|
| Pré-poda | max_depth | Limita a profundidade da árvore | 3-10 |
| Pré-poda | min_samples_leaf | Amostras mínimas por folha | 5-20 |
| Pré-poda | min_samples_split | Amostras mínimas para dividir | 10-40 |
| Pré-poda | max_features | Features consideradas por divisão | sqrt(n_features) |
| Pós-poda | ccp_alpha | Poda por custo-complexidade | Escolha via GridSearch |
4. Floresta Aleatória
(1) A ideia de Bagging
Floresta Aleatória = Bagging (Bootstrap Aggregating) + seleção aleatória de features.
graph TB
DATA[Dados Originais] --> B1[Amostra Bootstrap 1]
DATA --> B2[Amostra Bootstrap 2]
DATA --> B3[Amostra Bootstrap 3]
DATA --> BN[Amostra Bootstrap N]
B1 --> T1[Árvore 1<br/>Subconjunto Aleatório de Features]
B2 --> T2[Árvore 2<br/>Subconjunto Aleatório de Features]
B3 --> T3[Árvore 3<br/>Subconjunto Aleatório de Features]
BN --> TN[Árvore N<br/>Subconjunto Aleatório de Features]
T1 --> VOTE[Voto por Maioria<br/>/ Média]
T2 --> VOTE
T3 --> VOTE
TN --> VOTE
▶ Exemplo: Classificação com Floresta Aleatória + avaliação OOB
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
rf = RandomForestClassifier(
n_estimators=100,
max_depth=None,
oob_score=True,
random_state=42,
)
rf.fit(X_train, y_train)
print(f"OOB Score: {rf.oob_score_:.4f}")
print(f"Acurácia no Teste: {accuracy_score(y_test, rf.predict(X_test)):.4f}")
print(f"Número de árvores: {rf.n_estimators}")
Saída:
# Executa com sucesso
(2) Ajuste de hiperparâmetros
▶ Exemplo: Regressão com Floresta Aleatória — Previsão de vendas do SalesPredict
from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.metrics import mean_absolute_error, r2_score
import numpy as np
rng = np.random.default_rng(42)
n = 500
X = rng.uniform(0, 100, (n, 6))
y = 50 + 0.8 * X[:, 0] + 1.2 * X[:, 1] - 0.5 * X[:, 2] + rng.normal(0, 5, n)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# Comparar diferentes n_estimators
for n_est in [10, 50, 100, 200]:
rf = RandomForestRegressor(n_estimators=n_est, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
score = rf.score(X_test, y_test)
mae = mean_absolute_error(y_test, rf.predict(X_test))
print(f"n_estimators={n_est:3d}: R²={score:.4f}, MAE={mae:.2f}")
Saída:
# Executa com sucesso
5. Análise de importância das features
(1) Diminuição Média de Impureza vs. Importância por Permutação
▶ Exemplo: Comparando os dois métodos de importância de features
from sklearn.ensemble import RandomForestClassifier
from sklearn.inspection import permutation_importance
from sklearn.datasets import load_iris
import matplotlib.pyplot as plt
import numpy as np
X, y = load_iris(return_X_y=True)
feature_names = load_iris().feature_names
rf = RandomForestClassifier(n_estimators=100, random_state=42)
rf.fit(X, y)
# Método 1: MDI (embutido, rápido, mas com viés)
mdi_importance = rf.feature_importances_
# Método 2: Importância por permutação (mais lento, mas mais confiável)
perm_result = permutation_importance(rf, X, y, n_repeats=30, random_state=42, n_jobs=-1)
perm_importance = perm_result.importances_mean
# Comparar
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
y_pos = np.arange(len(feature_names))
axes[0].barh(y_pos, mdi_importance, color="#2196F3")
axes[0].set_yticks(y_pos)
axes[0].set_yticklabels(feature_names)
axes[0].set_title("Importância MDI")
axes[1].barh(y_pos, perm_importance, color="#4CAF50")
axes[1].set_yticks(y_pos)
axes[1].set_yticklabels(feature_names)
axes[1].set_title("Importância por Permutação")
plt.tight_layout()
plt.savefig("feature_importance.png", dpi=150)
Saída:
# Executa com sucesso
| Dimensão | Importância MDI | Importância por Permutação |
|---|---|---|
| Velocidade de cálculo | Rápida (calculada durante o treino) | Lenta (exige previsões repetidas) |
| Viés | Com viés em direção a features de alta cardinalidade | Sem viés |
| Aplicabilidade | Embutida em modelos de árvore | Funciona com qualquer modelo |
| Confiabilidade | Moderada | Mais confiável |
▶ Exemplo: Ranking de features para previsão de produtos de sucesso do Bob
from sklearn.ensemble import RandomForestClassifier
import pandas as pd
import numpy as np
rng = np.random.default_rng(42)
n = 1000
df = pd.DataFrame({
"price_usd": rng.uniform(10, 200, n),
"first_week_sales": rng.integers(10, 2000, n),
"category_trend_score": rng.uniform(0, 1, n),
"ad_budget_k": rng.uniform(1, 50, n),
"review_score": rng.uniform(1, 5, n),
"return_rate": rng.uniform(0, 0.3, n),
"stock_depth": rng.integers(10, 500, n),
})
df["is_hit"] = (
(df["first_week_sales"] > 500)
& (df["category_trend_score"] > 0.6)
& (df["price_usd"] < 100)
).astype(int) | (rng.random(n) < 0.05) # Adicionar 5% de ruído
X = df.drop(columns=["is_hit"])
y = df["is_hit"]
rf = RandomForestClassifier(n_estimators=200, random_state=42)
rf.fit(X, y)
importance = pd.DataFrame({
"feature": X.columns,
"importance": rf.feature_importances_,
}).sort_values("importance", ascending=False)
print("Importância das Features para Previsão de Produtos de Sucesso:")
print(importance.to_string(index=False))
Saída:
Importância das Features para Previsão de Produtos de Sucesso:
❓ Perguntas Frequentes
P: Por que uma Floresta Aleatória é melhor do que uma única árvore de decisão? R: Por duas razões — 1) Bagging reduz a variância (várias árvores votam e tiram a média); 2) a seleção aleatória de features diminui a correlação entre as árvores, tornando o ensemble mais forte. Uma única árvore tende a sobreajustar.
P: Um n_estimators maior é sempre melhor? R: Não necessariamente. Passado um certo ponto, os ganhos diminuem e você só adiciona tempo de computação. Normalmente 100-500 é suficiente. Observe o OOB score para ver quando ele se estabiliza.
P: O que é o OOB Score? R: Avaliação Out-of-Bag. Cada árvore é treinada com cerca de 63% dos dados, e os 37% restantes formam naturalmente um conjunto de validação. O OOB Score é a média de previsões entre todas as árvores em suas próprias amostras OOB — equivalente à validação cruzada, mas gratuita.
P: Árvores de decisão precisam de normalização? R: Não. Árvores de decisão fazem divisões por limiares de features, então não são afetadas pela escala. Mas se uma Floresta Aleatória for combinada com outros modelos (como um Scaler em um Pipeline), então você pode precisar dela.
P: E se todas as importâncias de features forem próximas de 0? R: Isso sugere que as features podem genuinamente ter pouca relação com o alvo. Tente interações entre features ou transformações não lineares, ou use a importância por permutação para verificar se o MDI está viesado.
P: Uma Floresta Aleatória consegue lidar com desbalanceamento de classes? R: Sim. Defina class_weight="balanced" ou class_weight={0:1, 1:10}. Você também pode combiná-la com oversampling (SMOTE) ou undersampling.
📖 Resumo
- Uma árvore de decisão é construída recursivamente usando um critério de divisão (ganho de informação / índice Gini) até que uma condição de parada seja atingida
- A poda previne o sobreajuste: pré-poda (limitar profundidade / contagem de amostras por folha) + pós-poda (ccp_alpha)
- Floresta Aleatória = Bagging + seleção aleatória de features, reduzindo a variância e resistindo ao sobreajuste
- O OOB Score é a "validação cruzada gratuita" da Floresta Aleatória, substituindo a avaliação por divisão treino/teste
- Dois métodos de importância de features: MDI (rápido, mas com viés) vs. Permutação (lento, mas confiável)
- A Floresta Aleatória é o "canivete suíço" do toolbox de ML — quase sempre uma escolha sólida como linha de base
📝 Exercícios
- Básico (Dificuldade ⭐): Use DecisionTreeClassifier para classificar a Iris, variando max_depth de 1 a 5, e plote a curva de acurácia vs. profundidade. Dica: itere sobre o treino com
cross_val_score. - Intermediário (Dificuldade ⭐⭐): Use RandomForestRegressor no California Housing, comparando R² e OOB Score para n_estimators=[10,50,100,200]. Dica: defina
oob_score=True. - Desafio (Dificuldade ⭐⭐⭐): Implemente o pipeline completo de previsão de produtos de sucesso do Bob — gere dados simulados, treine um RandomForestClassifier, analise features com permutation_importance, lide com desbalanceamento com class_weight (produtos de sucesso < 10%) e produza um classification report. Dica: consulte o exemplo de produtos de sucesso na Seção 5.
← Anterior: Regressão Logística | Próximo: Máquinas de Vetores de Suporte →