Inteligência artificial, sem ruído.
Tutoriais7 min

Vazamento de dados: como duas linhas de código inflaram um modelo em 12 pontos de R²

Um erro sutil de pré-processamento fez o modelo espiar o conjunto de teste — e o R² caiu de 0,887 para 0,767. Entenda como evitar.

Vazamento de dados: como duas linhas de código inflaram um modelo em 12 pontos de R²

O modelo parecia ótimo — até eu descobrir que ele estava trapaceando

Imagine um aluno que, na véspera da prova, recebe uma olhada disfarçada no gabarito. Ele ainda responde às questões, ainda parece competente, mas a nota que tira não mede mais nada. É exatamente isso que acontece com um modelo de machine learning quando ocorre vazamento de dados (data leakage): o modelo acessa informações que não deveria ver antes da avaliação, e a pontuação fica artificialmente inflada.

O cientista de dados Abdullahi Dattijo documentou um caso real e impressionante desse fenômeno. Ao treinar uma rede neural para prever preços de carros usados, ele obteve um R² de 0,887 — um resultado que parecia excelente para um conjunto com menos de 200 veículos. O problema? O número era mentiroso. Ao corrigir a ordem de duas linhas de código, a pontuação honesta despencou para 0,767. O modelo não piorou: a primeira medição é que estava, silenciosamente, lendo parte do gabarito.

O que é vazamento de dados (e por que ele engana tanto)

Vazamento de dados ocorre quando informação do conjunto de teste — ou do alvo que se quer prever — “vaza” para dentro do treinamento ou do pré-processamento. O clássico estudo de Kaufman, Rosset, Perlich e Stitelman (2012), no periódico ACM Transactions on Knowledge Discovery from Data, formalizou o problema e deu um exemplo célebre: no INFORMS 2010 Data Mining Challenge, competidores deveriam prever preços de ações usando apenas o conjunto de treino. Vários deles descobriram quais ações reais estavam escondidas no teste cruzando padrões com dados públicos de finanças — e inflaram suas pontuações.

A lição central é que o vazamento não costuma se anunciar. Ao contrário do erro óbvio (comparar rótulos de validação com eles mesmos e obter score perfeito), o vazamento sutil apenas “empurra” números — a média do escalonador, os limites de outliers, a lista de categorias — e o resultado final parece apenas um bom resultado comum, não um defeito.

O experimento: prevendo preço de carros com um MLP

O projeto usa o dataset UCI Automobile (doado por Jeffrey Schlimmer em 1987, com licença Creative Commons Attribution 4.0). São 193 carros restantes após limpeza, com 24 colunas para prever o preço: 16 numéricas (tamanho do motor, potência, peso) e 8 categóricas (tipo de combustível, tração, posição do motor).

O modelo é um multilayer perceptron (MLP) do scikit-learn, com duas camadas ocultas de 64 unidades cada:

MLPRegressor(
    hidden_layer_sizes=(64, 64),
    max_iter=1000,
    random_state=42,
)

Os 193 registros foram divididos em 60% treino (115 carros), 20% validação (39) e 20% teste (39).

O erro: duas linhas na ordem errada

O código que gerou o score “forte” aplicava o pré-processamento antes da divisão treino/teste:

# 1) Capa de outliers via IQR (calculado sobre TODAS as linhas)
for col in numerical_cols + ["price"]:
    Q1 = df[col].quantile(0.25)
    Q3 = df[col].quantile(0.75)
    IQR = Q3 - Q1
    df[col] = df[col].clip(lower=Q1 - 1.5 * IQR, upper=Q3 + 1.5 * IQR)

# 2) Pipeline de pré-processamento (aprende sobre TODAS as linhas)
preprocessor = ColumnTransformer(transformers=[
    ("num", StandardScaler(), numerical_cols),
    ("cat", OneHotEncoder(handle_unknown="ignore"), categorical_cols),
])
X_processed = preprocessor.fit_transform(X)

# 3) Só agora divide treino/teste
X_train_val, X_test, y_train_val, y_test = train_test_split(
    X_processed, y, test_size=0.2, random_state=42
)

Nenhuma dessas etapas parece errada isoladamente. O problema é a ordem: o corte de outliers, o StandardScaler e o OneHotEncoder calcularam seus parâmetros (limites, média, lista de categorias) olhando para todas as linhas — incluindo as 39 que virariam o conjunto de teste duas etapas depois. As linhas de teste não foram copiadas para o treino; elas apenas “empurraram” silenciosamente os números usados na transformação.

A correção: dividir primeiro, ajustar só no treino

A regra de ouro é simples: divida os dados primeiro e ajuste cada etapa de pré-processamento apenas nas linhas de treino. Validação e teste só podem ser transformados com parâmetros já aprendidos, nunca usados para calculá-los:

# 1) Divide ANTES de qualquer pré-processamento
X_train_val, X_test, y_train_val, y_test = train_test_split(
    X, y, test_size=0.2, random_state=42
)
X_train, X_val, y_train, y_val = train_test_split(
    X_train_val, y_train_val, test_size=0.25, random_state=42
)

# 2) Limites de IQR calculados SOMENTE sobre o treino
bounds = {}
for col in numerical_cols:
    Q1 = X_train[col].quantile(0.25)
    Q3 = X_train[col].quantile(0.75)
    IQR = Q3 - Q1
    bounds[col] = (Q1 - 1.5 * IQR, Q3 + 1.5 * IQR)

# 3) fit_transform só no treino; transform em validação e teste
X_train_p = preprocessor.fit_transform(X_train)
X_val_p = preprocessor.transform(X_val)
X_test_p = preprocessor.transform(X_test)

mlp = MLPRegressor(hidden_layer_sizes=(64, 64), max_iter=1000, random_state=42)
mlp.fit(X_train_p, y_train)

Repare no padrão: fit_transform roda apenas em X_train. Validação e teste passam somente por transform, que aplica os números já aprendidos sem aprender nada novo.

Quanto custou o vazamento

A diferença não é desprezível. Os números do artigo:

  • R² com vazamento: 0,887
  • R² corrigido: 0,767
  • Erro (MSE): de 6,9 milhões para 26,2 milhões
  • Erro típico (RMSE): de cerca de US$ 2.630 para US$ 5.120 por carro

Doze pontos de R² separam “um resultado para destacar no relatório” de “um resultado sólido, mas comum”. A direção do erro — score inflado — vale para qualquer tamanho de conjunto de dados; o tamanho exato da inflação, não. Em conjuntos maiores, a lacuna tende a ser menor, porque remover algumas linhas de teste muda menos a média e os limites calculados no treino.

Prós e contras de detectar vazamento cedo

✅ O que você ganha:

  • Métricas honestas que sobrevivem à auditoria e ao deploy em produção.
  • Detecção de bugs de pipeline antes que virem decisão de negócio.
  • Comparação justa entre modelos (sem “campeões” inflados).
  • Um hábito barato de incorporar: dividir antes de transformar.

⚠️ O que você não ganha:

  • A correção não melhora o modelo — apenas revela o desempenho real.
  • Em conjuntos pequenos (193 carros), as métricas têm variância alta.
  • Não existe detector automático infalível: é preciso revisar a ordem do pipeline.

Troubleshooting: como identificar vazamento na prática

  1. ❌ Score de teste “bom demais para ser verdade”: se o R² no teste é quase igual ao do treino em dados pequenos, desconfie. → Revise se algum passo usou fit_transform sobre o dataset inteiro.
  2. ❌ Gap pequeno entre treino e teste em modelo simples: um MLP com 115 linhas de treino deveria generalizar pior que o treino. → Verifique se o train_test_split veio antes do StandardScaler/OneHotEncoder.
  3. ❌ Pipeline do scikit-learn com fit_transform no lugar errado: → dentro de um Pipeline, o fit já é isolado por split no cross_val_score; mas em código manual, a ordem importa.
  4. ❌ Validação criada e nunca usada: → rode predict também na validação para verificar o gap de generalização antes do teste final.
  5. ❌ Score perfeito ou quase perfeito: → procure vazamento direto (rótulo comparado consigo mesmo, alvo escalado com dados do teste).

FAQ

O que é R²? Mede o quanto do modelo explica da variância do alvo, de 0 a 1. Quanto mais próximo de 1, melhor.

Por que o vazamento infla o score? Porque o pré-processamento “aprende” estatísticas que incluem o teste, dando ao modelo uma vantagem que ele não terá em produção.

A ordem sempre importa? Sim. Divida os dados antes de qualquer transformação que aprenda parâmetros (escalonamento, encoding, corte de outliers, imputação).

Isso vale para qualquer modelo? Vale para redes neurais, regressão, árvores — qualquer pipeline que transforme dados antes de avaliar.

Como confirmar que corrigi? Reexecute o pipeline corrigido e compare: o score deve cair (ficar honesto) e o gap treino/teste deve ficar coerente.



Descubra mais sobre noticiAI

Assine para receber nossas notícias mais recentes por e-mail.

R
Sobre o autorRedação Noticiai

Equipe editorial dedicada a explicar inteligência artificial com clareza, independência e contexto.