← Blog

SMOTE, ADASYN e outras técnicas para classes desbalanceadas

2023-09-15PythonScikit-learnImbalanced-learn

O que é desbalanceamento de classes e por que importa

Em muitos problemas reais de classificação, as classes não aparecem em proporções parecidas. Em detecção de fraude, inadimplência ou churn, a classe que você quer prever costuma ser a minoritária — às vezes menos de 5% dos registros.

Um modelo treinado nesse cenário sem cuidado aprende o atalho mais fácil: prever sempre a classe majoritária. A acurácia fica alta (95% ou mais), mas o modelo não detecta nenhum caso relevante. Em produção, isso significa fraude não flagada, clientes em risco ignorados ou crédito negado sem critério.

O desbalanceamento não é um detalhe técnico — é o que separa um modelo que "parece bom no notebook" de um que resolve o problema de negócio. Antes de escolher um algoritmo, você precisa medir o desbalanceamento e escolher uma estratégia coerente com o custo de cada tipo de erro.

Conceitos essenciais

Três ideias guiam a maioria das soluções:

  1. Distribuição das classes — a razão entre minoritária e majoritária define a severidade do problema
  2. Métricas adequadas — acurácia engana; precision, recall, F1 e AUC-PR refletem melhor o desempenho na classe rara
  3. Estratégia de reamostragem — alterar o dataset de treino (oversampling, undersampling ou combinação) ou ajustar pesos no modelo

Duas famílias de técnica aparecem com frequência:

  • Oversampling — cria exemplos sintéticos da classe minoritária (SMOTE, ADASYN) ou duplica registros existentes
  • Undersampling — remove exemplos da classe majoritária para equilibrar (Random UnderSampler, Tomek links)
  • Class weights — mantém o dataset original, mas penaliza mais erros na classe minoritária durante o treino

Nenhuma é universal. Oversampling pode causar overfitting em datasets pequenos; undersampling descarta informação; class weights funcionam bem com modelos que suportam class_weight, mas não resolvem todos os casos.

Diagnóstico: medir antes de tratar

O primeiro passo é quantificar o problema. Com pandas e scikit-learn:

import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report, average_precision_score

# Carregar dados — target binário: 1 = inadimplente, 0 = adimplente
df = pd.read_csv("credit_data.csv")
X = df.drop("default", axis=1)
y = df["default"]

# Ver proporção das classes
print(y.value_counts(normalize=True))
# default
# 0    0.94
# 1    0.06

X_train, X_test, y_train, y_test = train_test_split(
    X, y, test_size=0.2, stratify=y, random_state=42
)

stratify=y no split é obrigatório em dados desbalanceados — garante que treino e teste mantenham a mesma proporção de classes.

Treine um baseline sem tratamento e avalie com métricas que importam para a classe minoritária:

from sklearn.ensemble import RandomForestClassifier

baseline = RandomForestClassifier(random_state=42)
baseline.fit(X_train, y_train)
y_pred = baseline.predict(X_test)

print(classification_report(y_test, y_pred))
print("AUC-PR:", average_precision_score(y_test, baseline.predict_proba(X_test)[:, 1]))

Se o recall da classe 1 estiver próximo de zero, você confirmou o problema — hora de aplicar uma técnica de reamostragem.

SMOTE: oversampling sintético

SMOTE (Synthetic Minority Over-sampling Technique) cria exemplos sintéticos da classe minoritária interpolando entre vizinhos próximos no espaço de features. Em vez de duplicar registros idênticos, gera pontos novos que preservam a estrutura local dos dados.

from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline

smote = SMOTE(random_state=42, k_neighbors=5)
X_train_smote, y_train_smote = smote.fit_resample(X_train, y_train)

print(y_train_smote.value_counts())
# default
# 0    15000
# 1    15000

O parâmetro k_neighbors define quantos vizinhos usar na interpolação. Em datasets muito pequenos na classe minoritária, reduza k_neighbors (mínimo 1) para evitar erros.

Integre SMOTE em um pipeline para evitar data leakage — o oversampling deve acontecer apenas no fold de treino, nunca no conjunto de teste:

pipeline = ImbPipeline([
    ("smote", SMOTE(random_state=42)),
    ("classifier", RandomForestClassifier(random_state=42)),
])

pipeline.fit(X_train, y_train)
y_pred = pipeline.predict(X_test)

ADASYN: oversampling adaptativo

ADASYN (Adaptive Synthetic Sampling) é uma evolução do SMOTE. Em vez de gerar a mesma quantidade de exemplos sintéticos para todos os pontos da minoritária, concentra a geração nas regiões onde a classe majoritária domina — ou seja, onde o classificador teria mais dificuldade.

Isso faz sentido quando a fronteira de decisão é irregular: fraudes que "parecem" transações normais precisam de mais exemplos sintéticos nessa região.

from imblearn.over_sampling import ADASYN

adasyn = ADASYN(random_state=42, n_neighbors=5)
X_train_ada, y_train_ada = adasyn.fit_resample(X_train, y_train)

Na prática, SMOTE e ADASYN costumam ter desempenho parecido. ADASYN tende a ajudar quando a minoritária tem subclusters com densidades diferentes; SMOTE é mais simples de explicar e debugar.

Undersampling e combinações

Quando o dataset é enorme e a classe majoritária tem milhões de registros, oversampling pode inflar o treino desnecessariamente. Nesse caso, undersampling da majoritária é uma alternativa:

from imblearn.under_sampling import RandomUnderSampler

rus = RandomUnderSampler(random_state=42)
X_train_under, y_train_under = rus.fit_resample(X_train, y_train)

O risco é perder informação valiosa. Uma abordagem intermediária é combinar as duas:

from imblearn.combine import SMOTETomek

smt = SMOTETomek(random_state=42)
X_train_combo, y_train_combo = smt.fit_resample(X_train, y_train)

SMOTETomek aplica SMOTE e depois remove pares Tomek link — exemplos da majoritária que estão na fronteira e confundem o modelo. É uma técnica robusta para começar em projetos de crédito e fraude.

Class weights: alternativa sem reamostragem

Modelos como Random Forest, Logistic Regression e XGBoost aceitam pesos por classe. Você mantém o dataset original e instrui o algoritmo a penalizar mais erros na minoritária:

from sklearn.utils.class_weight import compute_class_weight
import numpy as np

weights = compute_class_weight("balanced", classes=np.unique(y_train), y=y_train)
class_weight = dict(zip(np.unique(y_train), weights))

model = RandomForestClassifier(class_weight=class_weight, random_state=42)
model.fit(X_train, y_train)

class_weight="balanced" no scikit-learn faz o mesmo automaticamente. A vantagem é simplicidade e velocidade; a desvantagem é que nem sempre recupera o recall tão bem quanto SMOTE em datasets muito desbalanceados (razão acima de 1:50).

Além do básico: pontos de atenção

Algumas armadilhas aparecem com frequência em projetos reais:

  • Aplicar SMOTE antes do split — vaza informação do teste para o treino; use pipeline ou aplique só em X_train
  • Otimizar acurácia — em 94/6, prever sempre 0 dá 94% de acurácia; use F1, recall ou AUC-PR na classe 1
  • Oversampling em features categóricas sem encoding — SMOTE opera em espaço numérico; encode antes (one-hot, target encoding)
  • Ignorar o threshold de decisão — após o treino, ajustar o threshold (ex.: 0.3 em vez de 0.5) costuma melhorar recall sem reamostragem
  • Comparar técnicas no mesmo split — fixe random_state e use validação cruzada estratificada para comparar SMOTE, ADASYN e class weights de forma justa

Para produção, documente qual técnica foi usada e congele o pipeline (incluindo o sampler) com joblib ou MLflow — reamostragem faz parte do artefato de treino, não é um passo manual descartável.

Conclusão

Dados desbalanceados são a regra em classificação aplicada a crédito, fraude e churn — não a exceção. O caminho prático é: medir a distribuição, treinar um baseline, escolher métricas adequadas e testar SMOTE, ADASYN ou class weights dentro de um pipeline que evite leakage.

Comece com SMOTETomek + Random Forest em validação cruzada estratificada. Se o recall da minoritária ainda for insuficiente, experimente ADASYN ou ajuste o threshold de decisão. O próximo passo natural é colocar esse pipeline em produção — o tema do post sobre score de crédito com Streamlit.

Referencias

  1. SMOTE: Synthetic Minority Over-sampling Technique (Chawla et al., 2002)
  2. ADASYN: Adaptive Synthetic Sampling (He et al., 2008)
  3. imbalanced-learn — Documentação oficial
  4. imbalanced-learn — SMOTE
  5. imbalanced-learn — Pipelines com samplers
  6. Scikit-learn — Metrics and scoring
  7. Scikit-learn — class_weight