Skip to content

Sklearn Random Forest: Guia Completo para Classificação e Regressão em Python

Updated on

Você construiu uma árvore de decisão que obtém 95% de acurácia no treinamento, mas pontua 62% em novos dados. Uma única árvore de decisão memoriza o conjunto de treinamento -- cada divisão, cada folha é ajustada para as amostras exatas que ela viu. O resultado é um modelo que parece ótimo no papel, mas falha em produção.

Este problema de overfitting não é apenas acadêmico. Equipes implantam modelos que performam bem em notebooks de desenvolvimento, mas geram previsões não confiáveis em dados ao vivo. Uma única árvore de decisão tem alta variância: pequenas mudanças nos dados de treinamento produzem estruturas de árvore completamente diferentes. Você não pode confiar em um modelo que é tão sensível aos seus dados de treinamento.

O Random Forest resolve isso construindo centenas de árvores de decisão em subconjuntos aleatórios de dados e features, depois combinando suas previsões através de votação majoritária (classificação) ou média (regressão). Esta abordagem de ensemble reduz drasticamente a variância enquanto mantém a acurácia. As implementações RandomForestClassifier e RandomForestRegressor do scikit-learn fornecem uma solução pronta para produção com importância de features integrada, avaliação out-of-bag e treinamento paralelo.

📚

O que é Random Forest?

O Random Forest é um método de ensemble learning que combina múltiplas árvores de decisão para produzir uma única previsão mais robusta. Ele usa uma técnica chamada bagging (Bootstrap Aggregating):

  1. Amostragem bootstrap: Cria múltiplos subconjuntos aleatórios dos dados de treinamento através de amostragem com reposição. Cada subconjunto é aproximadamente 63% dos dados originais.
  2. Seleção aleatória de features: Em cada divisão de cada árvore, considera apenas um subconjunto aleatório de features (tipicamente sqrt(n_features) para classificação, n_features/3 para regressão).
  3. Treinamento independente: Treina uma árvore de decisão em cada amostra bootstrap com a restrição de features aleatórias.
  4. Agregação: Combina previsões por votação majoritária (classificação) ou média (regressão).

A aleatoriedade tanto na amostragem dos dados quanto na seleção de features garante que as árvores individuais estejam descorrelacionadas. Mesmo que uma árvore faça overfitting em um padrão particular, a maioria das outras árvores não fará, e o ensemble compensa o ruído.

Quando Usar o Random Forest

CenárioRandom Forest?Por quê
Dados tabulares com tipos de features mistosSimLida com features numéricas e categóricas, sem necessidade de escalonamento
Você precisa de rankings de importância de featuresSimAtributo feature_importances_ integrado
Conjuntos de dados pequenos a médios (até ~100K linhas)SimTreinamento rápido com processamento paralelo
Classificação desbalanceadaSimSuporta class_weight='balanced'
Você precisa de previsões interpretáveisModeradoÁrvores individuais são interpretáveis, mas o ensemble é menos
Dados esparsos de muita alta dimensionalidade (texto)NãoModelos lineares ou gradient boosting são tipicamente melhores
Inferência em tempo real com latência estritaCuidadoFlorestas grandes podem ser lentas no momento da previsão

RandomForestClassifier: Exemplo de Classificação

Aqui está um exemplo completo de classificação usando o dataset de vinhos:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, accuracy_score
from sklearn.datasets import load_wine
 
# Load dataset
wine = load_wine()
X, y = wine.data, wine.target
feature_names = wine.feature_names
 
print(f"Dataset: {X.shape[0]} samples, {X.shape[1]} features")
print(f"Classes: {wine.target_names}")
 
# Split data
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)
 
# Train Random Forest
rf = RandomForestClassifier(
    n_estimators=100,
    max_depth=None,
    min_samples_split=2,
    min_samples_leaf=1,
    random_state=42,
    n_jobs=-1
)
rf.fit(X_train, y_train)
 
# Evaluate
y_pred = rf.predict(X_test)
print(f"\nAccuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"\nClassification Report:")
print(classification_report(y_test, y_pred, target_names=wine.target_names))

Parâmetros Principais Explicados

ParâmetroPadrãoDescriçãoDica de Ajuste
n_estimators100Número de árvores na florestaMais árvores = melhor performance, mas mais lento. 100-500 é típico.
max_depthNoneProfundidade máxima de cada árvoreNone significa totalmente desenvolvida. Defina 10-30 para reduzir overfitting.
min_samples_split2Mínimo de amostras para dividir um nóAumente para 5-20 para prevenir overfitting em dados ruidosos.
min_samples_leaf1Mínimo de amostras em um nó folhaAumente para 2-10 para previsões mais suaves.
max_features'sqrt'Features consideradas em cada divisão'sqrt' para classificação, 'log2' ou uma fração para alternativas.
bootstrapTrueUsar amostragem bootstrapDefina False para conjuntos de dados pequenos para usar todos os dados por árvore.
class_weightNonePesos para cada classeUse 'balanced' para conjuntos de dados desbalanceados.
n_jobsNoneNúmero de trabalhos paralelosDefina -1 para usar todos os núcleos da CPU.
oob_scoreFalseUsar amostras out-of-bag para avaliaçãoDefina True para uma estimativa de validação integrada sem um conjunto de holdout.

Pontuação Out-of-Bag (OOB)

Cada árvore é treinada em aproximadamente 63% dos dados. Os 37% restantes (amostras out-of-bag) podem ser usados como um conjunto de validação gratuito:

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
rf = RandomForestClassifier(
    n_estimators=200,
    oob_score=True,
    random_state=42,
    n_jobs=-1
)
rf.fit(X_train, y_train)
 
print(f"OOB Score:  {rf.oob_score_:.4f}")
print(f"Test Score: {rf.score(X_test, y_test):.4f}")

A pontuação OOB fornece uma estimativa de validação sem a necessidade de um conjunto de holdout separado. É especialmente útil quando os dados são limitados.

RandomForestRegressor: Exemplo de Regressão

A regressão com Random Forest prevê valores contínuos através da média das saídas de todas as árvores:

from sklearn.ensemble import RandomForestRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, mean_absolute_error, r2_score
from sklearn.datasets import fetch_california_housing
import numpy as np
 
# Load California housing dataset
housing = fetch_california_housing()
X, y = housing.data, housing.target
feature_names = housing.feature_names
 
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
 
# Train regressor
rf_reg = RandomForestRegressor(
    n_estimators=200,
    max_depth=20,
    min_samples_leaf=5,
    random_state=42,
    n_jobs=-1
)
rf_reg.fit(X_train, y_train)
y_pred = rf_reg.predict(X_test)
 
# Evaluation metrics
r2 = r2_score(y_test, y_pred)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
mae = mean_absolute_error(y_test, y_pred)
 
print(f"R-squared: {r2:.4f}")
print(f"RMSE:      {rmse:.4f}")
print(f"MAE:       {mae:.4f}")

Comparando Regressores

from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
from sklearn.linear_model import LinearRegression, Ridge
from sklearn.tree import DecisionTreeRegressor
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.datasets import fetch_california_housing
import numpy as np
 
housing = fetch_california_housing()
X_train, X_test, y_train, y_test = train_test_split(
    housing.data, housing.target, test_size=0.2, random_state=42
)
 
regressors = {
    'Linear Regression': LinearRegression(),
    'Ridge': Ridge(alpha=1.0),
    'Decision Tree': DecisionTreeRegressor(max_depth=10, random_state=42),
    'Random Forest': RandomForestRegressor(n_estimators=100, max_depth=20, random_state=42, n_jobs=-1),
    'Gradient Boosting': GradientBoostingRegressor(n_estimators=100, max_depth=5, random_state=42),
}
 
print(f"{'Model':<25} {'CV R² (mean)':>12} {'CV R² (std)':>12}")
print("-" * 52)
 
for name, model in regressors.items():
    scores = cross_val_score(model, X_train, y_train, cv=5, scoring='r2', n_jobs=-1)
    print(f"{name:<25} {scores.mean():>12.4f} {scores.std():>12.4f}")

O Random Forest tipicamente supera uma única árvore de decisão e modelos lineares em conjuntos de dados com relações não lineares, enquanto é competitivo com gradient boosting.

Ajuste de Hiperparâmetros

GridSearchCV: Busca Exaustiva

O GridSearchCV testa todas as combinações dos valores de parâmetros especificados:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import GridSearchCV, train_test_split
from sklearn.datasets import load_wine
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
param_grid = {
    'n_estimators': [100, 200, 300],
    'max_depth': [None, 10, 20],
    'min_samples_split': [2, 5, 10],
    'min_samples_leaf': [1, 2, 4],
}
 
rf = RandomForestClassifier(random_state=42, n_jobs=-1)
grid_search = GridSearchCV(
    rf,
    param_grid,
    cv=5,
    scoring='accuracy',
    n_jobs=-1,
    verbose=1
)
grid_search.fit(X_train, y_train)
 
print(f"Best Parameters: {grid_search.best_params_}")
print(f"Best CV Score:   {grid_search.best_score_:.4f}")
print(f"Test Score:      {grid_search.score(X_test, y_test):.4f}")

RandomizedSearchCV: Busca Eficiente

Quando o espaço de parâmetros é grande, o RandomizedSearchCV amostra um número fixo de combinações de parâmetros em vez de tentar todas:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import RandomizedSearchCV, train_test_split
from sklearn.datasets import load_wine
from scipy.stats import randint, uniform
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
param_distributions = {
    'n_estimators': randint(50, 500),
    'max_depth': [None, 5, 10, 15, 20, 30],
    'min_samples_split': randint(2, 20),
    'min_samples_leaf': randint(1, 10),
    'max_features': ['sqrt', 'log2', 0.3, 0.5, 0.7],
    'bootstrap': [True, False],
}
 
rf = RandomForestClassifier(random_state=42, n_jobs=-1)
random_search = RandomizedSearchCV(
    rf,
    param_distributions,
    n_iter=100,
    cv=5,
    scoring='accuracy',
    random_state=42,
    n_jobs=-1,
    verbose=1
)
random_search.fit(X_train, y_train)
 
print(f"Best Parameters: {random_search.best_params_}")
print(f"Best CV Score:   {random_search.best_score_:.4f}")
print(f"Test Score:      {random_search.score(X_test, y_test):.4f}")

Importância dos Parâmetros para Ajuste

Nem todos os parâmetros têm impacto igual. Concentre seu esforço de ajuste nos parâmetros que mais importam:

ParâmetroImpactoPrioridadeNotas
n_estimatorsAltoMais árvores quase sempre ajudam até retornos decrescentes (~200-500)
max_depthAltoControla o overfitting diretamente. Tente None, 10, 20, 30
min_samples_leafMédioSuaviza as previsões. Tente 1, 2, 5, 10
max_featuresMédioControla a diversidade das árvores. 'sqrt' é geralmente bom para classificação
min_samples_splitBaixoMenos impacto que min_samples_leaf na prática
bootstrapBaixoTrue é quase sempre melhor. Só tente False em conjuntos de dados muito pequenos

Importância de Features

Uma das maiores vantagens do Random Forest é a importância de features integrada. Entender quais features impulsionam as previsões ajuda na interpretação do modelo, seleção de features e insights de domínio.

Importância de Features Baseada em Impureza

O atributo padrão feature_importances_ mede quanto cada feature diminui a impureza (Gini para classificação, variância para regressão) em todas as árvores:

from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
 
# Get feature importances
importances = rf.feature_importances_
feature_names = wine.feature_names
indices = np.argsort(importances)[::-1]
 
# Print ranked features
print("Feature Ranking:")
for i, idx in enumerate(indices):
    print(f"  {i+1}. {feature_names[idx]:25s} ({importances[idx]:.4f})")
 
# Plot
plt.figure(figsize=(10, 6))
plt.barh(range(len(indices)), importances[indices[::-1]], align='center')
plt.yticks(range(len(indices)), [feature_names[i] for i in indices[::-1]])
plt.xlabel('Feature Importance (Gini)')
plt.title('Random Forest Feature Importance - Wine Dataset')
plt.tight_layout()
plt.savefig('rf_feature_importance.png', dpi=150)
plt.show()

Importância por Permutação

A importância baseada em impureza pode ser tendenciosa para features de alta cardinalidade. A importância por permutação mede a queda na performance do modelo quando os valores de uma feature são embaralhados aleatoriamente:

from sklearn.ensemble import RandomForestClassifier
from sklearn.inspection import permutation_importance
from sklearn.datasets import load_wine
from sklearn.model_selection import train_test_split
import matplotlib.pyplot as plt
import numpy as np
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
 
# Compute permutation importance on the test set
perm_imp = permutation_importance(
    rf, X_test, y_test,
    n_repeats=30,
    random_state=42,
    n_jobs=-1
)
 
# Sort and display
sorted_idx = perm_imp.importances_mean.argsort()[::-1]
 
print("Permutation Importance (test set):")
for idx in sorted_idx:
    mean = perm_imp.importances_mean[idx]
    std = perm_imp.importances_std[idx]
    print(f"  {wine.feature_names[idx]:25s}: {mean:.4f} +/- {std:.4f}")
 
# Plot with error bars
plt.figure(figsize=(10, 6))
plt.barh(
    range(len(sorted_idx)),
    perm_imp.importances_mean[sorted_idx[::-1]],
    xerr=perm_imp.importances_std[sorted_idx[::-1]],
    align='center'
)
plt.yticks(range(len(sorted_idx)), [wine.feature_names[i] for i in sorted_idx[::-1]])
plt.xlabel('Decrease in Accuracy')
plt.title('Permutation Importance - Wine Dataset')
plt.tight_layout()
plt.savefig('rf_permutation_importance.png', dpi=150)
plt.show()

Qual Método de Importância Usar?

MétodoPrósContrasMelhor Para
Baseada em impureza (feature_importances_)Rápida, sem computação extraTendenciosa para features de alta cardinalidadeTriagem rápida, exploração inicial
Importância por permutaçãoNão tendenciosa, funciona em dados de testeMais lenta, afetada por features correlacionadasSeleção final de features, relatórios

Validação Cruzada com Random Forest

Validação Cruzada Básica

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_wine
 
wine = load_wine()
X, y = wine.data, wine.target
 
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
scores = cross_val_score(rf, X, y, cv=5, scoring='accuracy')
 
print(f"CV Accuracy: {scores.mean():.4f} (+/- {scores.std():.4f})")
print(f"Per-fold:    {scores}")

StratifiedKFold para Dados Desbalanceados

O StratifiedKFold preserva a distribuição das classes em cada fold, o que é crítico para conjuntos de dados desbalanceados:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.datasets import load_wine
import numpy as np
 
wine = load_wine()
X, y = wine.data, wine.target
 
# Stratified 10-fold cross-validation
skf = StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
 
scores = cross_val_score(rf, X, y, cv=skf, scoring='accuracy')
print(f"Stratified 10-Fold Accuracy: {scores.mean():.4f} (+/- {scores.std():.4f})")
 
# Multiple metrics
from sklearn.model_selection import cross_validate
 
results = cross_validate(
    rf, X, y, cv=skf,
    scoring=['accuracy', 'f1_weighted', 'precision_weighted', 'recall_weighted'],
    n_jobs=-1
)
 
for metric in ['test_accuracy', 'test_f1_weighted', 'test_precision_weighted', 'test_recall_weighted']:
    vals = results[metric]
    name = metric.replace('test_', '')
    print(f"{name:>20s}: {vals.mean():.4f} (+/- {vals.std():.4f})")

Lidando com Dados Desbalanceados

Quando uma classe tem muito mais amostras que as outras, um modelo pode alcançar alta acurácia sempre prevendo a classe majoritária. O Random Forest fornece várias ferramentas para lidar com isso.

Usando class_weight='balanced'

O parâmetro class_weight='balanced' ajusta automaticamente os pesos inversamente proporcionais às frequências das classes:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.datasets import make_classification
 
# Create imbalanced dataset (95% class 0, 5% class 1)
X, y = make_classification(
    n_samples=2000,
    n_features=20,
    weights=[0.95, 0.05],
    flip_y=0,
    random_state=42
)
 
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)
 
# Without class weight
rf_default = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf_default.fit(X_train, y_train)
print("=== Without class_weight ===")
print(classification_report(y_test, rf_default.predict(X_test)))
 
# With balanced class weight
rf_balanced = RandomForestClassifier(
    n_estimators=200,
    class_weight='balanced',
    random_state=42,
    n_jobs=-1
)
rf_balanced.fit(X_train, y_train)
print("=== With class_weight='balanced' ===")
print(classification_report(y_test, rf_balanced.predict(X_test)))

Integrando SMOTE para Oversampling

SMOTE (Synthetic Minority Oversampling Technique) cria amostras sintéticas para a classe minoritária. Use-o com o pipeline do imblearn:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
from sklearn.datasets import make_classification
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline
 
# Create imbalanced dataset
X, y = make_classification(
    n_samples=2000,
    n_features=20,
    weights=[0.95, 0.05],
    flip_y=0,
    random_state=42
)
 
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.3, random_state=42, stratify=y
)
 
# SMOTE + Random Forest pipeline
pipeline = ImbPipeline([
    ('smote', SMOTE(random_state=42)),
    ('rf', RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1))
])
pipeline.fit(X_train, y_train)
 
print("=== SMOTE + Random Forest ===")
print(classification_report(y_test, pipeline.predict(X_test)))

Avaliação do Modelo

Relatório de Classificação e Matriz de Confusão

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import (
    classification_report, confusion_matrix,
    ConfusionMatrixDisplay, accuracy_score
)
from sklearn.datasets import load_wine
import matplotlib.pyplot as plt
 
wine = load_wine()
X_train, X_test, y_train, y_test = train_test_split(
    wine.data, wine.target, test_size=0.2, random_state=42, stratify=wine.target
)
 
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
y_pred = rf.predict(X_test)
 
# Metrics
print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"\n{classification_report(y_test, y_pred, target_names=wine.target_names)}")
 
# Confusion matrix plot
cm = confusion_matrix(y_test, y_pred)
disp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=wine.target_names)
disp.plot(cmap='Blues')
plt.title('Random Forest - Wine Classification')
plt.tight_layout()
plt.savefig('rf_confusion_matrix.png', dpi=150)
plt.show()

Curva ROC para Classificação Binária

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import roc_curve, roc_auc_score, RocCurveDisplay
from sklearn.datasets import load_breast_cancer
import matplotlib.pyplot as plt
 
cancer = load_breast_cancer()
X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, test_size=0.2, random_state=42, stratify=cancer.target
)
 
rf = RandomForestClassifier(n_estimators=200, random_state=42, n_jobs=-1)
rf.fit(X_train, y_train)
 
# Predict probabilities
y_prob = rf.predict_proba(X_test)[:, 1]
auc = roc_auc_score(y_test, y_prob)
 
# Plot ROC curve
RocCurveDisplay.from_estimator(rf, X_test, y_test)
plt.title(f'Random Forest ROC Curve (AUC = {auc:.4f})')
plt.tight_layout()
plt.savefig('rf_roc_curve.png', dpi=150)
plt.show()

Random Forest vs Outros Algoritmos

CaracterísticaRandom ForestXGBoostGradient BoostingÁrvore de Decisão
Tipo de EnsembleBagging (paralelo)Boosting (sequencial)Boosting (sequencial)Modelo único
AcuráciaAltaMuito AltaMuito AltaModerada
Velocidade de TreinamentoRápida (paralelizável)ModeradaLenta (sequencial)Muito Rápida
Velocidade de PrevisãoModeradaRápidaModeradaMuito Rápida
Risco de OverfittingBaixoBaixo (com ajuste)Baixo (com ajuste)Alto
Sensibilidade a HiperparâmetrosBaixaAltaAltaModerada
Escalonamento de Features NecessárioNãoNãoNãoNão
Lida com Valores AusentesNão (precisa de imputação)Sim (integrado)Não (precisa de imputação)Não
Importância de Features IntegradaSimSimSimSim
InterpretabilidadeModeradaBaixaBaixaAlta
Melhor ParaUso geral, primeiro modeloCompetições Kaggle, acurácia máximaDados tabulares estruturadosBaselines rápidos, conjuntos pequenos

Quando escolher Random Forest em vez de alternativas:

  • Você precisa de um modelo baseline forte com ajuste mínimo
  • A velocidade de treinamento importa e você tem múltiplos núcleos de CPU
  • Você quer estimativas confiáveis de importância de features
  • Você não está perseguindo os últimos 0,5% de acurácia que métodos de boosting podem fornecer

Pipeline do Mundo Real: Exemplo Completo

Este pipeline combina pré-processamento, engenharia de features, treinamento do modelo, avaliação e previsão em um fluxo de trabalho no estilo produção:

from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split, cross_val_score, StratifiedKFold
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.compose import ColumnTransformer
from sklearn.metrics import classification_report, confusion_matrix
from sklearn.datasets import load_breast_cancer
import numpy as np
import pandas as pd
 
# Load and prepare data
cancer = load_breast_cancer()
df = pd.DataFrame(cancer.data, columns=cancer.feature_names)
df['target'] = cancer.target
 
# Introduce some missing values to simulate real data
np.random.seed(42)
mask = np.random.random(df.shape) < 0.05
df_missing = df.mask(mask.astype(bool))
df_missing['target'] = cancer.target  # Keep target clean
 
X = df_missing.drop('target', axis=1)
y = df_missing['target']
 
# Split
X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42, stratify=y
)
 
# Build preprocessing + model pipeline
numeric_features = X.columns.tolist()
numeric_transformer = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('scaler', StandardScaler()),
])
 
preprocessor = ColumnTransformer(
    transformers=[
        ('num', numeric_transformer, numeric_features),
    ]
)
 
pipeline = Pipeline([
    ('preprocessor', preprocessor),
    ('classifier', RandomForestClassifier(
        n_estimators=300,
        max_depth=20,
        min_samples_leaf=2,
        class_weight='balanced',
        random_state=42,
        n_jobs=-1
    ))
])
 
# Cross-validation
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
cv_scores = cross_val_score(pipeline, X_train, y_train, cv=skf, scoring='accuracy')
print(f"Cross-validation accuracy: {cv_scores.mean():.4f} (+/- {cv_scores.std():.4f})")
 
# Train final model
pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)
 
# Evaluation
print(f"\nTest Set Results:")
print(classification_report(y_test, y_pred, target_names=cancer.target_names))
 
# Make predictions on new data
sample = X_test.iloc[:3]
predictions = pipeline.predict(sample)
probabilities = pipeline.predict_proba(sample)
 
print(f"\nSample Predictions:")
for i, (pred, prob) in enumerate(zip(predictions, probabilities)):
    class_name = cancer.target_names[pred]
    confidence = prob[pred]
    print(f"  Sample {i+1}: {class_name} (confidence: {confidence:.2%})")

Salvando e Carregando o Modelo

import joblib
 
# Save the trained pipeline
joblib.dump(pipeline, 'rf_pipeline.joblib')
 
# Load and use later
loaded_pipeline = joblib.load('rf_pipeline.joblib')
new_predictions = loaded_pipeline.predict(X_test[:5])
print(f"Loaded model predictions: {new_predictions}")

Explorando Resultados com PyGWalker

Depois de treinar seu modelo Random Forest, você frequentemente precisa explorar padrões de importância de features, distribuições de previsões e casos de classificação incorreta em detalhe. O PyGWalker (opens in a new tab) permite que você transforme seu DataFrame de resultados em uma interface de exploração interativa tipo Tableau diretamente no Jupyter:

import pandas as pd
import pygwalker as pyg
 
# Build a results DataFrame
results = pd.DataFrame(X_test.values, columns=cancer.feature_names)
results['actual'] = y_test.values
results['predicted'] = y_pred
results['correct'] = y_test.values == y_pred
results['prob_malignant'] = pipeline.predict_proba(X_test)[:, 0]
results['prob_benign'] = pipeline.predict_proba(X_test)[:, 1]
 
# Launch interactive exploration
walker = pyg.walk(results)

Arraste features para os eixos, filtre por amostras classificadas incorretamente e codifique por cores de acordo com a confiança da previsão para identificar onde o modelo tem dificuldades. Este tipo de análise visual ajuda você a decidir quais features engenhar ou quais amostras precisam de inspeção mais detalhada.

Para executar seu fluxo completo de experimentação de ML -- desde o carregamento de dados até a comparação de modelos e avaliação final -- o RunCell (opens in a new tab) fornece um ambiente Jupyter potencializado por IA que ajuda você a iterar mais rápido em experimentos, auto-gerar código de avaliação e gerenciar seu fluxo de trabalho de notebooks.

FAQ

Quantas árvores devo usar em um Random Forest?

Comece com 100-200 árvores. A acurácia geralmente melhora com mais árvores, mas estagna depois de um certo ponto. Use validação cruzada para encontrar o ponto ideal. Além de 500 árvores, os ganhos geralmente são negligenciáveis enquanto o tempo de treinamento aumenta. Monitore a pontuação OOB à medida que você aumenta o n_estimators -- quando ela parar de melhorar, você tem árvores suficientes.

O Random Forest precisa de escalonamento de features?

Não. O Random Forest faz divisões baseadas em limiares de valores de features, então a escala absoluta das features não afeta as decisões de divisão. Ao contrário da regressão logística, SVM ou redes neurais, o Random Forest lida naturalmente com features de diferentes ranges. No entanto, se seu pipeline incluir outros componentes (como PCA ou pré-processamento baseado em distância), o escalonamento ainda pode ser necessário para essas etapas.

Como o Random Forest lida com valores ausentes?

Os RandomForestClassifier e RandomForestRegressor do scikit-learn não lidam com valores ausentes nativamente. Você deve imputar dados ausentes antes do treinamento -- use SimpleImputer com estratégia de mediana ou média para features numéricas, ou use métodos de imputação mais avançados como IterativeImputer. Algumas outras implementações como H2O ou LightGBM podem lidar com valores ausentes diretamente.

Qual é a diferença entre Random Forest e Gradient Boosting?

O Random Forest constrói árvores independentemente em paralelo (bagging), enquanto o Gradient Boosting constrói árvores sequencialmente onde cada árvore corrige os erros da anterior (boosting). O Random Forest reduz variância, o Gradient Boosting reduz viés. Na prática, o Gradient Boosting (especialmente XGBoost) frequentemente alcança acurácia ligeiramente maior, mas o Random Forest é mais fácil de ajustar e menos propenso a overfitting.

O Random Forest pode ser usado para seleção de features?

Sim. Use feature_importances_ para um ranking rápido ou permutation_importance para uma estimativa mais confiável. Você pode então descartar features de baixa importância e retreinar. Alternativamente, use SelectFromModel com um estimador Random Forest dentro de um pipeline para selecionar automaticamente features acima de um limiar.

Conclusão

O Random Forest é um dos algoritmos mais confiáveis e versáteis em machine learning. Ele reduz o overfitting combinando centenas de árvores de decisão descorrelacionadas, lida com tarefas de classificação e regressão sem escalonamento de features, e fornece rankings integrados de importância de features. Para a maioria dos problemas com dados tabulares, ele serve como um excelente primeiro modelo que frequentemente performa bem o suficiente para uso em produção.

Comece com RandomForestClassifier ou RandomForestRegressor com parâmetros padrão como seu baseline. Ajuste n_estimators primeiro para análise de retornos decrescentes, depois max_depth e min_samples_leaf para controlar o overfitting. Use class_weight='balanced' para dados desbalanceados, importância por permutação para rankings confiáveis de features, e validação cruzada StratifiedKFold para avaliação robusta. Quando você precisar da mais alta acurácia possível em dados estruturados, considere Gradient Boosting ou XGBoost, mas o Random Forest permanece como a escolha padrão mais segura que raramente falha gravemente.

📚