Class Imbalance
Desbalanceamento de Classes
O desbalanceamento de classes ocorre quando a distribuição das classes alvo é altamente assimétrica — uma ou mais classes dominam o dataset. Isso é a norma, não a exceção, em problemas do mundo real: detecção de fraude (99,9% legítimo), diagnóstico de doenças (95% saudável), detecção de spam (80% e-mail legítimo).
Por que o Desbalanceamento Quebra o Treinamento Padrão
Um modelo treinado com perda de entropia cruzada em um dataset desbalanceado irá maximizar a acurácia prevendo a classe majoritária. Com 99% de negativos, prever sempre negativo dá 99% de acurácia — mas 0% de recall nos positivos.
Estratégias para Lidar com Desbalanceamento
1. Use a Métrica Correta
Primeiro e mais importante: pare de usar acurácia. Use:
| Métrica | Bom quando |
|---|---|
| F1-Score (macro/ponderado) | Binário ou multiclasse, quer equilíbrio |
| PR-AUC (Precision-Recall) | Quando positivos são raros e importantes |
| ROC-AUC | Quando você precisa de avaliação independente do limiar |
| Matthews Correlation Coefficient (MCC) | Classificação binária, muito desbalanceado |
| G-Mean | Média geométrica do recall por classe |
2. Pesos de Classe
Diga à função de perda para penalizar mais os erros na classe minoritária:
from sklearn.linear_model import LogisticRegression
from sklearn.utils.class_weight import compute_class_weight
import numpy as np
# Cálculo automático de pesos de classe
weights = compute_class_weight('balanced', classes=np.unique(y_train), y=y_train)
class_weight = dict(zip(np.unique(y_train), weights))
# Para modelos sklearn
model = LogisticRegression(class_weight='balanced')
# Para PyTorch
import torch
pos_weight = torch.tensor([neg_count / pos_count])
criterion = torch.nn.BCEWithLogitsLoss(pos_weight=pos_weight)
3. Oversampling — SMOTE
SMOTE (Synthetic Minority Over-sampling Technique) gera amostras minoritárias sintéticas interpolando entre as existentes:
from imblearn.over_sampling import SMOTE
smote = SMOTE(sampling_strategy='auto', k_neighbors=5, random_state=42)
X_resampled, y_resampled = smote.fit_resample(X_train, y_train)
print(f"Antes: {dict(zip(*np.unique(y_train, return_counts=True)))}")
print(f"Depois: {dict(zip(*np.unique(y_resampled, return_counts=True)))}")
Aplique SMOTE apenas nos dados de treino
Nunca reamostre o conjunto de validação ou teste. Isso daria uma distribuição de classes irreal não representativa da produção.
4. Undersampling
Remover amostras da classe majoritária. Mais simples que SMOTE, mas perde informação:
from imblearn.under_sampling import RandomUnderSampler, TomekLinks
# Undersampling aleatório
rus = RandomUnderSampler(sampling_strategy=0.5) # minoritária:majoritária = 1:2
X_res, y_res = rus.fit_resample(X_train, y_train)
# Tomek Links: remover amostras majoritárias próximas à fronteira
tl = TomekLinks()
X_res, y_res = tl.fit_resample(X_train, y_train)
5. Ajuste de Limiar
Classificadores padrão produzem probabilidades. O limiar padrão (0,5) pode não ser ótimo para dados desbalanceados. Ajuste-o no conjunto de validação:
from sklearn.metrics import precision_recall_curve
import numpy as np
probs = model.predict_proba(X_val)[:, 1]
precisions, recalls, thresholds = precision_recall_curve(y_val, probs)
# Encontrar limiar que maximiza F1
f1_scores = 2 * precisions * recalls / (precisions + recalls + 1e-8)
best_thresh = thresholds[np.argmax(f1_scores)]
y_pred = (probs >= best_thresh).astype(int)
Guia de Decisão de Estratégia
flowchart TD
A[Dataset desbalanceado] --> B{Razão de desbalanceamento?}
B -->|"< 1:10"| C[Pesos de classe\ngeralmente suficiente]
B -->|"1:10 a 1:100"| D{Amostras minoritárias suficientes?}
B -->|"> 1:100"| E[Combinar: SMOTE\n+ pesos de classe\n+ ajuste de limiar]
D -->|"> 500 amostras"| F[SMOTE ou\nabordagem combinada]
D -->|"< 500 amostras"| G[Pesos de classe\n+ coleta de dados]
C --> H[Avaliar com\nPR-AUC / F1]
F --> H
E --> H
G --> H Desbalanceamento em Aprendizado Profundo
Para redes neurais com grandes datasets, pesos de classe geralmente são a solução mais prática e eficaz. SMOTE em milhões de amostras é lento e as amostras sintéticas podem não corresponder bem à distribuição de features aprendida.
Técnicas adicionais específicas para aprendizado profundo:
| Técnica | Ideia |
|---|---|
| Focal Loss | Diminui o peso de exemplos fáceis (majoritários bem classificados), foca nos difíceis minoritários |
| Suavização de Rótulos | Previne previsões excessivamente confiantes na classe majoritária |
| Amostragem de Batch Balanceado | Garantir que cada batch contenha amostras iguais de cada classe |
| Mixup | Interpolar entre amostras de classes diferentes |
# Focal Loss (binária)
class FocalLoss(torch.nn.Module):
def __init__(self, gamma=2.0, alpha=0.25):
super().__init__()
self.gamma, self.alpha = gamma, alpha
def forward(self, pred, target):
bce = torch.nn.functional.binary_cross_entropy_with_logits(pred, target, reduction='none')
pt = torch.exp(-bce)
return (self.alpha * (1 - pt)**self.gamma * bce).mean()