Vídeo em breve
Por enquanto, esta aula está disponível só em texto — o vídeo entra assim que ficar pronto.
Avaliando modelos
Overfitting, validação cruzada e pipelines
Por que um modelo pode decorar em vez de aprender, como medir de forma mais confiável e como organizar o pré-processamento sem vazar dados. · 20 min
O maior perigo em Machine Learning é criar um modelo que vai muito bem nos dados que já viu e mal nos dados novos. Esta aula mostra como detectar esse problema e as ferramentas do scikit-learn para evitá-lo.
Overfitting e underfitting
- Overfitting (sobreajuste): o modelo é complexo demais e decora o treino, incluindo o ruído. Vai bem no treino e mal no teste.
- Underfitting (subajuste): o modelo é simples demais e não captura o padrão. Vai mal nos dois.
O sinal clássico de overfitting é uma grande diferença entre a nota no treino e a nota no teste. Veja com árvores de várias profundidades:
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.tree import DecisionTreeClassifier
X, y = load_breast_cancer(return_X_y=True)
X_treino, X_teste, y_treino, y_teste = train_test_split(
X, y, test_size=0.3, random_state=42, stratify=y
)
for profundidade in [1, 2, 3, 5, 10, None]:
arvore = DecisionTreeClassifier(max_depth=profundidade, random_state=42)
arvore.fit(X_treino, y_treino)
nota_treino = arvore.score(X_treino, y_treino)
nota_teste = arvore.score(X_teste, y_teste)
print(profundidade, round(nota_treino, 3), round(nota_teste, 3))
Com max_depth=None (sem limite) a nota no treino chega a 1.0, a árvore
acerta tudo o que já viu, mas a nota no teste não acompanha. Com profundidade
1 as duas notas são baixas (underfitting). O ponto ideal fica no meio.
Validação cruzada
Uma única divisão treino/teste pode dar sorte ou azar. Na validação
cruzada (cross-validation) os dados são divididos em k partes; o
modelo treina em k - 1 delas e testa na restante, repetindo até todas
as partes terem sido teste uma vez.
from sklearn.model_selection import cross_val_score
notas = cross_val_score(DecisionTreeClassifier(max_depth=3, random_state=42),
X, y, cv=5)
print(notas) # 5 notas, uma por divisão
print(notas.mean(), notas.std()) # média e variação
A média dá uma estimativa mais confiável, e o desvio padrão mostra o quanto o resultado é estável.
Pipelines: pré-processamento sem vazamento
Lembra que KNN e outros modelos são sensíveis à escala? O
StandardScaler padroniza cada feature (média 0 e desvio 1). Mas há uma
armadilha: se você padronizar todos os dados antes de dividir, a média
e o desvio do teste “vazam” para o treino. É o chamado vazamento de
dados (data leakage), que deixa a avaliação otimista demais.
A solução é o Pipeline, que encadeia os passos e garante que o
pré-processamento aprenda só com os dados de treino de cada divisão:
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
pipeline = make_pipeline(StandardScaler(), KNeighborsClassifier(n_neighbors=5))
notas = cross_val_score(pipeline, X, y, cv=5)
print(notas.mean())
Dica
Um pipeline se comporta como um modelo único: tem fit, predict e
score. Isso deixa o código mais limpo e é o jeito profissional de
organizar um projeto de ML.
Escolhendo hiperparâmetros com GridSearchCV
Em vez de testar valores na mão, o GridSearchCV testa todas as
combinações que você listar, usando validação cruzada em cada uma:
from sklearn.model_selection import GridSearchCV
parametros = {"max_depth": [2, 3, 4, 5, 8, None]}
busca = GridSearchCV(DecisionTreeClassifier(random_state=42), parametros, cv=5)
busca.fit(X_treino, y_treino)
print(busca.best_params_) # a melhor combinação encontrada
print(busca.best_score_) # a nota média dela na validação cruzada
print(busca.score(X_teste, y_teste)) # avaliação final no teste
Atenção
O conjunto de teste deve ficar guardado até o final. Se você ajusta o modelo olhando a nota do teste várias vezes, ele vira parte do treino sem você perceber, e a nota final deixa de ser honesta. Use a validação cruzada para ajustar, e o teste só uma vez, no fim.
Exercícios
Exercício
1. Aquecimento. Repita o laço de profundidades da aula e desenhe um
gráfico com duas linhas (nota de treino e nota de teste) em função de
max_depth. Onde as linhas começam a se afastar?
Exercício
2. Escala importa. Carregue load_wine e avalie um
KNeighborsClassifier com cross_val_score, uma vez sem escalar e outra
dentro de um pipeline com StandardScaler. Qual a diferença nas notas?
(As features do dataset de vinhos têm escalas muito diferentes.)
Exercício
3. Desafio. Use GridSearchCV num pipeline
make_pipeline(StandardScaler(), KNeighborsClassifier()) para buscar o
melhor k entre 1 e 15. Dica: no pipeline, o nome do parâmetro é
"kneighborsclassifier__n_neighbors" (nome do passo, dois underscores e
o parâmetro).