Machine learning do zero · Aula 4.5 · 24 de 33 aulas prontas · 14 min
Vazamento de dados
Em resumo
Vazamento é informação do teste ou do futuro que chega ao treino do modelo. Com ruído puro, ele finge 82% na validação. Conheça 8 tipos e encontre-os.
Aquecimento Duas perguntas de aulas anteriores. Um minuto, e a aula nunca fica bloqueada.
Na aula 3.2 a validação cruzada dava uma estimativa honesta do modelo. Honesta com uma condição: nada da parte de teste pode tocar o treino antes. Quando toca, falamos de vazamento de dados. O modelo parece ótimo na validação e desmorona no uso real. Kapoor e Narayanan reuniram relatos de outros pesquisadores sobre vazamento em 294 artigos científicos de 17 áreas. Vamos começar pelo exemplo mais limpo: dados em que não há nada para aprender.
Escolha uma resposta e o seu grau de certeza.
Responda para liberar o experimento.
Experimento interativo
Este experimento interativo funciona em um navegador com JavaScript ativado. O texto ao redor descreve o que ele mostra.
Ver a resposta
Resposta certa: Mais de 80%, embora esses dados não tenham nenhum sinal
A validação mostra 81,7%. Se você escolhe as colunas em cada dobra só com os dados de treino, a nota cai para 43,3%, cara ou coroa. Em 20 novos conjuntos de ruído a média é 86,6% com vazamento e 49,8% de forma honesta.
Experimente
- Com os atributos escolhidos antes da divisão, mova k de 5 a 50. Até onde sobe a nota com ruído puro?
- Com o mesmo k, troque para a escolha dentro da validação. Onde cai a barra, e onde caem os pontos dos 20 novos conjuntos de ruído?
- Na segunda parte do lab, encontre o passo que vaza em cada um dos 8 processos.
Depois de conferir no lab, vamos dar nome ao que você vê.
A ideia em poucas palavras
Vazamento de dados é qualquer informação que o modelo não teria no momento de uma previsão real e que mesmo assim chegou ao treino ou à avaliação dele. A validação deve fazer o papel do futuro: dados que o modelo nunca viu. O vazamento estraga esse papel, então a nota sobe e o modelo não fica nada melhor.
No lab, entre 1000 colunas de ruído sempre há algumas que combinam com os rótulos por acaso. A melhor tem correlação de 0,48 com o rótulo, quando o típico para ruído é cerca de 0,13. Escolher com as 60 amostras ajusta as colunas também às amostras que depois fazem o papel de teste. A validação cruzada então confere o modelo em dados que já o moldaram, e mostra 81,7%. Escolher dentro de uma dobra vê só 48 amostras de treino. As colunas que combinam com elas por acaso não dizem nada sobre as 12 amostras de teste, então a nota cai para 43,3%, dentro do cara ou coroa.
Kapoor e Narayanan dividiram o vazamento em 8 tipos, em três grupos:
- sem divisão limpa: sem conjunto de teste separado, pré-processamento ajustado em todos os dados, seleção de atributos com todos os dados, duplicatas dos dois lados da divisão;
- um atributo que você não conhece na hora de prever, por exemplo os dias no hospital quando o modelo deve prever a internação;
- um teste de outra distribuição que a do uso: uma divisão ao acaso de dados temporais, as mesmas pessoas no treino e no teste, um teste escolhido de outro jeito que os dados em que o modelo vai trabalhar.
Você vai encontrar cada um deles na segunda parte do lab.
Jak to policzono
- Ruído: 60 amostras, 1000 colunas de uma distribuição normal, 30 rótulos 0 e 30 rótulos 1 em ordem aleatória. O navegador o gera com a semente 1; os pontos são mais 20 conjuntos com outras sementes.
- Modelo: as k colunas com maior correlação com o rótulo (em valor absoluto), um classificador do centroide mais próximo, validação cruzada estratificada de 5 dobras (6 amostras de cada classe em cada dobra).
- Biópsias em “Erro comum”: Breast Cancer Wisconsin (Diagnostic), UCI, licença CC BY 4.0, 569 biópsias. Tipos de vazamento: S. Kapoor, A. Narayanan, “Leakage and the reproducibility crisis in machine-learning-based science”, Patterns, 2023.
Por que funciona
Uma avaliação só é honesta quando os dados de teste não influenciaram nenhuma decisão sobre o modelo. Selecionar atributos também é uma decisão, assim como preencher valores faltantes, escalonar, escolher um limiar ou ficar com o melhor de vários modelos. Se alguma delas viu os rótulos do teste, o teste deixa de ser independente. Quanto maior a escolha (10 colunas de 1000), mais ajuste ao acaso passa para a nota.
Um único conjunto de ruído poderia sair assim por acaso. Mas em nenhum dos 20 novos conjuntos do lab a nota com vazamento em k = 10 fica abaixo de 81,7%. No código abaixo o mesmo experimento roda em 50 conjuntos de ruído no scikit-learn: escolher antes da divisão dá em média 86,4% (no mínimo 73,3%), e escolher em cada dobra 50,0%.
Erro comum
Verdadeiro ou falso: “vazamento é só o rótulo escondido nos atributos”. Falso. No lab nenhuma coluna contém o rótulo, e mesmo assim a validação mostra 81,7% com ruído puro. O que vaza é uma decisão tomada com todos os dados. Nas biópsias, sobreamostrar as malignas antes da validação cruzada dá sensibilidade de 97,1%, e sobreamostrar só nos dados de treino dá 92,1%, em 50 de 50 divisões. As cópias caem dos dois lados da divisão, então o modelo reconhece biópsias que já viu. Duplicatas nos dados funcionam do mesmo jeito, e uma divisão ao acaso de dados temporais põe o futuro no treino.
Nem todo vazamento é igualmente perigoso. Um escalonador ajustado em todas as biópsias muda a acurácia de um modelo de vizinho mais próximo em 0,08 ponto em média: ele não vê os rótulos, então transmite muito pouco. A seleção de atributos e a sobreamostragem usam os rótulos, então transmitem muito. A regra é uma só: ajuste cada passo que aprende algo dos dados só na parte de treino.
Quiz
Três perguntas para você se testar. Escolha uma resposta ou abra a resposta embaixo de cada pergunta. No teclado: as setas escolhem, Enter ou Espaço confirma.
Pontos-chave
- Vazamento é informação que o modelo não teria na hora de prever e que mesmo assim tocou o treino ou a avaliação.
- Ajuste cada passo que aprende dos dados (seleção, imputação, escalonamento, sobreamostragem) só na parte de treino, de preferência dentro de um pipeline.
- Divida os dados como o modelo será usado: por tempo, por pessoa, sem duplicatas.
- Quando uma nota parece boa demais, procure primeiro um vazamento.
Mais a fundo: fórmula, código, prática
A matemática
Chutar com n amostras dá uma acurácia de média 0,5 e desvio padrão √(0,5 · 0,5 / n). Com n = 60 isso dá 0,065, então cara ou coroa é 50% ± 13 pontos (dois desvios).
Com ruído puro, a correlação de uma coluna com o rótulo tem média 0 e desvio de cerca de 1 / √n, cerca de 0,13 com n = 60. Entre 1000 colunas a melhor fica a vários desvios do zero: 0,48 no lab.
A validação cruzada honesta calcula  = média sobre k de A(f_k, T_k), em que o modelo f_k (incluindo a seleção de atributos) é construído só com os dados fora da dobra T_k. Se os atributos são escolhidos com todos os dados, a seleção depende dos rótulos de T_k, então f_k não é independente de T_k, e  deixa de estimar a acurácia em dados novos.
No código (Python)
O mesmo experimento no scikit-learn: escolher 10 colunas em 50 conjuntos de ruído e, nas biópsias, escalonar e sobreamostrar as malignas antes da divisão e dentro de cada dobra, em 50 divisões.
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.model_selection import StratifiedKFold, cross_val_score, cross_val_predict
from sklearn.neighbors import NearestCentroid, KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import recall_score
def noise(seed): # 60 amostras x 1000 colunas de ruído puro, rótulos 30/30 em ordem aleatória
rng = np.random.default_rng(seed)
X, y = rng.normal(size=(60, 1000)), rng.permutation(np.repeat([0, 1], 30))
cv = StratifiedKFold(5, shuffle=True, random_state=seed)
leak = cross_val_score(NearestCentroid(), SelectKBest(f_classif, k=10).fit_transform(X, y), y, cv=cv).mean() # 10 colunas escolhidas com as 60
honest = cross_val_score(make_pipeline(SelectKBest(f_classif, k=10), NearestCentroid()), X, y, cv=cv).mean() # escolhidas dentro de cada dobra
return leak, honest
r = np.array([noise(s) for s in range(50)])
print(f"pure noise, 50 datasets: picked before the split {r[:, 0].mean():.1%} (lowest {r[:, 0].min():.1%}), inside CV {r[:, 1].mean():.1%}")
X, y = load_breast_cancer(return_X_y=True)
y = 1 - y # 1 = maligno
knn = lambda: KNeighborsClassifier(1)
scale = []
for s in range(50):
cv = StratifiedKFold(5, shuffle=True, random_state=s)
before = cross_val_score(knn(), StandardScaler().fit_transform(X), y, cv=cv).mean() # escalonador ajustado em todas as biópsias
inside = cross_val_score(make_pipeline(StandardScaler(), knn()), X, y, cv=cv).mean() # ajustado em cada parte de treino
scale.append(abs(before - inside))
print(f"breast cancer, 1-NN, 50 splits: scaling before the split changes accuracy by {np.mean(scale) * 100:.2f} points on average")
def oversample(rows, rng): # copia malignas ao acaso até as duas classes ficarem iguais
mal, ben = rows[y[rows] == 1], rows[y[rows] == 0]
return np.r_[rows, rng.choice(mal, len(ben) - len(mal))]
rec = []
for s in range(50):
rng, cv = np.random.default_rng(s), StratifiedKFold(5, shuffle=True, random_state=s)
rows = oversample(np.arange(len(y)), rng) # cópias feitas ANTES da divisão também caem nas dobras de teste
before = recall_score(y[rows], cross_val_predict(make_pipeline(StandardScaler(), knn()), X[rows], y[rows], cv=cv))
pred = np.empty(len(y))
for tr, te in cv.split(X, y): # cópias só dentro de cada parte de treino
t = oversample(tr, rng)
pred[te] = make_pipeline(StandardScaler(), knn()).fit(X[t], y[t]).predict(X[te])
rec.append((before, recall_score(y, pred)))
rec = np.array(rec)
print(f"oversampling malignant before the split: recall {rec[:, 0].mean():.1%}, inside each training part: {rec[:, 1].mean():.1%}; higher before in {(rec[:, 0] > rec[:, 1]).sum()} of 50")
Saída
pure noise, 50 datasets: picked before the split 86.4% (lowest 73.3%), inside CV 50.0%
breast cancer, 1-NN, 50 splits: scaling before the split changes accuracy by 0.08 points on average
oversampling malignant before the split: recall 97.1%, inside each training part: 92.1%; higher before in 50 of 50
O lab mostra um conjunto de ruído e mais 20, o código outros 50 conjuntos, então os números diferem um pouco: no lab o vazamento dá 81,7% no primeiro conjunto e 86,6% em média nos 20 seguintes, no código 86,4% em média. A conclusão é a mesma em cada conjunto.
Baixar o notebook (.ipynb). Abra no Jupyter ou envie para o Google Colab; ele baixa sozinho os dados que faltarem.
Para quem pratica
- No scikit-learn ponha cada passo que aprende dos dados num
Pipelinejunto com o modelo, e só então avalie o todo comcross_val_score: assim ofitde cada passo vê só a parte de treino. - Divida dados temporais com
TimeSeriesSplite dados com grupos (pacientes, clientes, fotos de uma mesma câmera) comGroupKFold. - Use a sobreamostragem e o SMOTE dentro de um
Pipelinedo imbalanced-learn, só nas partes de treino (aula 2.5). - No Kaggle o vazamento às vezes está no próprio arquivo: identificadores, ordem das linhas, carimbos de tempo. Uma nota muito melhor que a de todos os outros é um sinal para procurar vazamento antes de comemorar.
Isto foi útil?
Obrigado! Isso nos ajuda a melhorar.
Vá mais fundo no Atlas
Verbetes da enciclopédia sobre as ideias desta aula: