Ir para o conteúdo

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