Pular para o conteúdo
</>RoByte

Buscar cursos e aulas

Aulas do curso

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).