Train/Val/Test Split
Divisão Treino / Validação / Teste
Dividir os dados corretamente não é uma tecnicidade — é o design experimental do aprendizado de máquina. Uma divisão errada produz um experimento quebrado: resultados que parecem bons durante o desenvolvimento, mas falham em produção.
A Divisão em Três Conjuntos
Cada conjunto tem um papel distinto e não sobreponível:
| Conjunto | Usado para | Quantas vezes? |
|---|---|---|
| Treino | Ajustar pesos do modelo | Muitas iterações |
| Validação | Ajustar hiperparâmetros, early stopping, seleção de modelo | Muitas vezes |
| Teste | Reportar desempenho final | Exatamente uma vez |
A Regra de Ouro
Se você olhar o desempenho no conjunto de teste e depois mudar qualquer coisa no seu modelo, o conjunto de teste não é mais uma medida válida de generalização. Você passou a ajustar implicitamente com base nele.
Validação Cruzada
Quando os dados são escassos, uma única divisão treino/val desperdiça dados. Validação Cruzada K-Fold usa todos os dados para treinamento e validação:
K=5 folds:
Fold 1: [VAL][TRN][TRN][TRN][TRN]
Fold 2: [TRN][VAL][TRN][TRN][TRN]
Fold 3: [TRN][TRN][VAL][TRN][TRN]
Fold 4: [TRN][TRN][TRN][VAL][TRN]
Fold 5: [TRN][TRN][TRN][TRN][VAL]
Métrica final = média ± desvio padrão entre os folds. Muito mais confiável do que uma única divisão.
from sklearn.model_selection import cross_val_score, KFold
kf = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=kf, scoring='f1_macro')
print(f"F1: {scores.mean():.3f} ± {scores.std():.3f}")
Nota: Mesmo com validação cruzada, mantenha um conjunto de teste separado que nunca é usado durante a validação cruzada.
Divisão Estratificada
Para classificação, uma divisão aleatória pode produzir distribuições de classe muito diferentes entre os folds — especialmente com desbalanceamento de classes. A divisão estratificada preserva a proporção de classes em cada fold.
from sklearn.model_selection import StratifiedKFold, train_test_split
# Divisão estratificada treino/teste
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, stratify=y, random_state=42
)
# K-fold estratificado
skf = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
Sempre use stratify=y ao trabalhar com tarefas de classificação.
Divisão Temporal para Séries Temporais
Divisões aleatórias são incorretas para séries temporais — permitem que informações futuras vazem para o treinamento. Sempre divida por tempo:
# Para um DataFrame com índice de tempo
split_date = '2024-01-01'
train = df[df.index < split_date]
test = df[df.index >= split_date]
Para validação cruzada de séries temporais, use TimeSeriesSplit:
from sklearn.model_selection import TimeSeriesSplit
tscv = TimeSeriesSplit(n_splits=5)
# Cada fold: treina no passado, valida no futuro imediato
Quanto de Dados para Cada Conjunto?
| Tamanho do dataset | Divisão recomendada | Justificativa |
|---|---|---|
| Pequeno (< 10k) | 60/20/20 ou use VC | Mais dados de validação/teste para estimativas confiáveis |
| Médio (10k–1M) | 70/15/15 ou 80/10/10 | Mais dados de treino melhora o modelo |
| Grande (> 1M) | 98/1/1 | 1% de 1M = 10k amostras, suficiente para avaliação |
| Muito grande (> 100M) | 99/0,5/0,5 | Mesmo 0,5% resulta em 500k amostras de avaliação |