Phase 02 - Lesson 11

Métodos de conjunto

This lesson includes a graded coding exercise that runs in your browser, unlocked with lifetime access.

Um grupo de alunos fracos, combinados corretamente, torna-se um aluno forte. Isto não é uma metáfora. É um teorema.

Tipo: Construir Idioma: Python Pré-requisitos: Fase 2, Lição 10 (Compensação entre polarização e variância) Tempo: ~120 minutos

Objetivos de aprendizagem

  • Implemente AdaBoost e aumento de gradiente do zero e explique como o aumento sequencial reduz o viés
  • Construir um conjunto de ensacamento e demonstrar como a média de modelos desrelacionados reduz a variância sem aumentar o viés
  • Compare o ensacamento, o reforço e o empilhamento em termos de qual componente de erro cada método visa
  • Avaliar a diversidade do conjunto e explicar por que a precisão da votação por maioria melhora com alunos fracos mais independentes

O problema

Uma única árvore de decisão é rápida de treinar e fácil de interpretar, mas se ajusta demais. Um único modelo linear não se adapta bem a limites complexos. Você poderia passar dias projetando a arquitetura do modelo perfeito. Ou você pode combinar vários modelos imperfeitos e obter algo melhor do que qualquer um deles individualmente.

Os métodos de conjunto fazem exatamente isso. Eles são a técnica mais confiável para vencer competições Kaggle em dados tabulares, alimentam a maioria dos sistemas de ML de produção e ilustram a compensação entre viés e variância em ação. Bagging reduz a variação. Boosting reduz o preconceito. O empilhamento aprende em quais modelos confiar em quais entradas.

O Conceito

Por que os conjuntos funcionam

Suponha que você tenha N classificadores independentes, cada um com precisão p > 0,5. A votação majoritária tem precisão:

P(majority correct) = sum over k > N/2 of C(N,k) * p^k * (1-p)^(N-k)

Para 21 classificadores, cada um com 60% de precisão, a precisão do voto da maioria é de cerca de 74%. Com 101 classificadores, sobe para 84%. Os erros são anulados quando os modelos cometem erros diferentes.

O principal requisito é diversidade. Se todos os modelos cometem os mesmos erros, combiná-los não ajuda em nada. Os conjuntos funcionam porque produzem diversos modelos através de:

  • Diferentes subconjuntos de treinamento (bagging)
  • Diferentes subconjuntos de recursos (florestas aleatórias)
  • Correção de erros sequencial (boost)
  • Diferentes famílias de modelos (empilhamento)

Bagging (Agregação Bootstrap)

Bagging cria diversidade treinando cada modelo em uma amostra de bootstrap diferente dos dados de treinamento.

flowchart TD
    D[Training Data] --> B1[Bootstrap Sample 1]
    D --> B2[Bootstrap Sample 2]
    D --> B3[Bootstrap Sample 3]
    D --> BN[Bootstrap Sample N]

    B1 --> M1[Model 1]
    B2 --> M2[Model 2]
    B3 --> M3[Model 3]
    BN --> MN[Model N]

    M1 --> V[Average or Majority Vote]
    M2 --> V
    M3 --> V
    MN --> V

    V --> P[Final Prediction]

Uma amostra bootstrap é extraída com substituição dos dados originais, do mesmo tamanho do original. Cerca de 63,2% das amostras exclusivas aparecem em cada bootstrap. Os 36,8% restantes (amostras fora da sacola) fornecem um conjunto de validação gratuito.

Bagging reduz a variância sem aumentar muito o viés. Cada árvore individual se adapta à sua amostra de bootstrap, mas o overfitting é diferente para cada árvore, portanto, a média cancela o ruído.

Florestas Aleatórias trazem uma novidade extra: em cada divisão, apenas um subconjunto aleatório de recursos é considerado. Isso força ainda mais diversidade entre as árvores. O número típico de recursos candidatos é sqrt(n_features) para classificação e n_features / 3 para regressão.

Boosting (Correção de erros sequencial)

Boosting treina modelos sequencialmente. Cada novo modelo concentra-se nos exemplos que os modelos anteriores erraram.

flowchart LR
    D[Data with weights] --> M1[Model 1]
    M1 --> E1[Find errors]
    E1 --> W1[Increase weights on errors]
    W1 --> M2[Model 2]
    M2 --> E2[Find errors]
    E2 --> W2[Increase weights on errors]
    W2 --> M3[Model 3]
    M3 --> F[Weighted sum of all models]

Boosting reduz o preconceito. Cada novo modelo corrige os erros sistemáticos do conjunto até então. A previsão final é uma soma ponderada de todos os modelos, onde os melhores modelos obtêm pesos mais elevados.

A desvantagem: o reforço pode se ajustar demais se você executar muitas rodadas, porque continua ajustando exemplos mais difíceis, alguns dos quais podem ser ruídos.

AdaBoost

AdaBoost (Adaptável Boosting) foi o primeiro algoritmo de boost prático. Funciona com qualquer aluno básico, normalmente tocos de decisão (árvores de profundidade 1).

O algoritmo:

1. Initialize sample weights: w_i = 1/N for all i

2. For t = 1 to T:
   a. Train weak learner h_t on weighted data
   b. Compute weighted error:
      err_t = sum(w_i * I(h_t(x_i) != y_i)) / sum(w_i)
   c. Compute model weight:
      alpha_t = 0.5 * ln((1 - err_t) / err_t)
   d. Update sample weights:
      w_i = w_i * exp(-alpha_t * y_i * h_t(x_i))
   e. Normalize weights to sum to 1

3. Final prediction: H(x) = sign(sum(alpha_t * h_t(x)))

Modelos com erro menor obtêm alfa maior. Amostras classificadas incorretamente recebem pesos mais altos, então o próximo modelo se concentra nelas.

Gradient Boosting

O aumento de gradiente generaliza o aumento para funções de perda arbitrárias. Em vez de reponderar as amostras, ajusta cada novo modelo aos resíduos (gradiente negativo da perda) do conjunto atual.

1. Initialize: F_0(x) = argmin_c sum(L(y_i, c))

2. For t = 1 to T:
   a. Compute pseudo-residuals:
      r_i = -dL(y_i, F_{t-1}(x_i)) / dF_{t-1}(x_i)
   b. Fit a tree h_t to the residuals r_i
   c. Find optimal step size:
      gamma_t = argmin_gamma sum(L(y_i, F_{t-1}(x_i) + gamma * h_t(x_i)))
   d. Update:
      F_t(x) = F_{t-1}(x) + learning_rate * gamma_t * h_t(x)

3. Final prediction: F_T(x)

Para perda de erro quadrático, os pseudo-resíduos são apenas os resíduos reais: r_i = y_i - F_{t-1}(x_i). Cada árvore corresponde literalmente aos erros do conjunto anterior.

A taxa de aprendizagem (redução) controla quanto cada árvore contribui. Taxas de aprendizagem menores requerem mais árvores, mas generalizam melhor. Valores típicos: 0,01 a 0,3.

XGBoost: Por que domina os dados tabulares

XGBoost (eXtreme Gradient Boosting) é um aumento de gradiente com otimizações de engenharia que o tornam rápido, preciso e resistente ao overfitting:

  • Objetivo regularizado: Penalidades L1 e L2 no peso das folhas evitam que árvores individuais sejam muito confiantes
  • Aproximação de segunda ordem: Usa a primeira e a segunda derivadas da perda, proporcionando melhores decisões de divisão
  • Divisões com reconhecimento de dispersão: Lida com valores ausentes nativamente, aprendendo a melhor direção para dados ausentes em cada divisão
  • Subamostragem de coluna: como florestas aleatórias, amostras de recursos em cada divisão para diversidade
  • Esboço de quantil ponderado: encontra pontos de divisão com eficiência para recursos contínuos em dados distribuídos
  • Estrutura de bloco com reconhecimento de cache: Layout de memória otimizado para linhas de cache da CPU

Para dados tabulares, XGBoost (e seu sucessor LightGBM) supera consistentemente as redes neurais. Isso não vai mudar tão cedo. Se seus dados couberem em uma tabela com linhas e colunas, comece com o aumento de gradiente.

Empilhamento (Meta-Aprendizado)

O empilhamento usa as previsões de vários modelos básicos como recursos para um meta-aluno.

flowchart TD
    D[Training Data] --> M1[Model 1: Random Forest]
    D --> M2[Model 2: SVM]
    D --> M3[Model 3: Logistic Regression]

    M1 --> P1[Predictions 1]
    M2 --> P2[Predictions 2]
    M3 --> P3[Predictions 3]

    P1 --> META[Meta-Learner]
    P2 --> META
    P3 --> META

    META --> F[Final Prediction]

O meta-aluno aprende em qual modelo base confiar para quais entradas. Se a random forest for melhor em certas regiões e SVM em outras, o meta-aluno aprenderá a rotear de acordo.

Para evitar vazamento de dados, as previsões do modelo base devem ser geradas por meio de validação cruzada no conjunto de treinamento. Você nunca treina modelos básicos e gera meta-recursos nos mesmos dados.

Votação

O conjunto mais simples. Basta combinar as previsões diretamente.

  • Votação difícil: Votação majoritária nos rótulos das classes.
  • Votação suave: Média de probabilidades previstas, escolha a classe com maior probabilidade média. Geralmente é melhor porque usa informações confiáveis.

Construa

Etapa 1: Coto de decisão (aluno básico)

O código em code/ensembles.py implementa tudo do zero. Começamos com um toco de decisão: uma árvore com uma única divisão.

class DecisionStump:
    def __init__(self):
        self.feature_idx = None
        self.threshold = None
        self.polarity = 1
        self.alpha = None

    def fit(self, X, y, weights):
        n_samples, n_features = X.shape
        best_error = float("inf")

        for f in range(n_features):
            thresholds = np.unique(X[:, f])
            for thresh in thresholds:
                for polarity in [1, -1]:
                    pred = np.ones(n_samples)
                    pred[polarity * X[:, f] < polarity * thresh] = -1
                    error = np.sum(weights[pred != y])
                    if error < best_error:
                        best_error = error
                        self.feature_idx = f
                        self.threshold = thresh
                        self.polarity = polarity

    def predict(self, X):
        n = X.shape[0]
        pred = np.ones(n)
        idx = self.polarity * X[:, self.feature_idx] < self.polarity * self.threshold
        pred[idx] = -1
        return pred

Etapa 2: AdaBoost do zero

class AdaBoostScratch:
    def __init__(self, n_estimators=50):
        self.n_estimators = n_estimators
        self.stumps = []
        self.alphas = []

    def fit(self, X, y):
        n = X.shape[0]
        weights = np.full(n, 1 / n)

        for _ in range(self.n_estimators):
            stump = DecisionStump()
            stump.fit(X, y, weights)
            pred = stump.predict(X)

            err = np.sum(weights[pred != y])
            err = np.clip(err, 1e-10, 1 - 1e-10)

            alpha = 0.5 * np.log((1 - err) / err)
            weights *= np.exp(-alpha * y * pred)
            weights /= weights.sum()

            stump.alpha = alpha
            self.stumps.append(stump)
            self.alphas.append(alpha)

    def predict(self, X):
        total = sum(a * s.predict(X) for a, s in zip(self.alphas, self.stumps))
        return np.sign(total)

Etapa 3: Gradient Boosting do zero

class GradientBoostingScratch:
    def __init__(self, n_estimators=100, learning_rate=0.1, max_depth=3):
        self.n_estimators = n_estimators
        self.lr = learning_rate
        self.max_depth = max_depth
        self.trees = []
        self.initial_pred = None

    def fit(self, X, y):
        self.initial_pred = np.mean(y)
        current_pred = np.full(len(y), self.initial_pred)

        for _ in range(self.n_estimators):
            residuals = y - current_pred
            tree = SimpleRegressionTree(max_depth=self.max_depth)
            tree.fit(X, residuals)
            update = tree.predict(X)
            current_pred += self.lr * update
            self.trees.append(tree)

    def predict(self, X):
        pred = np.full(X.shape[0], self.initial_pred)
        for tree in self.trees:
            pred += self.lr * tree.predict(X)
        return pred

Etapa 4: compare com o sklearn

O código verifica se nossas implementações do zero produzem precisão semelhante à AdaBoostClassifier e GradientBoostingClassifier do sklearn e compara todos os métodos lado a lado.

Use-o

Quando usar cada método

Método Reduz Melhor para Cuidado com
Bagging / Random Forest Variância Dados barulhentos, muitos recursos Não ajuda com preconceito
AdaBoost Viés Dados limpos, alunos básicos simples Sensível a outliers e ruído
Gradient Boosting Viés Dados tabulares, concursos Lento para treinar, fácil de overfit sem ajuste
XGBoost / LightGBM Ambos Produção tabular ML Muitos hiperparâmetros
Empilhamento Ambos Obtendo a última precisão de 1-2% Complexo, risco de overfitting do meta-aluno
Votação Variância Combinação rápida de diversos modelos Só ajuda se os modelos forem diversos

A pilha de produção para dados tabulares

Para a maioria dos problemas de previsão tabular, esta é a ordem a ser tentada:

  1. LightGBM ou XGBoost com parâmetros padrão
  2. Ajuste n_estimadores, taxa de aprendizagem, profundidade máxima, min_child_weight
  3. Se você precisar dos últimos 0,5%, construa um conjunto de empilhamento com 3 a 5 modelos diversos
  4. Use validação cruzada em todo o processo

As redes neurais em dados tabulares são quase sempre piores do que o aumento de gradiente, apesar das contínuas tentativas de pesquisa. TabNet, NODE e arquiteturas semelhantes ocasionalmente combinam, mas raramente superam um XGBoost bem ajustado.

Envie

Esta lição produz outputs/prompt-ensemble-selector.md – um prompt que ajuda você a escolher o método de conjunto correto para um determinado conjunto de dados. Descreva seus dados (tamanho, tipos de recursos, nível de ruído, equilíbrio de classes) e o problema que você está resolvendo. O prompt percorre uma lista de verificação de decisão, recomenda um método, sugere o início de hiperparâmetros e alerta sobre erros comuns nesse método. Também produz outputs/skill-ensemble-builder.md com guia de seleção completo.

Exercícios

  1. Modifique a implementação AdaBoost para monitorar a precisão do treinamento após cada rodada. Precisão do gráfico versus número de estimadores. Quando isso converge?

  2. Implemente uma random forest do zero adicionando subamostragem de recursos aleatórios à árvore de regressão. Treine 100 árvores com max_features=sqrt(n_features) e previsões médias. Compare a redução da variância com uma única árvore.

  3. Na implementação do aumento de gradiente, adicione parada antecipada: rastreie a perda de validação após cada rodada e pare quando não melhorar por 10 rodadas consecutivas. De quantas árvores ele realmente precisa?

  4. Construir um conjunto de empilhamento com três modelos básicos (regressão logística, árvore de decisão, k-vizinhos mais próximos) e um meta-aluno de regressão logística. Use validação cruzada de 5 vezes para gerar meta-recursos. Compare com cada modelo básico sozinho.

  5. Execute XGBoost no mesmo conjunto de dados com parâmetros padrão. Compare sua precisão com o aumento de gradiente do zero. Tempo ambos. Qual é a diferença de velocidade?

Termos-chave

Prazo O que as pessoas dizem O que isso realmente significa
Bagging "Treinar em subconjuntos aleatórios" Agregação de bootstrap: treinar modelos em amostras de bootstrap, previsões médias para reduzir a variância
Boosting "Foco em exemplos difíceis" Treinar modelos sequencialmente, cada um corrigindo os erros do conjunto até o momento, para reduzir o viés
AdaBoost “Repesar os dados” Boosting por meio de atualizações de peso de amostra; pontos classificados incorretamente ganham peso maior para o próximo aluno
Aumento de gradiente “Ajuste os resíduos” Boosting ajustando cada novo modelo ao gradiente negativo da função de perda
XGBoost “A arma Kaggle” Aumento de gradiente com regularização, otimização de segunda ordem e truques de velocidade em nível de sistema
Empilhamento “Modelos em cima de modelos” Use previsões de modelos básicos como recursos de entrada para um meta-aluno
Floresta aleatória “Muitas árvores aleatórias” Bagging com árvores de decisão, adicionando subamostragem de características aleatórias em cada divisão para diversidade
Diversidade de conjunto “Cometa erros diferentes” Os modelos devem ser não correlacionados em seus erros para que o conjunto melhore em relação aos indivíduos
Erro fora da sacola “Validação gratuita” Amostras que não estão em um sorteio bootstrap (~36,8%) servem como um conjunto de validação sem a necessidade de validação

Leitura Adicional

0 lifetime access. Curriculum based on AI Engineering from Scratch by Rohit Ghumare (MIT, used under attribution).