Validação e validação cruzada
Validação (validation) e validação cruzada (cross-validation) são a espinha dorsal de uma avaliação confiável de aprendizado de máquina (machine learning). Elas ajudam você a estimar o quão bem um modelo vai generalizar para novos dados, escolher hiperparâmetros (hyperparameters) sem se enganar, e detectar problemas como vazamento de dados (data leakage) que podem fazer os resultados parecerem espetaculares no desenvolvimento e falharem em produção.
Este artigo foca em estratégias de particionamento (split), prevenção de vazamentos e práticas de avaliação robusta que se sustentam sob escrutínio.
Por que a validação existe
Um modelo pode se ajustar extremamente bem aos dados de treinamento e ainda assim ter desempenho ruim em exemplos novos — o clássico sobreajuste (overfitting). A validação lida com isso simulando dados “futuros” usando subconjuntos retidos, para que você possa:
- Selecionar modelos e hiperparâmetros (por exemplo, força de regularização (regularization strength), profundidade da árvore (tree depth), taxa de aprendizado (learning rate)).
- Ajustar procedimentos de treinamento (por exemplo, parada antecipada (early stopping) em aprendizado profundo (deep learning)).
- Estimar desempenho de generalização (generalization performance) com incerteza (variância (variance) entre partições).
- Comparar abordagens de forma justa (mesmas partições, mesmo protocolo).
A validação faz parte do conjunto mais amplo de ferramentas de avaliação, ao lado de Métricas, Calibração, Análise de Erros (Fatiamento) e Desenho de Experimentos e Poder.
Treino/validação/teste: a divisão básica
Um ponto de partida comum é uma divisão em três partes:
- Conjunto de treinamento (training set): usado para ajustar os parâmetros do modelo.
- Conjunto de validação (validation set): usado para escolher hiperparâmetros e tomar decisões de modelagem.
- Conjunto de teste (test set): usado uma única vez no final para uma estimativa final imparcial.
Fluxo de trabalho típico
- Divida os dados em treino/validação/teste.
- Itere em atributos, pré-processamento (preprocessing), classe de modelo e hiperparâmetros usando apenas treino + validação.
- Congele as decisões.
- Avalie exatamente uma vez no conjunto de teste.
Proporções comuns de divisão (orientação aproximada)
- Conjuntos de dados grandes: 80/10/10 ou 90/5/5
- Conjuntos de dados moderados: 70/15/15
- Conjuntos de dados pequenos: considere validação cruzada em vez de uma única divisão de validação
A ideia-chave não é a proporção — é disciplina: o conjunto de teste não pode influenciar decisões.
Validação cruzada: avalie com mais robustez
Uma única divisão de validação pode ser ruidosa: o desempenho pode mudar de forma perceptível dependendo de quais exemplos caíram na validação. Validação cruzada (cross-validation, CV) reduz essa variância ao tirar a média do desempenho em múltiplas partições.
Validação cruzada k-fold
Na validação cruzada em k dobras (k-fold cross-validation), você divide o conjunto de dados em k dobras (folds). Você treina k vezes, a cada vez usando:
- (k − 1) dobras para treinamento
- 1 dobra para validação (a “dobra retida”)
Em seguida, você calcula a média da métrica entre as dobras.
Escolhas típicas: k = 5 ou k = 10.
Prós
- Estimativa mais estável do que uma única divisão
- Uso eficiente de dados limitados
Contras
- Mais computação (k treinamentos)
- Exige cuidado com vazamento (especialmente com pré-processamento e engenharia de atributos)
K-fold estratificado (classificação)
Se você tem desbalanceamento de classes (class imbalance), dobras aleatórias podem ter proporções de classe muito diferentes. StratifiedKFold mantém as proporções de classe semelhantes entre as dobras.
Isso costuma ser essencial em classificação desbalanceada; veja Desbalanceamento de Classes.
Validação cruzada deixa-um-de-fora (LOOCV)
Um caso especial em que k é igual ao número de amostras. Cada dobra retém um exemplo.
- Baixo viés (quase todos os dados usados para treinar a cada vez)
- Frequentemente alta variância e cara
- Raramente é o melhor padrão em práticas modernas
K-fold repetido e divisões de Monte Carlo
Para reduzir ainda mais a sensibilidade a uma partição específica de dobras:
- K-fold repetido (repeated k-fold): repete a validação cruzada k-fold múltiplas vezes com diferentes partições aleatórias.
- Monte Carlo / ShuffleSplit: amostra repetidamente divisões aleatórias de treino/validação.
Isso é útil quando o conjunto de dados é pequeno e você quer ter noção da variabilidade do desempenho.
Estratégias de divisão: combine com o processo gerador de dados
Uma “boa” divisão é aquela que imita como o modelo será usado. A suposição IID (independentes e identicamente distribuídos (independent and identically distributed, IID)) frequentemente falha em conjuntos de dados reais.
Divisão aleatória (dados tabulares quase IID)
Use quando os exemplos são independentes e extraídos da mesma distribuição. Comum em muitas tarefas tabulares de aprendizado de máquina.
Divisão estratificada (classificação)
Use quando as proporções de classe importam e você quer estimativas confiáveis para classes minoritárias.
Divisões orientadas por grupos (evite vazamento correlacionado)
Se múltiplas linhas correspondem à mesma entidade (usuário, paciente, dispositivo, documento), dividir aleatoriamente pode vazar informação porque quase duplicatas ou amostras correlacionadas podem aparecer tanto em treino quanto em validação.
Use:
- GroupKFold (grupos não se sobrepõem entre dobras)
- GroupShuffleSplit (holdout aleatório baseado em grupos)
Exemplos em que grupos importam:
- Medicina: múltiplas visitas por paciente
- Recomendadores: múltiplas interações por usuário
- Visão: múltiplos recortes/aumentos da mesma imagem original
Séries temporais e divisões temporais (encadeamento para frente)
Se o modelo prevê o futuro a partir do passado, divisões aleatórias criam vazamento temporal (time leakage).
Use:
- Treine em tempos anteriores, valide em tempos posteriores
- Janelas deslizantes/expansivas (rolling/expanding windows) (também conhecido como encadeamento para frente)
- Ferramentas como
TimeSeriesSplit(com ressalvas; veja abaixo)
Divisões espaciais
Se você modela fenômenos geoespaciais (clima, tráfego), locais próximos são correlacionados. Divisões aleatórias podem superestimar o desempenho.
Use divisões em blocos ou baseadas em regiões (treine em algumas regiões, teste em regiões retidas).
Mudança de distribuição e “divisões de implantação”
Às vezes você deve dividir por:
- Segmento de cliente
- Geografia
- Tipo de dispositivo
- Versão do produto
- Período de tempo (pré/pós mudança)
Isso não serve apenas para análise de erros; pode ser a avaliação principal se corresponder à implantação.
Vazamento de dados: o jeito mais rápido de se enganar
Vazamento ocorre quando informações indisponíveis no momento da predição influenciam o treinamento, direta ou indiretamente. O vazamento geralmente infla o desempenho na validação e desmorona em produção.
Fontes comuns de vazamento
Pré-processamento ajustado no conjunto de dados completo
- Padronização (scaling), imputação (imputation), Análise de Componentes Principais (PCA), seleção de atributos (feature selection) calculadas usando todas as linhas (incluindo validação/teste).
- Correção: ajuste transformações apenas no treinamento e aplique em validação/teste.
Vazamento de alvo (target leakage) (informação do rótulo entra nos atributos)
- Exemplo: um atributo “refunded_amount” ao prever se uma transação é fraudulenta.
- Exemplo: atributos pós-desfecho (resultados de exames coletados depois do momento do diagnóstico).
Duplicatas e quase duplicatas (near-duplicates) entre partições
- Imagens idênticas, texto padronizado, linhas repetidas.
- Correção: deduplicar, ou dividir por grupo por entidade/fonte.
Vazamento temporal
- Usar informação futura (mesmo estatísticas agregadas computadas usando linhas futuras).
- Correção: computar atributos usando apenas dados passados em relação a cada exemplo.
Vazamento por reamostragem (resampling leakage) (aprendizado com classes desbalanceadas)
- Se você superamostra (oversample) (por exemplo, SMOTE) antes de dividir, pontos sintéticos vazam para a validação.
- Correção: reamostrar dentro da dobra de treinamento apenas.
Seleção de atributos em todos os dados
- Selecionar os principais atributos usando correlação com o rótulo (label) no conjunto de dados inteiro e então fazer validação cruzada.
- Correção: a seleção de atributos deve ocorrer dentro de cada dobra de treinamento (pipeline).
Pipelines evitam muitos bugs de vazamento
Em bibliotecas como scikit-learn, um Pipeline garante que as transformações sejam ajustadas apenas nas dobras de treinamento durante a validação cruzada.
Validação cruzada aninhada: seleção de modelo + avaliação imparciais
Há um ponto sutil, mas importante: se você usa validação cruzada para escolher hiperparâmetros e depois reporta esse mesmo score de validação cruzada como desempenho, você corre o risco de viés otimista (optimistic bias) (você “testou muitas coisas” e ficou com a melhor).
A validação cruzada aninhada (nested cross-validation) separa:
- Laço interno (inner loop): ajuste de hiperparâmetros/seleção de modelo (model selection)
- Laço externo (outer loop): avaliação imparcial da abordagem selecionada
Ela é especialmente valiosa quando:
- Você tem poucos dados
- Você compara muitos modelos/hiperparâmetros
- Você precisa de uma estimativa de desempenho defensável
Avaliação robusta: além de um único número
Reporte variabilidade, não apenas a média
A validação cruzada naturalmente produz uma distribuição de scores (um por dobra). Reporte:
- Média
- Desvio padrão (ou erro padrão)
- Às vezes intervalos de confiança (confidence intervals) (com cuidado — as dobras não são totalmente independentes)
Isso ajuda partes interessadas a interpretar se uma melhoria de 0,5% é real ou ruído.
Compare modelos com protocolos pareados
Ao comparar Modelo A vs Modelo B, use:
- As mesmas partições
- Uma comparação pareada entre dobras
Isso reduz a variância devido a diferenças de amostragem. Para testes formais e análise de poder (power) adequada, conecte isso com Desenho de Experimentos e Poder.
Use predições de validação cruzada para análises mais profundas
Às vezes você quer predições por exemplo que sejam sempre feitas por um modelo que não treinou naquele exemplo. Isso habilita:
- Fatiamento confiável de erros (Análise de Erros (Fatiamento))
- Seleção de limiar (threshold selection)
- Análise de calibração (Calibração)
No scikit-learn, cross_val_predict suporta esse padrão.
Reprodutibilidade: sementes e execuções repetidas
Muitos procedimentos de treinamento são estocásticos (redes neurais (neural nets), otimização estocástica (stochastic optimization), dropout (dropout), aumento de dados (data augmentation)). Para uma avaliação robusta:
- Fixe sementes aleatórias (random seeds) para experimentos controlados
- Considere também múltiplas sementes para medir estabilidade
A validação cruzada pode ser cara em aprendizado profundo; às vezes, divisões repetidas de treino/validação com múltiplas sementes é o compromisso prático.
Exemplos práticos (Python / scikit-learn)
1) Treino/validação/teste com estratificação
from sklearn.model_selection import train_test_split
X_trainval, X_test, y_trainval, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
X_train, X_val, y_train, y_val = train_test_split(
X_trainval, y_trainval, test_size=0.25, stratify=y_trainval, random_state=42
)
# Result: 60% train, 20% val, 20% test
Use o conjunto de teste apenas depois de finalizar decisões de modelagem.
2) Pré-processamento sem vazamento com validação cruzada k-fold
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import StratifiedKFold, cross_val_score
pipe = Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression(max_iter=2000))
])
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(pipe, X, y, cv=cv, scoring="roc_auc")
print(scores.mean(), scores.std())
Como a padronização está dentro do pipeline, o escalonador de cada dobra é ajustado apenas na partição de treinamento daquela dobra.
3) Ajuste de hiperparâmetros com validação cruzada aninhada
from sklearn.model_selection import GridSearchCV, StratifiedKFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
import numpy as np
pipe = Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression(max_iter=2000))
])
param_grid = {
"clf__C": np.logspace(-3, 3, 13),
"clf__penalty": ["l2"],
}
inner_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=1)
outer_cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=2)
search = GridSearchCV(pipe, param_grid=param_grid, cv=inner_cv, scoring="roc_auc")
nested_scores = cross_val_score(search, X, y, cv=outer_cv, scoring="roc_auc")
print("Nested CV ROC-AUC:", nested_scores.mean(), "+/-", nested_scores.std())
Isso reporta uma estimativa mais defensável de desempenho após o ajuste.
4) Séries temporais: avaliação por encadeamento para frente
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import Ridge
pipe = Pipeline([
("scaler", StandardScaler()),
("model", Ridge())
])
tscv = TimeSeriesSplit(n_splits=5) # preserves order
scores = cross_val_score(pipe, X_time_ordered, y_time_ordered, cv=tscv, scoring="neg_mean_absolute_error")
print("MAE:", (-scores).mean())
Importante: séries temporais frequentemente exigem engenharia de atributos (feature engineering) que respeite o tempo, por exemplo, médias móveis (rolling averages) computadas usando apenas valores passados. Dividir corretamente é necessário, mas não é suficiente para evitar vazamento temporal.
5) Divisão orientada por grupos (por exemplo, separação por paciente)
from sklearn.model_selection import GroupKFold, cross_val_score
from sklearn.pipeline import Pipeline
from sklearn.impute import SimpleImputer
from sklearn.ensemble import RandomForestClassifier
pipe = Pipeline([
("imputer", SimpleImputer(strategy="median")),
("clf", RandomForestClassifier(random_state=42))
])
gkf = GroupKFold(n_splits=5)
scores = cross_val_score(pipe, X, y, cv=gkf, groups=patient_id, scoring="roc_auc")
print(scores.mean(), scores.std())
Isso impede que o mesmo paciente apareça tanto em dobras de treino quanto de validação.
Escolhendo uma estratégia: regras práticas
- Classificação/regressão tabular IID: comece com treino/validação/teste estratificado ou validação cruzada k-fold com 5 dobras.
- Conjunto de dados pequeno + muito ajuste: use validação cruzada aninhada (ou mantenha um conjunto de teste rigoroso e ajuste via validação cruzada apenas no treinamento).
- Entidades repetidas (usuários/pacientes/documentos): use divisões por grupos.
- Previsão / predição temporal: use divisões baseadas em tempo (encadeamento para frente), não aleatórias.
- Classificação altamente desbalanceada: use estratificação, métricas apropriadas e considere reporte por classe (Desbalanceamento de Classes).
- Aprendizado profundo com muitos dados: um único conjunto de validação é comum; garanta pré-processamento sem vazamento e considere múltiplas sementes.
Checklist de armadilhas comuns
- Mexeu no conjunto de teste durante o desenvolvimento (mesmo “só uma vez” para conferir): os resultados agora estão enviesados.
- Ajustou escalonadores/imputadores/PCA nos dados completos: vazamento.
- Computou agregações usando todas as linhas (incluindo linhas futuras ou de validação): vazamento.
- Divisão aleatória quando grupos/tempo importam: desempenho inflado.
- Reportou o melhor de muitos experimentos sem contabilizar a busca: viés de seleção; considere validação cruzada aninhada ou protocolo rigoroso.
- Reportou apenas uma métrica: complemente com variabilidade, calibração (se probabilístico) e fatiamento.
Como a validação se encaixa na história mais ampla de avaliação
Validação e validação cruzada respondem: “Quão bem este modelo generaliza sob uma suposição definida de particionamento de dados?” Para construir uma avaliação completa, você tipicamente adiciona:
- Métricas que reflitam custos reais (Métricas)
- Análise de limiar e de coortes (Análise de Erros (Fatiamento))
- Confiabilidade de probabilidades (Calibração)
- Quantificação de incerteza (uncertainty quantification) (Estimativa de Incerteza)
- Planejamento de experimentos com base estatística (Desenho de Experimentos e Poder)
Quando bem feita, a validação não é apenas uma caixa a marcar — é um processo disciplinado que torna seus resultados críveis, comparáveis e com mais chances de sobreviver ao contato com produção.