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:
- LightGBM ou XGBoost com parâmetros padrão
- Ajuste n_estimadores, taxa de aprendizagem, profundidade máxima, min_child_weight
- Se você precisar dos últimos 0,5%, construa um conjunto de empilhamento com 3 a 5 modelos diversos
- 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
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?
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.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?
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.
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
- Schapire & Freund: Boosting: Fundações e Algoritmos - o livro dos criadores de AdaBoost
- Friedman: Greedy Function Aproximação: A Gradient Boosting Machine (2001) - o papel original para aumentar o gradiente
- Chen & Guestrin: XGBoost (2016) - o artigo XGBoost
- Wolpert: Stacked Generalization (1992) - o papel de empilhamento original
- scikit-learn Métodos de conjunto - referência prática