Divisão Treino-Teste
O que é uma Divisão Treino–Teste?
Uma divisão treino–teste (train–test split) é a prática de particionar um conjunto de dados em (no mínimo) dois subconjuntos disjuntos:
- Conjunto de treinamento (training set): usado para ajustar os parâmetros do modelo (por exemplo, pesos em um modelo linear ou rede neural).
- Conjunto de teste (test set): usado apenas para estimar quão bem o modelo treinado irá performar em dados novos e não vistos (seu desempenho de generalização (generalization performance)).
Essa é uma das ideias mais fundamentais na avaliação em aprendizado de máquina (machine learning): você quer uma estimativa honesta de desempenho sob as mesmas condições que espera no momento da implantação. O conjunto de teste atua como um proxy para dados futuros.
Em muitos fluxos de trabalho, você também introduz uma terceira partição:
- Conjunto de validação (validation set): usado durante o desenvolvimento para escolher hiperparâmetros (hyperparameters), atributos (features), limiares (thresholds) e variantes de modelo, mantendo o conjunto de teste intocado. Veja Validação e Validação Cruzada.
Por que a divisão importa: generalização e avaliação não viesada
Modelos de aprendizado de máquina normalmente são otimizados para minimizar o erro nos dados de treinamento. Se você avalia nos mesmos dados em que treinou, você mede o erro de treinamento (training error), que pode ser dramaticamente otimista devido a:
- Sobreajuste (overfitting): o modelo aprende ruído ou idiossincrasias do conjunto de treinamento.
- Viés de seleção (selection bias): você implicitamente ajusta decisões com base no desempenho no treinamento.
A quantidade que de fato importa é a perda esperada em novos dados (expected loss) do mesmo processo gerador de dados (data-generating process):
[ \mathcal{E}{\text{gen}} = \mathbb{E}{(x,y)\sim \mathcal{D}}[L(f(x), y)] ]
Um conjunto de teste separado (held-out) fornece uma estimativa empírica dessa expectativa. Não é perfeito — conjuntos de teste finitos têm variância — mas é muito melhor do que avaliar nos dados de treinamento.
A suposição i.i.d. (e quando ela falha)
Uma divisão treino–teste básica assume que os exemplos são independentes e identicamente distribuídos (independent and identically distributed, i.i.d.): as amostras de treino e teste são extraídas da mesma distribuição, e as amostras não “vazam” informação umas sobre as outras.
Quando a suposição i.i.d. é violada (séries temporais, múltiplas amostras por usuário, correlação espacial, itens duplicados), uma divisão aleatória pode produzir um desempenho no teste enganosamente alto. Lidar corretamente com esses casos é parte central de fazer uma divisão treino–teste “de verdade”.
Proporções comuns de divisão e regras práticas
Não existe uma proporção universalmente melhor; ela depende do tamanho do conjunto de dados, da complexidade do modelo e de quanto ajuste você fará.
Padrões comuns:
- 80/20 (treino/teste): linha de base simples quando você não fará muito ajuste de hiperparâmetros.
- 70/15/15 ou 80/10/10 (treino/validação/teste): comum quando você precisa de um conjunto de validação.
- 90/10 ou 95/5: às vezes usado quando dados são escassos e você dependerá de validação cruzada para seleção de modelo.
Orientação prática:
- Se o conjunto de dados é pequeno, uma única divisão pode ter alta variância. Considere Validação e Validação Cruzada (por exemplo, validação cruzada k-fold (k-fold CV)) para uma seleção de modelo mais estável, e mantenha um conjunto de teste final intocado, se possível.
- Se o conjunto de dados é grande, mesmo uma pequena fração de teste pode produzir uma estimativa bem precisa (porque o próprio conjunto de teste é grande).
Fluxo de trabalho básico: uma avaliação limpa com conjunto retido
Um padrão robusto se parece com isto:
- Divida os dados em treino e teste.
- Ajuste etapas de pré-processamento (preprocessing) apenas no treino (imputação, escalonamento, codificação, seleção de atributos).
- Treine o modelo nos dados de treinamento processados.
- Avalie uma única vez nos dados de teste processados.
- Faça análise de erros nas predições do teste (sem realimentar os rótulos do teste nas decisões de treinamento). Veja Análise de Erros (Fatiamento).
Exemplo: divisão simples para classificação (scikit-learn)
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import roc_auc_score
X = np.random.randn(1000, 20)
y = (X[:, 0] + 0.5 * X[:, 1] + np.random.randn(1000) * 0.5 > 0).astype(int)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
model = Pipeline([
("scaler", StandardScaler()),
("clf", LogisticRegression(max_iter=1000))
])
model.fit(X_train, y_train)
proba = model.predict_proba(X_test)[:, 1]
print("Test ROC AUC:", roc_auc_score(y_test, proba))
Pontos-chave:
stratify=ypreserva as proporções de classe (importante sob Desbalanceamento de Classes).- O pré-processamento está dentro de um pipeline (pipeline), o que ajuda a evitar vazamento.
Variantes de divisões treino–teste (e quando usá-las)
Divisões estratificadas (classificação)
Uma divisão estratificada (stratified split) garante que cada subconjunto tenha aproximadamente a mesma distribuição de classes que o conjunto completo. Isso importa quando:
- O conjunto de dados é desbalanceado (por exemplo, 1% de positivos).
- Algumas classes são raras e podem desaparecer do conjunto de teste sob uma divisão aleatória ingênua.
Use estratificação para a maioria das tarefas de classificação, a menos que você tenha um motivo para não usar (por exemplo, divisão baseada em tempo, em que a estratificação é incompatível).
No scikit-learn, train_test_split(..., stratify=y) é a abordagem mais simples.
Ressalva prática: se uma classe tem pouquíssimos exemplos, você pode não conseguir estratificar para um determinado tamanho de teste (por exemplo, você não consegue colocar pelo menos um exemplo de cada classe no conjunto de teste). Nesses casos, você pode precisar de:
- um conjunto de dados maior,
- uma fração de teste maior,
- lógica de agrupamento/tempo que mantenha classes raras juntas,
- ou um desenho de avaliação diferente.
Divisões por grupos (amostras não independentes)
Uma divisão por grupos (grouped split) garante que todos os exemplos do mesmo grupo (usuário, paciente, dispositivo, documento, sessão, produto) vão inteiramente para treino ou inteiramente para teste.
Use divisões por grupos quando múltiplas linhas compartilham informação, como:
- Múltiplas medições médicas do mesmo paciente
- Muitos cliques do mesmo usuário
- Múltiplas imagens do mesmo objeto
- Múltiplos trechos do mesmo documento
Se você dividir linhas aleatoriamente, o modelo pode indiretamente “reconhecer” a entidade no teste porque viu essa entidade no treino. Isso normalmente infla o desempenho.
Exemplo com GroupShuffleSplit:
import numpy as np
from sklearn.model_selection import GroupShuffleSplit
X = np.random.randn(1000, 10)
y = np.random.randint(0, 2, size=1000)
groups = np.random.randint(0, 100, size=1000) # 100 users, multiple samples each
gss = GroupShuffleSplit(n_splits=1, test_size=0.2, random_state=42)
train_idx, test_idx = next(gss.split(X, y, groups=groups))
X_train, X_test = X[train_idx], X[test_idx]
y_train, y_test = y[train_idx], y[test_idx]
# Verify no group overlap
assert set(groups[train_idx]).isdisjoint(set(groups[test_idx]))
Divisões baseadas em tempo (previsão e predição temporal)
Uma divisão baseada em tempo (time-based split) treina em períodos anteriores e testa em períodos posteriores. Isso é essencial quando:
- Você está prevendo resultados futuros a partir de dados passados.
- A distribuição dos dados muda ao longo do tempo (deriva de conceito (concept drift)).
- Usar dados futuros no treinamento seria irrealista e causa vazamento.
Abordagem comum:
- Ordenar por timestamp
- Treinar nos primeiros 80% do tempo
- Testar nos últimos 20%
import pandas as pd
df = df.sort_values("timestamp")
cut = int(len(df) * 0.8)
train_df = df.iloc[:cut]
test_df = df.iloc[cut:]
Para uma avaliação mais robusta, especialmente com dados limitados, use janelas deslizantes/expansivas (veja Validação Cruzada em Séries Temporais).
Ressalvas importantes para séries temporais:
- Vazamento por antecipação (lookahead leakage): atributos computados usando informação futura (por exemplo, “média móvel” que acidentalmente inclui pontos futuros).
- Vazamento por sobreposição temporal (temporal overlap leakage): rótulos que agregam em janelas podem se sobrepor entre treino e teste.
- Embargo/lacuna (embargo/gap): às vezes você precisa de uma lacuna entre treino e teste para evitar vazamento por efeitos próximos à fronteira (comum em finanças).
O que pode dar errado (e como prevenir)
1) Vazamento de dados
Vazamento (leakage) ocorre quando informação do conjunto de teste influencia o treinamento — direta ou indiretamente. Isso produz resultados excessivamente otimistas e modelos que falham em produção.
Fontes comuns de vazamento:
- Pré-processamento ajustado nos dados completos: escalonamento, imputação, codificação aprendida usando todas as linhas.
- Vazamento do alvo (target leakage): atributos que codificam o rótulo (ou algo muito próximo a ele), por exemplo “delinquent_flag” para prever inadimplência.
- Seleção de atributos feita antes da divisão: selecionar atributos pela correlação com o alvo calculada no conjunto completo.
- Espiar repetidamente o conjunto de teste (repeated test-set peeking): iterar no modelo usando o desempenho no teste para guiar decisões.
Boas práticas:
- Divida cedo.
- Coloque o pré-processamento em um pipeline ajustado apenas nos dados de treinamento.
- Use um conjunto de validação (ou validação cruzada) para seleção de modelo; avalie uma vez no conjunto de teste ao final.
- Mantenha um conjunto de teste “final” intocado por experimentação (às vezes chamado de conjunto de teste dourado).
Exemplo de vazamento: escalonamento nos dados completos (errado) vs apenas no treino (certo)
Errado:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X) # ❌ uses test distribution too
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.2)
Certo:
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)
pipe = Pipeline([
("scaler", StandardScaler()), # ✅ fit only on X_train when calling fit
("clf", LogisticRegression())
])
pipe.fit(X_train, y_train)
2) Divisão não i.i.d. (correlação oculta)
Mesmo sem grupos explícitos, correlação pode aparecer via:
- Amostras quase duplicadas (por exemplo, imagens aumentadas/augmentadas)
- A mesma fonte aparecendo várias vezes (por exemplo, conteúdo republicado)
- Autocorrelação espacial (locais próximos são similares)
- Contaminação de teste A/B (usuários expostos a múltiplas variantes)
Sintomas:
- O desempenho no teste é bom demais para ser verdade
- O desempenho em produção cai abruptamente
- A análise de erros revela que falhas se agrupam por entidade/fonte
Correção:
- Identifique a estrutura de dependência e divida pela unidade correta (grupo/usuário/item/tempo/localização).
3) Ordem inadequada de pré-processamento e “treinar nas estatísticas do teste”
Além do escalonamento, outras etapas devem ser ajustadas apenas nos dados de treinamento:
- Valores de imputação (média/mediana)
- Vocabulários de codificação categórica (especialmente target encoding)
- Redução de dimensionalidade (PCA)
- Limiares de outliers
- Vetorizadores de texto (construção de vocabulário)
- Qualquer embedding ou normalização aprendida
A regra geral: qualquer operação que aprende parâmetros a partir dos dados deve ser aprendida no conjunto de treinamento.
4) Ajuste de hiperparâmetros no conjunto de teste
Se você testa 50 modelos e escolhe o que tem o melhor score no teste, o conjunto de teste deixa de ser uma estimativa não viesada — ele vira um conjunto de validação. Este é um modo de falha sutil, porém muito comum.
Padrão correto:
- Use validação (ou validação cruzada) para escolher hiperparâmetros.
- Use o conjunto de teste uma vez, ao final, para estimar generalização.
Para comparações cuidadosas e ganhos pequenos, também considere rigor estatístico e análise de poder; veja Desenho de Experimentos e Poder.
5) Incompatibilidade de métrica e ajuste de limiar
A avaliação depende da métrica. Para classificação, você pode calcular:
- ROC AUC, PR AUC
- acurácia, F1
- taxas da matriz de confusão
A seleção de limiar (por exemplo, converter probabilidades em rótulos de classe) é, em si, uma etapa de ajuste e deve ser feita usando dados de validação, não os dados de teste. Tópicos relacionados: Métricas, Matriz de Confusão e, para qualidade de probabilidades, Calibração.
Como a divisão treino–teste se relaciona com validação e validação cruzada
Divisão em duas partes (treino/teste)
Use quando:
- Você tem muitos dados
- Você não fará muito ajuste
- Você precisa principalmente de uma estimativa rápida
Risco: você ainda pode “acidentalmente ajustar” usando o score do teste ao iterar em experimentos.
Divisão em três partes (treino/validação/teste)
Fluxo de trabalho moderno típico:
- Treino: ajustar parâmetros do modelo
- Validação: escolher hiperparâmetros, época de early stopping, atributos, limiar de decisão
- Teste: avaliação final não viesada
Esta é a abordagem robusta mais simples para muitos projetos aplicados de ML.
Validação cruzada (CV)
Com dados limitados, muitas vezes você quer reutilizar os dados de forma eficiente para seleção de modelo:
- A validação cruzada k-fold divide o conjunto de dados em k dobras; cada dobra é usada uma vez como validação enquanto se treina nas outras k−1 dobras.
- Você tira a média das métricas entre as dobras para reduzir a variância.
Após selecionar hiperparâmetros usando validação cruzada, é comum:
- Retreinar no conjunto completo de treinamento (possivelmente treino+validação)
- Avaliar uma vez no conjunto de teste separado (held-out)
Essa conexão é coberta com mais detalhes em Validação e Validação Cruzada.
Validação cruzada aninhada (nested cross-validation) (quando você quer uma estimativa não viesada *e* ajuste)
Se você ajusta hiperparâmetros e também quer uma estimativa não viesada sem um conjunto de teste separado, a validação cruzada aninhada (nested CV) usa:
- um laço interno de validação cruzada para ajuste
- um laço externo de validação cruzada para avaliação
Isso é especialmente útil em contextos de pesquisa e conjuntos de dados pequenos, mas pode ser computacionalmente caro.
Considerações práticas e boas práticas
Reprodutibilidade: sementes aleatórias e divisões determinísticas
- Fixe
random_state/semente para que os resultados sejam comparáveis entre execuções. - Salve os índices exatos das divisões treino/teste para auditabilidade.
- Em sistemas de ML em produção, garanta que a lógica de divisão seja versionada e repetível.
Tamanho e representatividade do conjunto de teste
Um conjunto de teste deve refletir a população de implantação:
- Mesma disponibilidade de atributos (sem atributos “apenas do futuro”)
- Distribuição similar de classes e subgrupos (a menos que você esteja intencionalmente fazendo um teste de estresse)
- Mesma unidade de predição (por usuário vs por evento)
Se você espera mudança de distribuição, considere múltiplos conjuntos de teste:
- Um conjunto de teste in-time (padrão)
- Um conjunto de teste out-of-time ou fora do domínio (teste de estresse)
Como lidar com desbalanceamento de classes
Se você usa divisão estratificada, você preserva as taxas base, o que ajuda a interpretar métricas corretamente. Mas também:
- Escolha métricas apropriadas (PR AUC, acurácia balanceada etc.). Veja Desbalanceamento de Classes e Métricas.
- Considere intervalos de confiança para eventos raros; um número pequeno de positivos no conjunto de teste pode tornar métricas instáveis.
Quando não usar uma divisão treino–teste simples
Um único conjunto retido aleatório pode ser enganoso quando:
- Existem dependências temporais (use divisão baseada em tempo)
- Existem múltiplas linhas por entidade (use divisão por grupos)
- Existe autocorrelação espacial (use bloqueio/agrupamento espacial)
- Você está fazendo seleção de modelo extensa (precisa de validação/CV e um conjunto de teste intocado)
Resumo
Uma divisão treino–teste é o método base para estimar como um modelo irá performar em dados não vistos. Quando feita corretamente, ela oferece uma verificação honesta contra sobreajuste e ajuda você a escolher modelos que generalizam. Quando feita incorretamente — por vazamento, divisões não i.i.d. ou pré-processamento inadequado — ela pode produzir avaliações que parecem ótimas no papel e falham em produção.
Principais conclusões:
- Divida os dados antes de qualquer pré-processamento aprendido; use pipelines para evitar vazamento.
- Faça a estratégia de divisão corresponder ao processo gerador de dados:
- Estratificada para preservar a distribuição de classes
- Por grupos para amostras correlacionadas (usuários/pacientes/itens)
- Baseada em tempo para predição temporal e deriva
- Use validação e/ou validação cruzada para seleção de modelo e mantenha o conjunto de teste para a avaliação final. Veja Validação e Validação Cruzada e Validação Cruzada em Séries Temporais.