Árvore de Decisão
Aprenda como funcionam as árvores de decisão, como construir árvores de classificação e regressão em Python com scikit-learn e visualizá-las.
Uma árvore de decisão é um algoritmo supervisionado de aprendizado de máquina que faz previsões aprendendo uma hierarquia de regras if-then-else a partir de dados de treinamento. Cada nó interno testa uma característica, cada ramo representa um resultado desse teste, e cada nó folha contém uma previsão (um rótulo de classe para classificação, ou um valor numérico para regressão).
Este capítulo aborda:
- Como as árvores de decisão dividem dados usando medidas de impureza (Gini e entropia)
- Como construir uma árvore de classificação e uma árvore de regressão em Python com
scikit-learn - Como controlar a profundidade da árvore e evitar overfitting com hiperparâmetros
- Como visualizar e inspecionar uma árvore treinada
- Vantagens, limitações e quando usar árvores de decisão
Como uma Árvore de Decisão Divide os Dados
Durante o treinamento, o algoritmo pesquisa cada característica e cada limiar possível para encontrar a divisão que mais reduz a impureza — uma medida de quão misturadas as classes estão em um nó.
Duas medidas de impureza são comuns no scikit-learn:
Impureza de Gini
A impureza de Gini mede a probabilidade de classificar incorretamente uma amostra escolhida aleatoriamente caso ela fosse rotulada de acordo com a distribuição de classes no nó.
Gini(node) = 1 - Σ pᵢ²Um nó puro (todas as amostras pertencem a uma classe) tem Gini = 0. Um nó com máxima mistura tem Gini próximo de 0,5 para classificação binária.
Entropia e Ganho de Informação
A entropia vem da teoria da informação. É maximizada quando as classes estão igualmente distribuídas e zero quando o nó é puro.
Entropy(node) = -Σ pᵢ log₂(pᵢ)O ganho de informação é a queda na entropia após uma divisão. O algoritmo escolhe a divisão que produz o maior ganho de informação. No scikit-learn, você escolhe entre as duas medidas pelo parâmetro criterion ("gini" é o padrão).
Divisão Recursiva
A divisão se repete recursivamente em cada nó filho até que uma condição de parada seja atingida: o nó está puro, nenhuma característica melhora a impureza, ou um limite de profundidade/tamanho é alcançado. Isso produz a estrutura de árvore binária.
Árvore de Classificação em Python
O conjunto de dados Iris tem 150 amostras e 4 características numéricas. O objetivo é prever uma das três espécies de flores.
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, classification_report
# Load dataset
data = load_iris()
X, y = data.data, data.target
# Split: 80 % train, 20 % test
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
# Train — limit depth to 3 to keep the tree readable
clf = DecisionTreeClassifier(criterion="gini", max_depth=3, random_state=42)
clf.fit(X_train, y_train)
# Evaluate
y_pred = clf.predict(X_test)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.2f}")
print(classification_report(y_test, y_pred, target_names=data.target_names))Saída esperada:
Accuracy: 1.00
precision recall f1-score support
setosa 1.00 1.00 1.00 10
versicolor 1.00 1.00 1.00 9
virginica 1.00 1.00 1.00 11
accuracy 1.00 30
macro avg 1.00 1.00 1.00 30
weighted avg 1.00 1.00 1.00 30O conjunto de dados Iris é linearmente separável com profundidade 3, por isso a árvore atinge acurácia perfeita no teste. Conjuntos de dados reais serão mais complexos.
Prevendo Novas Amostras
Após o treinamento, chame predict() para classificar novas observações e predict_proba() para obter as probabilidades de cada classe:
import numpy as np
# A new flower: sepal length 5.1, sepal width 3.5, petal length 1.4, petal width 0.2
new_sample = np.array([[5.1, 3.5, 1.4, 0.2]])
predicted_class = clf.predict(new_sample)
predicted_proba = clf.predict_proba(new_sample)
print("Predicted class:", data.target_names[predicted_class[0]])
print("Class probabilities:", predicted_proba)Saída esperada:
Predicted class: setosa
Class probabilities: [[1. 0. 0.]]Árvore de Regressão em Python
Árvores de decisão também lidam com alvos contínuos. Use DecisionTreeRegressor em vez de DecisionTreeClassifier.
from sklearn.tree import DecisionTreeRegressor
from sklearn.datasets import make_regression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
# Synthetic regression dataset
X_reg, y_reg = make_regression(
n_samples=300, n_features=5, noise=20, random_state=42
)
X_train_r, X_test_r, y_train_r, y_test_r = train_test_split(
X_reg, y_reg, test_size=0.2, random_state=42
)
reg = DecisionTreeRegressor(max_depth=5, random_state=42)
reg.fit(X_train_r, y_train_r)
y_pred_r = reg.predict(X_test_r)
mse = mean_squared_error(y_test_r, y_pred_r)
r2 = r2_score(y_test_r, y_pred_r)
print(f"MSE : {mse:.2f}")
print(f"R² : {r2:.2f}")Uma árvore de regressão divide os dados minimizando o erro quadrático médio (MSE) dentro de cada nó e prevê o valor médio do alvo de todas as amostras de treinamento que chegam a uma folha.
Ajustando Hiperparâmetros
Sem limites, uma árvore de decisão crescerá até que cada folha seja pura, memorizando perfeitamente o conjunto de treinamento (overfitting). Os hiperparâmetros controlam a complexidade da árvore:
| Parâmetro | Padrão | Efeito |
|---|---|---|
max_depth | None | Número máximo de níveis. Menor = árvore mais simples. |
min_samples_split | 2 | Mínimo de amostras necessárias para dividir um nó. Maior = menos divisões. |
min_samples_leaf | 1 | Mínimo de amostras necessárias em uma folha. Maior = fronteiras mais suaves. |
max_features | None | Número de características a considerar em cada divisão (útil para seleção de características). |
criterion | "gini" | Medida de impureza: "gini" ou "entropy" para classificadores; "squared_error" para regressores. |
Use validação cruzada e busca em grade para encontrar a melhor combinação:
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import GridSearchCV
data = load_iris()
X, y = data.data, data.target
param_grid = {
"max_depth": [2, 3, 4, 5, None],
"min_samples_split": [2, 5, 10],
"criterion": ["gini", "entropy"],
}
grid_search = GridSearchCV(
DecisionTreeClassifier(random_state=42),
param_grid,
cv=5,
scoring="accuracy",
)
grid_search.fit(X, y)
print("Best params :", grid_search.best_params_)
print(f"Best CV score: {grid_search.best_score_:.3f}")Saída esperada (os valores podem variar ligeiramente entre versões do scikit-learn):
Best params : {'criterion': 'gini', 'max_depth': 3, 'min_samples_split': 2}
Best CV score: 0.973Tratando Características Categóricas
Árvores de decisão do scikit-learn exigem entrada numérica. Codifique as colunas categóricas antes do treinamento:
- Categorias ordinais (ex.: tamanho: pequeno < médio < grande): use
OrdinalEncoder. - Categorias nominais (ex.: cor: vermelho, verde, azul): use
OneHotEncoderpara evitar implicar uma ordem.
from sklearn.preprocessing import OrdinalEncoder
import numpy as np
# Encode only the categorical column; keep the numeric column as-is
sizes = np.array([["small"], ["large"], ["medium"], ["large"]])
weights = np.array([1.2, 3.4, 2.1, 4.0])
# Explicit category order: large=0, medium=1, small=2
enc = OrdinalEncoder(categories=[["large", "medium", "small"]])
sizes_encoded = enc.fit_transform(sizes)
X_encoded = np.column_stack([sizes_encoded, weights])
print(X_encoded)Saída esperada:
[[2. 1.2]
[0. 3.4]
[1. 2.1]
[0. 4. ]]Consulte o capítulo Dados Categóricos para um passo a passo completo.
Visualizando uma Árvore de Decisão
Inspecionar a estrutura da árvore revela quais características impulsionam mais as divisões e torna o modelo auditável.
Representação em Texto
from sklearn.tree import DecisionTreeClassifier, export_text
from sklearn.datasets import load_iris
data = load_iris()
clf = DecisionTreeClassifier(max_depth=2, random_state=42)
clf.fit(data.data, data.target)
print(export_text(clf, feature_names=list(data.feature_names)))Saída esperada:
|--- petal length (cm) <= 2.45
| |--- class: 0
|--- petal length (cm) > 2.45
| |--- petal width (cm) <= 1.75
| | |--- class: 1
| |--- petal width (cm) > 1.75
| | |--- class: 2Gráfico Visual
import matplotlib.pyplot as plt
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.datasets import load_iris
data = load_iris()
clf = DecisionTreeClassifier(max_depth=2, random_state=42)
clf.fit(data.data, data.target)
plt.figure(figsize=(10, 5))
plot_tree(
clf,
feature_names=data.feature_names,
class_names=data.target_names,
filled=True,
rounded=True,
)
plt.title("Iris Decision Tree (max_depth=2)")
plt.tight_layout()
plt.savefig("iris_tree.png", dpi=150)
plt.show()filled=True colore cada nó pela sua classe majoritária; tonalidades mais escuras indicam maior pureza de classe.
Importância das Características
Após o treinamento, feature_importances_ atribui a cada característica uma pontuação entre 0 e 1, onde valores mais altos indicam que a característica contribuiu mais para reduzir a impureza em todas as divisões:
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
import numpy as np
data = load_iris()
clf = DecisionTreeClassifier(max_depth=3, random_state=42)
clf.fit(data.data, data.target)
importances = clf.feature_importances_
for name, imp in sorted(
zip(data.feature_names, importances), key=lambda x: x[1], reverse=True
):
print(f"{name:30s}: {imp:.4f}")Saída esperada:
petal length (cm) : 0.5856
petal width (cm) : 0.4144
sepal length (cm) : 0.0000
sepal width (cm) : 0.0000Características com importância 0 nunca foram usadas em nenhuma divisão e poderiam ser removidas para simplificar o modelo.
Vantagens e Limitações
Quando usar árvores de decisão
- Você precisa de um modelo interpretável — as regras podem ser impressas em linguagem simples.
- Seu conjunto de dados contém uma mistura de características numéricas e categóricas (após codificação).
- Você quer uma baseline rápida antes de experimentar métodos de ensemble.
- A relação entre as características e o alvo é não linear ou envolve interações.
Limitações
| Limitação | Mitigação |
|---|---|
| Tende ao overfitting sem ajuste | Restrinja max_depth, min_samples_leaf; use validação cruzada |
| Alta variância (pequenas mudanças nos dados → árvore diferente) | Use métodos de ensemble: Random Forest / Bootstrap Aggregation |
| Viés em direção a características com mais valores únicos | Use max_features ou normalize os critérios de divisão |
| Fraca extrapolação além do intervalo dos dados de treinamento | Prefira modelos lineares para tarefas de extrapolação |
| Apenas divisões alinhadas aos eixos | Árvores oblíquas existem, mas não estão no scikit-learn |
Árvores de Decisão vs. Algoritmos Relacionados
| Algoritmo | Diferença principal |
|---|---|
| Regressão Logística | Fronteira linear; melhor para dados linearmente separáveis; não lida com interações automaticamente |
| K-Nearest Neighbors | Baseado em instâncias; sem modelo explícito; requer escalonamento de características |
| Árvore de Decisão | Não linear; sem necessidade de escalonamento; altamente interpretável |
| Random Forest (ver Bootstrap Aggregation) | Ensemble de muitas árvores; variância muito menor; menos interpretável |
Principais Conclusões
- As árvores de decisão dividem os dados maximizando o ganho de informação (ou minimizando a impureza de Gini) em cada nó; o processo se repete recursivamente.
DecisionTreeClassifiereDecisionTreeRegressorno scikit-learn compartilham a mesma API e os mesmos nomes de hiperparâmetros.- Sempre defina
max_depthoumin_samples_leafpara evitar overfitting; ajuste-os com busca em grade e validação cruzada. feature_importances_revela em quais características a árvore se baseia mais — útil para seleção de características.- Árvores individuais são uma boa baseline interpretável, mas métodos de ensemble como Random Forest quase sempre superam o desempenho delas em dados do mundo real.