Validação & Vazamento de Dados
A regra de ouro — nunca avaliar em dados de treino — parece trivial. Esta aula a transforma em prática de engenharia: como dividir os dados, como validar honestamente com dados limitados e como detectar o vazamento de dados (data leakage), o modo de falha responsável pela maioria dos resultados "bons demais para ser verdade" em projetos e competições reais.
Treino / validação / teste
flowchart LR
D[Todos os dados] --> TR[Conjunto de treino<br><small>ajustar modelos</small>]
D --> VA[Conjunto de validação<br><small>comparar modelos,<br>ajustar hiperparâmetros</small>]
D --> TE[Conjunto de teste<br><small>estimativa final,<br>tocado UMA VEZ</small>] - Conjunto de treino — o modelo aprende seus parâmetros aqui;
- Conjunto de validação — usado para escolher entre modelos e valores de hiperparâmetros. Como você toma decisões nele, o desempenho nele se torna otimisticamente enviesado com o tempo;
- Conjunto de teste — simula o futuro. Usado uma vez, no fim de tudo. Se você itera contra o conjunto de teste, ele silenciosamente vira um conjunto de validação e você deixa de ter uma estimativa honesta.
from sklearn.model_selection import train_test_split
X_temp, X_test, y_temp, y_test = train_test_split(X, y, test_size=0.2, random_state=42,
stratify=y) # manter as proporções de classe
X_train, X_val, y_train, y_val = train_test_split(X_temp, y_temp, test_size=0.25,
random_state=42, stratify=y_temp)
stratify=y preserva as proporções de classe em cada divisão — importante para classificação desbalanceada (ver ROC-AUC & Dados Desbalanceados).
Validação cruzada
Uma única divisão de validação desperdiça dados e dá uma estimativa ruidosa — com conjuntos pequenos, a sorte da divisão pode dominar. A validação cruzada k-fold (tipicamente \(k = 5\) ou \(10\)) resolve ambos:
- particione os dados de treino em \(k\) folds;
- para cada fold: treine nos outros \(k-1\), avalie nele;
- reporte a média (e o desvio padrão!) dos \(k\) escores.
flowchart TD
subgraph CV de 5 folds
R1["fold1 = <b>val</b> | fold2..5 = treino"]
R2["fold2 = <b>val</b> | outros = treino"]
R3["..."]
R5["fold5 = <b>val</b> | outros = treino"]
end
R1 & R2 & R3 & R5 --> M["média ± desvio dos 5 escores"] from sklearn.model_selection import cross_val_score, StratifiedKFold
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipe, X_train, y_train, cv=cv, scoring='f1')
print(f"{scores.mean():.3f} ± {scores.std():.3f}")
Toda observação serve de validação exatamente uma vez; a dispersão dos escores diz o quanto confiar na média.
Escolhendo um esquema de CV
| Método | Quando usar | Vantagens | Cuidado com |
|---|---|---|---|
| K-Fold | dados gerais (o padrão) | simples, eficiente | não preserva as proporções de classe |
| Stratified K-Fold | classificação (especialmente desbalanceada) | mantém as proporções de classe em cada fold | um pouco mais lento |
| Leave-One-Out (LOO) | conjuntos muito pequenos (< 100–200 amostras) | usa quase todos os dados para treinar | muito lento (\(n\) folds), alta variância |
| TimeSeriesSplit | dados temporais / séries temporais | respeita a ordem cronológica (sem vazamento) | menos flexível |
| Group K-Fold | dados agrupados (pacientes, usuários, ...) | evita vazamento entre linhas relacionadas | exige uma coluna de grupo |
O conjunto de teste fica fora de todo o procedimento: a validação cruzada substitui o conjunto de validação, não o de teste.
O pré-processamento precisa reiniciar em cada fold
Escalonamento, codificação, imputação e seleção de atributos precisam ser reajustados do zero dentro de cada fold. Se você faz StandardScaler().fit_transform() ou SelectKBest().fit() no conjunto inteiro antes do cross_val_score/GridSearchCV, as estatísticas do fold de validação vazam para o treino — um escore otimisticamente enviesado. Passar um Pipeline torna isso automático: em cada fold, todos os passos são ajustados apenas na porção de treino daquele fold e depois aplicados congelados à porção de validação. Cada fold recebe seu próprio scaler, seus próprios atributos selecionados e seu próprio modelo.
Vazamento de dados
Vazamento = informação de fora do fold de treino se infiltrando no treino. O modelo parece brilhante na avaliação e desaba em produção, onde a informação vazada ainda não existe. Os padrões clássicos:
1. Pré-processamento antes da divisão
Ajustar um scaler, imputador ou seletor de atributos em todos os dados antes da divisão vaza estatísticas do conjunto de teste para o treino. Você viu isso em Pré-processamento; a cura é estrutural — coloque todo passo ajustado dentro de um Pipeline para que seja reajustado em cada fold.
2. Atributos que conhecem o futuro
Uma coluna registrada depois do momento da previsão: prever readmissão hospitalar usando number_of_followup_visits; prever churn usando account_closed_date is null. Impecável na tabela histórica, inexistente no momento da previsão. Pergunte de todo atributo: "esse valor está disponível no momento em que a previsão precisa ser feita?"
3. Linhas duplicadas ou quase duplicadas
O mesmo registro (ou cópias trivialmente perturbadas) caindo em treino e teste — comum após aumento de dados (data augmentation) ou junções. O modelo é avaliado em perguntas que memorizou.
4. Vazamento de grupo
Múltiplas linhas por entidade (várias imagens por paciente, vários pedidos por cliente) divididas aleatoriamente: o modelo reconhece o paciente, não a doença. Use GroupKFold chaveado pela entidade.
5. Vazamento temporal
Embaralhar aleatoriamente dados com marca de tempo treina o modelo no futuro para prever o passado. Sempre divida cronologicamente; valide com TimeSeriesSplit.
O teste do cheiro
Se os resultados parecem bons demais — 99% de acurácia num problema difícil, um atributo com importância implausível, uma enorme diferença entre métricas offline e de produção — suspeite de vazamento primeiro, não de genialidade. Confira os principais atributos em um relatório de explicabilidade: atributos vazados costumam dominá-lo.
Um experimento sem vazamento, de ponta a ponta
O fluxo honesto completo, como praticado em aula no conjunto de câncer de mama — divisão hold-out, pipeline, CV estratificada dentro de uma busca em grade, um ajuste final, uma medição final:
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, StratifiedKFold, GridSearchCV
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import roc_auc_score
# 1. Divisão hold-out final (muito importante!)
X, y = load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.15, random_state=42, stratify=y)
# 2. Pipeline: todo passo ajustado dentro, para que a CV o reajuste por fold
pipeline = Pipeline([
('scaler', StandardScaler()),
('clf', RandomForestClassifier(random_state=42)),
])
# 3. Desenvolvimento do modelo: busca em grade com CV estratificada só no TREINO
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
param_grid = {
'clf__n_estimators': [100, 200, 300],
'clf__max_depth': [5, 10, 20],
}
grid = GridSearchCV(pipeline, param_grid, cv=cv, scoring='roc_auc', n_jobs=-1)
grid.fit(X_train, y_train)
# 4. O modelo de produção: melhor configuração reajustada em TODOS os dados de treino
final_model = grid.best_estimator_
final_model.fit(X_train, y_train)
# 5. Avaliação honesta — só agora, só uma vez
proba = final_model.predict_proba(X_test)[:, 1]
print("ROC AUC de teste:", roc_auc_score(y_test, proba))
Exercícios
- No conjunto iris, compare validação cruzada K-Fold, Stratified K-Fold e Leave-One-Out: reporte acurácia média ± desvio para cada e explique as diferenças.
- Usando o índice BOVESPA (preços de
Closeviayfinance, ticker^BVSP, 2 anos): useTimeSeriesSplitpara a validação cruzada e ajuste uma regressão linear para prever o índice 20 dias à frente. Por que o K-Fold embaralhado seria desonesto aqui? - Pesquise o
SelectKBest: por que a seleção de atributos também precisa viver dentro do pipeline?
Material de aula
Notebook da aula (em português)
Notebook prático usado em sala — Aula 10 — Divisão de Dados, Data Leakage e Validação Cruzada: abrir no Colab