Ir para o conteúdo

ROC-AUC & Dados Desbalanceados

A aula passada terminou em uma constatação-chave: um classificador produz escores, e o rótulo depende de um limiar. Esta aula avalia modelos em todos os limiares — as curvas ROC e precisão–revocação — e então enfrenta o cenário onde tudo isso mais importa: conjuntos de dados desbalanceados, em que a classe interessante é rara.

A curva ROC

Percorra o limiar do estrito ao permissivo e plote, em cada ponto:

\[ \text{TPR (revocação)} = \frac{TP}{TP + FN} \qquad\text{vs.}\qquad \text{FPR} = \frac{FP}{FP + TN} \]
  • limiar ≈ 1: nada sinalizado — canto inferior esquerdo (0, 0);
  • limiar ≈ 0: tudo sinalizado — canto superior direito (1, 1);
  • no meio: quanta revocação o modelo compra por unidade de alarme falso.

Um escorador aleatório se move ao longo da diagonal (TPR = FPR); um perfeito abraça o canto superior esquerdo (100% de revocação com 0% de alarmes falsos).

AUC

A Área Sob a Curva ROC (AUC) comprime a curva em um único número livre de limiar, com um belo significado probabilístico:

\[ \text{AUC} = P\big(\text{escore}(x^+) > \text{escore}(x^-)\big) \]

— a probabilidade de que um positivo aleatório seja ordenado acima de um negativo aleatório. AUC 0,5 = cara ou coroa, 1,0 = ordenação perfeita. Ela mede a qualidade da ordenação: quão bem o modelo ordena os casos, independentemente de qualquer escolha de limiar.

Curva ROC e curva precisão-revocação em dados desbalanceados

from sklearn.metrics import roc_auc_score, RocCurveDisplay
roc_auc_score(y_test, model.predict_proba(X_test)[:, 1])   # precisa de escores, não rótulos!

Quando a ROC lisonjeia: use precisão–revocação

O denominador da FPR é o número de negativos. Com 95% de negativos, mesmo um modelo ruim mantém a FPR pequena em termos absolutos — a curva ROC parece ótima enquanto os alarmes do modelo são majoritariamente falsos. A curva precisão–revocação (painel direito) substitui a FPR pela precisão, cujo denominador são os próprios alarmes do modelo, tornando-a brutalmente honesta sobre positivos raros: sua baseline de modelo aleatório é a prevalência (aqui 0,05), não uma diagonal.

Regra de bolso: classes balanceadas ou custos em ambas as classes → ROC-AUC; classe positiva rara e você se importa com os positivos → curva PR e precisão média (AP).

Veja o trade-off do limiar ao vivo — deslize-o e observe cada métrica (e o ponto de operação da ROC) reagir de uma vez:

Aprendendo com dados desbalanceados

Métricas resolvidas, agora o lado do treino. Com desbalanceamento de 1:1000, a maioria dos aprendizes — que por padrão otimizam objetivos parecidos com acurácia — converge para "preveja a maioria".

1. Acerte a avaliação primeiro

Divisões estratificadas (Validação), PR-AUC / macro-F1 / revocação a precisão fixa — antes de tocar nos dados. Muitos "problemas de desbalanceamento" são, na verdade, problemas de métrica.

2. Pesos de classe: torne os erros da minoria caros

A maioria dos classificadores do scikit-learn aceita class_weight, multiplicando a contribuição de cada classe à perda por \(w_c \propto n / (k \cdot n_c)\):

LogisticRegression(class_weight='balanced')
SVC(class_weight='balanced')
RandomForestClassifier(class_weight='balanced')

Sem manipulação de dados, sem perda de informação — geralmente a primeira coisa a tentar.

3. Reamostragem: mude os dados

  • Subamostragem aleatória — descartar exemplos da maioria. Rápido; descarta informação; viável quando os dados são abundantes;
  • Sobreamostragem aleatória — duplicar exemplos da minoria. Mantém todos os dados; duplicatas convidam ao sobreajuste;
  • SMOTE (Chawla et al., 2002) — sintetizar novos pontos da minoria interpolando entre um exemplo da minoria e seus vizinhos mais próximos da minoria: \(x_{\text{new}} = x_i + \lambda\,(x_{\text{nn}} - x_i)\), \(\lambda \sim U(0,1)\). Mais rico que a duplicação, mas pode fabricar pontos em regiões de sobreposição ruidosas — variantes (Borderline-SMOTE, SMOTE-Tomek) mitigam.
# pip install imbalanced-learn
from imblearn.over_sampling import SMOTE
from imblearn.pipeline import Pipeline as ImbPipeline

pipe = ImbPipeline([
    ('scaler', StandardScaler()),
    ('smote', SMOTE(random_state=0)),      # aplicado SÓ no ajuste
    ('model', LogisticRegression()),
])

Reamostre dentro do pipeline, após a divisão

Sobreamostrar antes da divisão treino/teste copia (ou interpola) pontos da minoria para os dois lados — o modelo é testado em ecos dos próprios dados de treino: vazamento com escores inflados. O pipeline do imblearn aplica a reamostragem apenas aos folds de treino e nunca aos dados de validação/teste.

4. Mova o limiar

Muitas vezes a correção mais barata: mantenha o modelo, escolha o limiar que atende à restrição de negócio ("revocação ≥ 90%", "precisão ≥ 80%", ou custo esperado mínimo) usando a curva PR em dados de validação — nunca no teste.

Uma ordem sensata de ataque

flowchart LR
    A[Métricas certas +<br>CV estratificada] --> B[class_weight<br>= balanced] --> C[Ajuste de limiar<br>na validação] --> D[SMOTE / reamostragem<br>se ainda necessário] --> E[Coletar mais<br>dados da minoria]

Material de aula

Notebook da aula (em português)

Notebook prático usado em sala — Aula 15 — Curva ROC-AUC e Datasets Desbalanceados: abrir no Colab


Quiz