ML Atlas

Machine learning do zero · Aula 3.2 · 15 de 33 aulas prontas · 10 min

Validação e validação cruzada

Em resumo

Escolher configurações com o conjunto de teste estraga o teste. A validação cruzada avalia o modelo em cada parte dos dados e mostra se a nota é estável.

Aquecimento Duas perguntas de aulas anteriores. Um minuto, e a aula nunca fica bloqueada.
Da aula 3.1: Overfitting: quando o modelo decora Por que mais dados de treino costumam deixar um modelo flexível mais seguro?

Ver a resposta

Resposta certa: Porque, com mais pontos, o ruído se dilui e a curva não persegue pontos isolados

O ruído de cada ponto continua lá, mas com muitos pontos ele se compensa, e o ajuste segue a tendência real.

Da aula 2.6: Métrica e limiar pelo custo dos erros Uma falha custa 10, um alarme falso 1, e o modelo está calibrado. Que limiar sai da teoria da decisão?

Ver a resposta

Resposta certa: 1 / (1 + 10) ≈ 0,09, porque o alarme já compensa com um risco pequeno

O alarme compensa quando p · 10 > (1 − p) · 1, ou seja, p > 1/11. No lab, o limiar 0,09 custa 125. O limiar mais barato, 0,06, custa 111 só nas pontuações usadas para escolhê-lo.

Na lição anterior, escolhemos o melhor grau do polinômio dando uma espiada no conjunto de teste. Pareceu inofensivo, mas pense no que isso significa: testamos vários modelos e ficamos com o que o conjunto de teste mais gostou. O teste deixou de ser uma prova inédita — virou um juiz que consultamos durante a preparação. Como escolher configurações sem estragar a nossa única medida honesta?

Preveja, depois confira Você aumenta o número de folds k de 2 para 10 (178 vinhos no total). O que acontece com a diferença entre o pior e o melhor fold?
Qual é o seu grau de certeza?

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: Aumenta, porque cada fold de teste tem menos vinhos

Para k = 2, o pior fold tem 94,4% e o melhor 97,8% (diferença de 3,4 pontos percentuais). Para k = 10, a amplitude vai de 83,3% a 100,0% (16,7 pontos), porque um fold de teste tem 17 ou 18 vinhos e um único erro pesa mais de 5 pontos. A média quase não muda (96,1% e 96,7%).

Experimente

  • Coloque o controle de k no menor valor e leia a acurácia de cada fold. Quão diferentes os folds são entre si?
  • Aumente o controle de k passo a passo. Observe a média e a dispersão, e repare que cada fold agora tem menos vinhos.
  • Com um k grande, procure folds com acurácia muito baixa ou muito alta. Com só um punhado de vinhos num fold, um único erro mexe muito na nota desse fold.
  • Compare a dispersão com as diferenças entre configurações que você talvez queira ajustar. Um pequeno ganho na média é maior que a dispersão?

A ideia em poucas palavras

Vamos ver agora, em palavras simples, o que você acabou de manipular.

A primeira solução é uma terceira pilha de dados. Divida as linhas em conjuntos de treino, validação e teste. Treine os modelos candidatos no conjunto de treino, compare-os no de validação, escolha o vencedor e só então abra o conjunto de teste, uma vez, para relatar a nota final. O conjunto de validação absorve todas as espiadas para que o de teste não precise.

O problema: cada divisão custa dados. Com um conjunto de dados pequeno, um conjunto de validação grande o bastante para ser confiável deixa pouco demais para treinar, e a lição 2 mostrou que um conjunto pequeno dá uma nota que pula. A validação cruzada resolve isso reaproveitando os dados com esperteza.

Imagine avaliar uma cozinheira nova. Provar um prato só diz pouco: talvez tenha sido o melhor dela, talvez o pior. Então você pede vários pratos em dias diferentes e olha a média e o quanto eles variam. A validação cruzada faz o mesmo com um modelo.

Por que funciona

Na validação cruzada k-fold, os dados (sem contar o conjunto de teste final) são cortados em k partes iguais chamadas folds. O modelo é treinado k vezes. Em cada uma, um fold fica de fora para validação e os k − 1 restantes são usados no treino. No fim, cada linha foi usada exatamente uma vez para validação e k − 1 vezes para treino. Cada treinamento vê esta fração dos dados:

fração de treino = (k − 1) / k

O resultado não é uma nota, e sim k notas, uma por fold. A média delas é uma estimativa mais confiável que qualquer divisão única, porque não depende de uma divisão de linhas com sorte ou azar. A dispersão é igualmente valiosa: mostra o quanto a nota depende de quais linhas o modelo recebeu. Dois modelos cujas médias diferem menos que essa dispersão não podem ser realmente distinguidos.

A escolha de k é um dilema. Um k pequeno (digamos 2) treina cada modelo com só metade dos dados, então cada modelo é mais fraco que o que você vai construir no fim, e a estimativa é pessimista. Um k grande treina com quase todos os dados, mas cada fold é pequeno, então as notas de cada fold ficam mais ruidosas, e o cálculo leva k vezes o tempo de um único ajuste. Valores 5 ou 10 são um meio-termo comum.

O exemplo desta lição é o conjunto de dados Wine: 178 vinhos da mesma região da Itália, feitos com três cultivares de uva diferentes, cada um descrito por 13 resultados de análise química. A tarefa é distinguir os três cultivares.

Erro comum

A validação cruzada só protege você se tudo o que é aprendido dos dados acontecer dentro de cada fold. Se você padroniza os atributos ou seleciona os “melhores” atributos usando todas as linhas primeiro e só depois faz a validação cruzada, informação dos folds de validação já vazou para o treino, e as notas ficam otimistas demais. O segundo erro é ajustar dezenas de configurações com validação cruzada e relatar a melhor nota da validação cruzada como resultado final. Depois de tantas comparações, o vencedor tem uma parte de sorte; por isso um conjunto de teste separado, usado uma única vez, continua importando.

Com avaliação honesta na caixa de ferramentas, podemos explorar modelos mais poderosos sem nos enganar. Na próxima lição conhecemos o mais legível deles: a árvore de decisão.

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.

1. Numa validação cruzada com 5 folds, quantas vezes cada linha é usada para validação?
Ver a resposta

Resposta certa: Exatamente uma vez, em um único fold

Cada linha pertence a um fold, e cada fold serve de conjunto de validação em exatamente uma das cinco rodadas.

2. O modelo A tem acurácia média na validação cruzada um pouco acima da do modelo B, mas a dispersão entre os folds é muito maior que a diferença. O que se conclui?
Ver a resposta

Resposta certa: Que os dados não mostram diferença confiável entre os dois modelos

Quando a diferença entre as médias é menor que a variação entre os folds, ela pode facilmente ser obra do acaso.

3. Por que padronizar os atributos com todas as linhas antes da validação cruzada é um erro?
Ver a resposta

Resposta certa: Porque dados dos folds de validação vazam para o treino e inflam as notas

Toda etapa que aprende com os dados deve ser ajustada só nos folds de treino; caso contrário, os folds de validação deixam de ser inéditos.

Pontos-chave

  • Escolher configurações com o conjunto de teste o estraga; use um conjunto de validação ou validação cruzada.
  • Na validação cruzada k-fold, cada linha é usada para validação exatamente uma vez.
  • A média das notas dos folds estima o desempenho; a dispersão mostra o quanto ele depende da divisão.
  • Toda preparação que aprende com os dados deve acontecer dentro de cada fold para evitar vazamento.

Mais a fundo: fórmula, código, prática

A matemática

Na validação cruzada de k dobras, as n linhas são cortadas em k dobras. Cada ajuste treina com n · (k − 1) / k linhas e testa nas n / k restantes. A pontuação é a média das dobras: CV = (1/k) · Σ score_f. A dispersão é o desvio padrão delas, s = √( Σ (score_f − CV)² / (k − 1) ).

Uma dobra de m linhas de teste dá uma acurácia que se move em saltos de 1 / m. Se a acurácia verdadeira é p, seu erro padrão é de cerca de √( p · (1 − p) / m ). Isso cresce quando m diminui, e por isso cada dobra isolada fica mais ruidosa quando k sobe, embora a média fique menos pessimista. Com 178 vinhos e k = 10, um erro em uma dobra de 17 ou 18 vinhos custa de 5,6 a 5,9 pontos.

As pontuações das dobras não são totalmente independentes: os conjuntos de treino se sobrepõem. Por isso s / √k subestima a incerteza real da média.

No código (Python)

Os 178 vinhos com 5 vizinhos mais próximos e variáveis padronizadas. Para ver o padrão por trás do embaralhamento único do lab, a diferença entre a pior e a melhor dobra é calculada em média sobre 20 embaralhamentos. As dobras são estratificadas e embaralhadas com uma semente, então os números não são os 3,4 e 16,7 pontos do lab:

import numpy as np
from sklearn.datasets import load_wine
from sklearn.model_selection import StratifiedKFold, cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

X, y = load_wine(return_X_y=True)
model = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=5))  # o escalonador é reajustado dentro de cada dobra
for k in (2, 10):
    runs = [cross_val_score(model, X, y, cv=StratifiedKFold(k, shuffle=True, random_state=s)) for s in range(20)]
    gap = np.mean([a.max() - a.min() for a in runs])
    print(f"k={k:2d}: mean accuracy {np.mean(runs):.3f}, average worst-to-best gap {gap:.3f}")

Saída

k= 2: mean accuracy 0.956, average worst-to-best gap 0.020
k=10: mean accuracy 0.966, average worst-to-best gap 0.113

A média quase não se move (0,956 e 0,966), mas a diferença média entre a pior e a melhor dobra cresce de 0,020 para 0,113 quando k vai de 2 para 10.

Para quem pratica
  • Ajuste cada etapa aprendida (escalonamento, imputação, seleção de variáveis) dentro de cada dobra. No scikit-learn, coloque-as em um Pipeline e passe-o para cross_val_score.
  • Na classificação use dobras estratificadas, para que cada dobra tenha todas as classes. cross_val_score faz isso por padrão nos classificadores quando cv é um número, mas esse padrão não embaralha.
  • Embaralhe antes de cortar as dobras se as linhas estiverem ordenadas, por exemplo por classe ou por data. load_wine está ordenado por classe, então passe StratifiedKFold(k, shuffle=True, random_state=0), como acima. Em séries temporais não embaralhe: use uma divisão sequencial para a frente.
  • Se as linhas pertencem a grupos (várias linhas por paciente), mantenha cada grupo em uma só dobra com GroupKFold. Caso contrário, o mesmo paciente fica no treino e na validação.
  • Informe a média e a dispersão. Se dois modelos diferem menos que a dispersão, não diga que um deles é melhor.

Vá mais fundo no Atlas

Verbetes da enciclopédia sobre as ideias desta aula: