O bagging combina modelos treinados em paralelo e independentes, atacando a variância. Boosting faz o oposto: treina modelos em sequência, cada um corrigindo os erros do anterior, atacando o viés. É a família por trás do XGBoost, LightGBM e CatBoost — os modelos que ganham a maioria das competições de dados tabulares e sustentam sistemas de produção no mundo inteiro. Fechamos com o stacking, a forma mais sofisticada de combinar modelos.

Entender o mecanismo do boosting (do AdaBoost ao gradient boosting), dominar na prática XGBoost, LightGBM e CatBoost com suas técnicas de regularização e early stopping, e construir ensembles de stacking sem vazamento.

A ideia do boosting: aprender com os erros

Boosting constrói um modelo forte somando muitos modelos fracos (tipicamente árvores rasas), sequencialmente. Cada novo modelo foca nos exemplos que os anteriores erraram. O resultado é um modelo de baixo viés — capaz de capturar padrões complexos que um único modelo fraco jamais aprenderia.

Contraste-chave com bagging:

Bagging Boosting
TreinoParalelo, independenteSequencial, dependente
AtacaVariânciaViés
Modelo baseForte (árvore profunda)Fraco (árvore rasa)
RiscoBaixo overfittingPode overfittar sem regularização

AdaBoost — o primeiro boosting

AdaBoost (Adaptive Boosting) reponderam os exemplos: após cada modelo, aumenta o peso dos exemplos classificados errado, forçando o próximo a focar neles. Os modelos são combinados com pesos proporcionais à sua acurácia.

from sklearn.ensemble import AdaBoostClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import make_classification

X, y = make_classification(n_samples=3000, n_features=20, random_state=42)

ada = AdaBoostClassifier(
    estimator=DecisionTreeClassifier(max_depth=1),  # "stump" = árvore de 1 nível
    n_estimators=200, learning_rate=0.5, random_state=42,
)
print("AdaBoost:", cross_val_score(ada, X, y, cv=5, scoring="roc_auc").mean())

AdaBoost é sensível a ruído e outliers (dá peso crescente a exemplos difíceis, que podem ser justamente os ruidosos). Foi superado pelo gradient boosting, mas é a raiz conceitual da família.

Gradient Boosting — boosting como descida de gradiente

A generalização decisiva: em vez de reponderar exemplos, cada novo modelo é treinado para prever os resíduos (o gradiente da função de perda) do ensemble atual. É, literalmente, descida de gradiente no espaço de funções.

F₀ = predição inicial (ex.: média)
Para m = 1 até M:
   resíduo = gradiente negativo da perda em F_{m-1}
   treina árvore h_m para prever esse resíduo
   F_m = F_{m-1} + learning_rate · h_m
from sklearn.ensemble import GradientBoostingClassifier

gb = GradientBoostingClassifier(
    n_estimators=300, learning_rate=0.05, max_depth=3,
    subsample=0.8, random_state=42,
)
print("GBM:", cross_val_score(gb, X, y, cv=5, scoring="roc_auc").mean())

A taxa de aprendizado (learning_rate) controla o tamanho de cada passo. Valores menores exigem mais árvores mas generalizam melhor — o trade-off central do boosting. Regra: learning_rate baixo + muitas árvores + early stopping.

XGBoost — gradient boosting industrializado

XGBoost adicionou regularização explícita (L1/L2 nas folhas), tratamento nativo de faltantes, paralelização e uma aproximação de segunda ordem (usa também a curvatura da perda). É o padrão-ouro de desempenho em muitos problemas tabulares.

from xgboost import XGBClassifier
from sklearn.model_selection import train_test_split

X_tr, X_val, y_tr, y_val = train_test_split(X, y, test_size=0.2,
                                            stratify=y, random_state=42)

xgb = XGBClassifier(
    n_estimators=2000,        # alto — o early stopping decide o real
    learning_rate=0.03,
    max_depth=4,
    subsample=0.8,            # amostra de linhas por árvore (estocasticidade)
    colsample_bytree=0.8,     # amostra de colunas por árvore
    reg_lambda=1.0,           # regularização L2
    reg_alpha=0.0,            # regularização L1
    eval_metric="auc",
    early_stopping_rounds=50, # para quando a validação não melhora
    random_state=42,
)
xgb.fit(X_tr, y_tr, eval_set=[(X_val, y_val)], verbose=False)
print(f"Melhor iteração: {xgb.best_iteration}")

Early stopping é essencial: define n_estimators alto e deixa a validação decidir quando parar, evitando overfitting e escolhendo o número de árvores automaticamente.

LightGBM e CatBoost

  • LightGBM cresce as árvores por folha (leaf-wise) em vez de por nível, e usa histogramas para binning — muito mais rápido em datasets grandes. Cuidado: leaf-wise overfitta mais fácil; controle com num_leaves e min_child_samples.
from lightgbm import LGBMClassifier
lgbm = LGBMClassifier(
    n_estimators=2000, learning_rate=0.03, num_leaves=31,
    subsample=0.8, colsample_bytree=0.8, reg_lambda=1.0,
    random_state=42, verbose=-1,
)
lgbm.fit(X_tr, y_tr, eval_set=[(X_val, y_val)],
         callbacks=[__import__("lightgbm").early_stopping(50, verbose=False)])
  • CatBoost trata variáveis categóricas nativamente (com target encoding ordenado, que evita vazamento por construção) e usa "ordered boosting" para reduzir viés de predição. Excelente quando há muitas categóricas e ótimo com pouca sintonia.

Qual escolher? LightGBM pela velocidade em bases grandes; CatBoost quando há muitas categóricas ou você quer bons resultados com pouco tuning; XGBoost como referência robusta e madura. Na prática, teste os três.

Regularização em boosting

Como o boosting pode overfittar, o controle vem de vários lados combinados:

  • learning_rate baixo (+ mais árvores).
  • max_depth / num_leaves — profundidade das árvores.
  • subsample (linhas) e colsample_bytree (colunas) — introduzem estocasticidade (stochastic gradient boosting).
  • reg_lambda / reg_alpha — L2/L1 nos pesos das folhas.
  • min_child_weight / min_child_samples — mínimo de amostras por folha.
  • Early stopping — o regularizador mais eficaz.

Sintonize esses com otimização bayesiana (Módulo 5) e valide com nested CV.

Stacking — aprendendo a combinar

Voting usa uma regra fixa (média/voto). Stacking treina um meta-modelo que aprende a melhor forma de combinar as predições dos modelos base. A chave para não vazar: as predições que alimentam o meta-modelo devem ser out-of-fold.

from sklearn.ensemble import StackingClassifier, RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.svm import SVC

stack = StackingClassifier(
    estimators=[
        ("xgb", XGBClassifier(n_estimators=300, learning_rate=0.05,
                              max_depth=4, eval_metric="logloss", random_state=42)),
        ("rf", RandomForestClassifier(n_estimators=300, random_state=42)),
        ("svc", SVC(probability=True, random_state=42)),
    ],
    final_estimator=LogisticRegression(),  # meta-modelo simples
    cv=5,          # gera predições out-of-fold para treinar o meta-modelo
    stack_method="predict_proba",
    n_jobs=-1,
)
print("Stacking:", cross_val_score(stack, X, y, cv=5, scoring="roc_auc").mean())

Boas práticas de stacking:

  • Modelos base diversos (algoritmos diferentes que erram de formas diferentes) — a diversidade é o que dá ganho.
  • Meta-modelo simples (regressão logística/linear) para não overfittar sobre as predições.
  • Predições out-of-fold obrigatórias (o cv do StackingClassifier cuida disso).
  • Ganhos costumam ser incrementais e custam complexidade — avalie se o trade-off compensa em produção.

Boosting em R

library(tidymodels)

xgb_spec <- boost_tree(
  trees = 1000, tree_depth = tune(), learn_rate = tune(),
  loss_reduction = tune(), sample_size = tune(), mtry = tune()
) |> set_engine("xgboost") |> set_mode("classification")

# stacks::  -> pacote 'stacks' do tidymodels para ensembles de stacking
library(stacks)
# combina múltiplos workflows tunados em um único stack via meta-modelo

Boosting treina modelos em sequência, cada um corrigindo os erros do anterior, e por isso ataca o viés — o oposto do bagging, que ataca a variância. O AdaBoost repondera os exemplos difíceis; o gradient boosting treina cada modelo nos resíduos, que são o gradiente da perda, o que equivale a fazer descida de gradiente no espaço de funções. XGBoost, LightGBM e CatBoost são as implementações de ponta, com regularização, early stopping e tratamento nativo de faltantes; o LightGBM é rápido por crescer folha a folha, e o CatBoost lida com categóricas sem pré-processamento. A receita central é taxa de aprendizado baixa, muitas árvores e early stopping, com o overfitting contido por profundidade, subsample, colsample e regularização L1 e L2. Já o stacking treina um meta-modelo simples sobre predições out-of-fold de modelos base diversos: é mais poderoso que o voting, e também mais complexo de montar e validar.

Exercícios

  1. Qual a diferença fundamental entre bagging e boosting em termos de como treinam os modelos e qual componente do erro atacam?

    ✓ Resposta:

    No bagging, os modelos são treinados em paralelo e de forma independente, cada um em uma amostra bootstrap; a agregação (média/voto) reduz a variância. No boosting, os modelos são treinados sequencialmente e de forma dependente: cada novo modelo corrige os erros do ensemble acumulado até então, reduzindo o viés. Consequentemente, bagging usa modelos base fortes (árvores profundas, alta variância) e boosting usa modelos fracos (árvores rasas, alto viés). Boosting tende a atingir menor erro, mas exige mais cuidado com overfitting e regularização.

  2. No gradient boosting, o que cada nova árvore tenta prever, e como isso se relaciona com "descida de gradiente"?

    ✓ Resposta:

    Cada nova árvore é treinada para prever o gradiente negativo da função de perda avaliado nas predições atuais do ensemble — que, no caso da perda quadrática, equivale aos resíduos (erros) atuais. Ao adicionar essa árvore (multiplicada pela taxa de aprendizado) ao ensemble, damos um passo na direção que mais reduz a perda. Isso é exatamente descida de gradiente, mas realizada no espaço de funções: em vez de atualizar parâmetros numéricos, adicionamos funções (árvores) que apontam na direção de menor perda. Por isso o nome "gradient boosting".

  3. Por que a combinação "learning_rate baixo + muitas árvores + early stopping" é a receita padrão do boosting?

    ✓ Resposta:

    Um learning_rate baixo faz cada árvore contribuir com um passo pequeno, o que suaviza o aprendizado e melhora a generalização (evita que o modelo se ajuste rápido demais aos dados de treino). Passos pequenos, porém, exigem mais árvores para atingir bom desempenho — daí definir n_estimators alto. Para não overfittar nem desperdiçar computação, usa-se early stopping: monitora-se a perda em um conjunto de validação e interrompe-se o treino quando ela para de melhorar, escolhendo automaticamente o número ideal de árvores. A combinação equilibra viés, variância e custo, sendo mais robusta que taxas altas com poucas árvores.

  4. Por que é crucial que as predições usadas para treinar o meta-modelo no stacking sejam "out-of-fold"?

    ✓ Resposta:

    Se o meta-modelo fosse treinado com predições que os modelos base fizeram sobre os mesmos dados em que foram treinados, essas predições seriam otimistas (os modelos base já "viram" essas amostras e tendem a acertá-las), transmitindo overfitting ao meta-modelo e causando vazamento. Usar predições out-of-fold — geradas por validação cruzada interna, onde cada predição vem de um modelo que não treinou naquela amostra — garante que o meta-modelo aprenda a combinar predições que refletem o comportamento em dados não vistos, como ocorrerá em produção. Isso torna a estimativa honesta e o ensemble generalizável. O cv do StackingClassifier implementa isso automaticamente.

  5. Cite uma situação em que você preferiria CatBoost e uma em que preferiria LightGBM, justificando.

    ✓ Resposta:

    Preferiria CatBoost quando o dataset tem muitas variáveis categóricas (especialmente de alta cardinalidade), pois ele as trata nativamente com um target encoding ordenado que evita vazamento, poupando trabalho de pré-processamento e frequentemente entregando ótimos resultados com pouca sintonia. Preferiria LightGBM quando o dataset é muito grande e a velocidade de treino importa, pois seu crescimento leaf-wise e o binning por histogramas o tornam substancialmente mais rápido e eficiente em memória — cuidando de controlar num_leaves/min_child_samples para não overfittar. Em ambos os casos, o ideal é testar empiricamente, pois o vencedor varia por problema.

Fontes e leituras recomendadas