Machine learning do zero · Aula 3.5 · 18 de 33 aulas prontas · 14 min
Ajustar hiperparâmetros sem trapacear
Em resumo
A melhor de 9 configurações da árvore tem 93,7% na validação cruzada e 88,3% em biópsias novas. Escolher a vencedora infla o resultado dela.
Aquecimento Duas perguntas de aulas anteriores. Um minuto, e a aula nunca fica bloqueada.
Na aula 3.2 a validação cruzada avaliava uma única configuração do modelo. Na prática você testa muitas e fica com a melhor. Isso se chama ajuste de hiperparâmetros. As configurações que você escolhe antes do treino (a profundidade de uma árvore, a força da penalidade α da aula 3.4) são hiperparâmetros: o modelo não os aprende com os dados, você os escolhe. Hoje você vai ver que o resultado da vencedora do ajuste é otimista demais, e por quê.
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: Menos, por exemplo 88,3%, porque também escolhemos ruído favorável
Nas 171 biópsias de teste a vencedora tem 88,3%, 5,4 pontos a menos que na validação cruzada. Parte disso é seleção, parte é o teste pequeno: uma biópsia vale 0,6 ponto. Escolher a melhor de 20 sementes de divisão infla o resultado em 2,7 pontos. Esta divisão está do lado pessimista: em outras 50 divisões (código abaixo) a vencedora perde em média 1,4 ponto.
Experimente
- Com a grade, mude o orçamento de 4 para 9. Quanto a vencedora tem na validação cruzada e quanto no teste?
- Mude para busca aleatória e clique em "novo sorteio" algumas vezes. A vencedora alguma vez é melhor no teste do que na validação?
- Na segunda parte, mova o controle de sementes de 1 a 20. Como cresce o melhor resultado e quanto ele se afasta da média de 200 divisões?
Depois de conferir no lab, vamos dar nome ao que você vê.
A ideia em palavras simples
Ajustar é comparar muitas configurações em dados de validação e escolher a melhor. A busca em grade testa todas as combinações de poucos valores. A busca aleatória sorteia combinações de toda a faixa. Com o mesmo orçamento, uma grade 3×3 testa só 3 profundidades: 1, 6 e 12. Nove tentativas aleatórias costumam ver mais valores diferentes de cada hiperparâmetro.
Cada resultado de validação é a qualidade real da configuração mais um pouco de sorte: quais biópsias caíram em qual parte. Quando você escolhe o melhor resultado, escolhe também a configuração que a sorte mais favoreceu. Em dados novos essa sorte some. É a maldição do vencedor. Ela cresce com o número de configurações comparadas e diminui com o tamanho dos dados.
O mesmo acontece quando você procura a melhor divisão dos dados. A mesma regressão logística em 200 divisões aleatórias tem em média 93,8% no teste. Se você testar as sementes 1–20 e relatar a melhor, sai 96,5%, 2,7 pontos a mais. O código é o mesmo; você só escolheu a sorte.
Jak to policzono
- Biópsias: Breast Cancer Wisconsin (Diagnostic), UCI, licença CC BY 4.0. 569 biópsias, 10 medidas "mean" de cada uma.
- Ajuste: divisão estratificada, 398 biópsias para ajustar e 171 de teste. Uma árvore CART (Gini), 10 profundidades vezes 11 números mínimos de biópsias por folha, ou seja, 110 configurações, cada uma na validação cruzada de 5 partes com as 398 biópsias.
- Vencedora: a melhor média das 5 partes; em caso de empate, a árvore mais simples. No teste se avalia a árvore treinada com as 398 biópsias.
- Sementes: regressão logística (C = 1) em medidas padronizadas, 200 divisões aleatórias 70/30, com 171 biópsias de teste em cada uma.
Por que funciona
Pegue 9 configurações de qualidade real quase igual. Os resultados de validação delas diferem sobretudo por ruído. O maior de 9 resultados é quase sempre um em que o ruído foi positivo. No lab a melhor CV acerta 373 de 398 biópsias, e várias configurações ficam só uma ou duas biópsias atrás. Diferenças tão pequenas são em boa parte acaso.
A seleção aparece no modo aleatório: em 200 sorteios de 9 configurações, a vencedora tem mais na validação do que no teste em todos eles, 3,6 pontos em média. O teste não participou da escolha, então não carrega seleção, só o ruído comum de um conjunto finito. Mas os 200 sorteios são configurações diferentes sobre uma mesma divisão dos dados, e essa divisão caiu do lado pessimista: mesmo sem escolha nenhuma, as 110 configurações têm em média 0,6 ponto a mais na validação do que no teste. No código abaixo, em 50 divisões diferentes, a vencedora perde em média 1,4 ponto, e em 17 das 50 divisões a validação não fica acima do teste.
Por isso o teste não pode ser usado para escolher. Se você escolhesse uma configuração pelo resultado do teste, a melhor célula teria 92,4% no teste. Esse número também estaria inflado, só que seria mais difícil perceber, porque não sobrariam dados para conferir.
Erro comum
Verdadeiro ou falso: "o resultado da vencedora do ajuste é uma previsão para dados novos". Falso. O resultado de validação da vencedora é inflado, porque é o máximo de muitos números com ruído. No lab a vencedora de 9 configurações tem 93,7% na validação e 88,3% no teste. No código abaixo, em 50 divisões e 110 configurações, a vencedora tem em média 93,3% na validação e 91,9% no teste reservado.
Uma previsão justa vem de um de dois caminhos. Primeiro: um teste reservado, que você não usa para nenhuma escolha e olha uma vez, no fim. Segundo: a validação cruzada aninhada, ou seja, todo o ajuste repetido dentro de cada parte de uma validação cruzada externa. Com as 569 biópsias a vencedora tem 94,0% na validação cruzada comum, enquanto a aninhada dá 91,9%. O segundo número está mais perto do que o modelo fará amanhã.
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
- Os hiperparâmetros você escolhe antes do treino; eles são ajustados na validação, nunca no teste.
- O resultado da vencedora do ajuste é inflado: ao escolher o melhor resultado você escolhe também a sorte (a maldição do vencedor).
- Escolher a melhor semente de divisão também é ajustar no conjunto de teste.
- Uma previsão justa vem de um teste reservado olhado uma vez ou da validação cruzada aninhada.
Mais a fundo: fórmula, código, prática
A matemática
Seja μₖ a qualidade real da configuração k e Ŝₖ = μₖ + εₖ o resultado de validação dela, onde o ruído εₖ tem média 0. Para uma configuração, E[Ŝₖ] = μₖ. Para a vencedora k = argmaxₖ Ŝₖ, em média E[Ŝ_k] ≥ μ_k*: o máximo de vários números com ruído é em média maior que a qualidade real da escolhida. Com m configurações de igual qualidade, E[max εₖ] cresce com m mais ou menos como σ · √(2 · ln m).
Uma biópsia de teste com n = 171 é 1/171, ou seja, 0,6 ponto. O erro-padrão de uma acurácia p em n exemplos: √(p(1 − p)/n).
Validação cruzada aninhada: um laço externo divide os dados em K partes; para cada parte, todo o ajuste (uma validação cruzada interna) roda só nas outras, e o modelo escolhido é avaliado na parte reservada.
No código (Python)
O mesmo experimento no scikit-learn: a maldição do vencedor em 50 divisões com as 110 configurações da árvore, a validação cruzada comum e a aninhada com todas as biópsias, e a melhor de 20 sementes de divisão.
import numpy as np
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split, GridSearchCV, StratifiedKFold, cross_val_score
from sklearn.tree import DecisionTreeClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LogisticRegression
X, y = load_breast_cancer(return_X_y=True)
X = X[:, :10] # as 10 medidas "mean", como no lab
grid = {"max_depth": [1, 2, 3, 4, 5, 6, 7, 8, 10, 12], "min_samples_leaf": [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48]} # as 110 configurações do lab
tree = DecisionTreeClassifier(random_state=0)
r = []
for s in range(50):
tr, te = train_test_split(np.arange(len(y)), test_size=0.3, stratify=y, random_state=s)
search = GridSearchCV(tree, grid, cv=StratifiedKFold(5, shuffle=True, random_state=s)).fit(X[tr], y[tr])
r.append((search.best_score_, search.score(X[te], y[te]))) # a CV da vencedora, depois a vencedora no teste reservado
r = np.array(r)
print(f"winner of 110 settings, 50 splits: CV {r[:, 0].mean():.1%}, held-out test {r[:, 1].mean():.1%}; CV higher in {(r[:, 0] > r[:, 1]).sum()} of 50")
outer = StratifiedKFold(5, shuffle=True, random_state=0)
flat = GridSearchCV(tree, grid, cv=outer).fit(X, y).best_score_ # o resultado da vencedora nas mesmas partes que a escolheram
nested = cross_val_score(GridSearchCV(tree, grid, cv=StratifiedKFold(5, shuffle=True, random_state=1)), X, y, cv=outer).mean() # a busca roda dentro de cada parte externa
print(f"all 569 biopsies: winner's CV {flat:.1%}, nested CV {nested:.1%}")
model = make_pipeline(StandardScaler(), LogisticRegression())
acc = []
for s in range(200): # 200 divisões aleatórias, 30% de teste, como no lab
tr, te = train_test_split(np.arange(len(y)), test_size=0.3, random_state=s)
acc.append(model.fit(X[tr], y[tr]).score(X[te], y[te]))
acc = np.array(acc)
best = acc.reshape(10, 20).max(axis=1) # dez pessoas, cada uma testa 20 sementes e relata a melhor
print(f"logistic regression, 200 seeds: mean {acc.mean():.1%}; best of 20 seeds {best.mean():.1%} on average (+{(best.mean() - acc.mean()) * 100:.1f} points), higher in {(best > acc.mean()).sum()} of 10")
Saída
winner of 110 settings, 50 splits: CV 93.3%, held-out test 91.9%; CV higher in 33 of 50
all 569 biopsies: winner's CV 94.0%, nested CV 91.9%
logistic regression, 200 seeds: mean 93.7%; best of 20 seeds 96.4% on average (+2.6 points), higher in 10 of 10
As divisões são diferentes das do lab, então os números também. O quadro é o mesmo: a vencedora do ajuste tem em média 93,3% na validação e 91,9% no teste reservado; a validação é maior em 33 divisões de 50. A validação cruzada aninhada dá 91,9% em vez de 94,0%, e a melhor de 20 sementes infla o resultado em 2,6 pontos, para cada uma das 10 pessoas.
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:
GridSearchCVeRandomizedSearchCV. Obest_score_é inflado pelo mesmo motivo do lab; avalie a vencedora comscoreem um teste reservado. - Validação cruzada aninhada:
cross_val_score(GridSearchCV(...), X, y, cv=...). O ajuste então roda dentro de cada parte externa. - Com muitos hiperparâmetros dos quais só alguns importam, a busca aleatória costuma achar uma boa configuração mais rápido que a grade (Bergstra e Bengio, 2012).
- Fixe a semente aleatória uma vez, antes do experimento, e anote. Relate a média e a dispersão de várias divisões ou da validação cruzada, não o resultado da melhor semente.
Isto foi útil?
Obrigado! Isso nos ajuda a melhorar.
Vá mais fundo no Atlas
Verbetes da enciclopédia sobre as ideias desta aula: