Machine learning do zero · Aula 1.4 · 5 de 33 aulas prontas · 12 min
Vizinhos mais próximos: o modelo mais simples
Em resumo
O kNN não aprende regra: um caso novo recebe o rótulo da maioria dos seus k exemplos mais próximos. O k define as fronteiras; as unidades, quem está perto.
Aquecimento Duas perguntas de aulas anteriores. Um minuto, e a aula nunca fica bloqueada.
A ideia mais simples para um modelo é esta: casos parecidos têm respostas parecidas. Para reconhecer um pinguim novo, encontre os k pinguins de aprendizado mais parecidos com ele e pergunte de que espécie eles são. É o método dos k vizinhos mais próximos (em inglês, k-nearest neighbours, kNN). Voltamos aos pinguins da aula 0.1, desta vez descritos só por duas medidas: o comprimento do bico e a massa corporal.
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: Acerta menos, em média menos que o kNN que consulta 9 vizinhos
Nos pinguins de aprendizado, k = 1 tem 100%; nos de teste, 94,2% na divisão 1. Em média em 100 divisões, k = 1 acerta 92,2% e k = 9 94,2%; k = 9 vence em 78 divisões e perde em 9. Um só vizinho copia cada exceção; o voto de 9 vizinhos a suaviza.
Experimente
- O lab começa em unidades brutas, milímetros e gramas, com k = 1. Mude para unidades padronizadas: o que acontece com as fronteiras e com o resultado?
- Ainda com k = 1, olhe as fronteiras entre as espécies: onde estão recortadas?
- Aumente o k. Com qual k as fronteiras ficam suaves, e com qual o resultado no teste cai claramente?
- Compare a última linha, a média de 20 divisões, para k = 1 e k = 9.
Depois de conferir no lab, vamos dar nome ao que você vê.
A ideia em poucas palavras
O kNN não constrói regra nenhuma. “Aprender” é guardar os 239 pinguins de aprendizado. Todo o trabalho acontece quando chega uma pergunta: para um pinguim novo, o modelo calcula a distância até cada pinguim guardado, pega os k mais próximos e deixa a maioria decidir. O fundo do lab é exatamente essa resposta em cada ponto do gráfico.
O número k define quanto o modelo confia em exemplos isolados. Com k = 1, a fronteira contorna cada pinguim, inclusive as exceções que ficam no meio de outra espécie. Com um k grande, por exemplo 101 de 239, quase metade do conjunto vota, as fronteiras ficam suaves e a espécie mais rara, Chinstrap, perde votações: na divisão 1 a acurácia cai para 83,5%. Um bom k fica no meio, e ele é escolhido com dados, não no olho.
O segundo controle aparece menos: as unidades. A distância soma a diferença no comprimento do bico com a diferença na massa. Os bicos vão de 32,1 a 59,6 mm, e a massa de 2700 a 6300 g. Sem reescalar, uma diferença de 100 g pesa tanto quanto 100 mm de bico, então quase só a massa decide quem está perto. Padronizar transforma cada medida em um número de desvios-padrão em relação à média, e as duas medidas passam a pesar o mesmo.
Jak to policzono
- Dados: Palmer Penguins (Gorman, Williams e Fraser, 2014; pacote palmerpenguins: Horst, Hill e Gorman, 2020), licença CC0. 342 pinguins com todas as medidas, 3 espécies.
- Divisão: 70/30, estratificada; a divisão 1 é
train_test_split(test_size=0.3, stratify=y, random_state=1)no scikit-learn, a mesma da aula 0.1. 239 pinguins para aprender, 103 no teste. - Modelo: kNN com distância euclidiana, um voto por vizinho; um empate vai para a espécie que vem primeiro em ordem alfabética (como no scikit-learn).
- Padronização: a média e o desvio-padrão vêm só dos pinguins de aprendizado.
Por que funciona
O kNN funciona quando estar perto nas medidas significa ser parecido na resposta. Nos pinguins isso vale: as espécies diferem na constituição física, então pinguins com bico e massa parecidos costumam ser da mesma espécie. Por isso duas medidas já dão 94,2% contra 43,7% do chute “sempre Adelie”.
Um k pequeno e um k grande estragam o resultado por lados opostos. Com k = 1 o modelo é muito sensível ao acaso: um pinguim de aprendizado atípico muda a resposta em toda a sua vizinhança. Os 100% no aprendizado não dizem nada aqui, porque cada pinguim encontra a si mesmo. Com um k muito grande o modelo deixa de ver a forma dos dados e se aproxima de “a espécie mais comum por perto”. É o mesmo equilíbrio entre ajustar e generalizar que volta no módulo 3.
As unidades importam tanto porque o kNN não sabe qual medida é importante: ele só calcula distâncias. Em unidades brutas, com k = 5, ele acerta 77,7% dos pinguins de teste; depois de padronizar, 94,2%. Com as quatro medidas, com a massa em gramas, a diferença é ainda maior: 76,7% sem escalonamento e 96,1% depois de padronizar, e em média em 100 divisões 76,7% e 98,9%.
Erro comum
Verdadeiro ou falso: “O menor k é o melhor, porque o modelo se ajusta perfeitamente aos dados”. Falso. Com k = 1 o modelo tem 100% nos pinguins de aprendizado, porque cada um é o próprio vizinho, mas em pinguins novos perde em média para k = 9: 92,2% contra 94,2% em 100 divisões. Ajustar-se perfeitamente aos dados de aprendizado é decorar também as exceções. Escolha o k pelo resultado em dados que o modelo não viu.
Onde a analogia falha: os “vizinhos” do kNN não são os pinguins parados ao seu lado na praia, e sim os de medidas parecidas. E quem é parecido depende das unidades que você escolher.
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
- O kNN não constrói regra: um caso novo recebe o voto da maioria dos seus k exemplos de aprendizado mais próximos.
- Um k pequeno segue cada exceção, um k grande suaviza as fronteiras até virar chute; o k é escolhido em dados que o modelo não viu.
- 100% nos dados de aprendizado com k = 1 não significa nada, porque cada exemplo é o próprio vizinho.
- O kNN calcula distâncias, então as unidades decidem o resultado: padronize as medidas nos dados de aprendizado.
Mais a fundo: fórmula, código, prática
A matemática
A distância euclidiana entre os pinguins a e b descritos por duas medidas é d(a, b) = √((a₁ − b₁)² + (a₂ − b₂)²). O modelo ordena os pinguins de aprendizado por d e pega os k primeiros; a resposta é a espécie com mais votos entre eles.
Padronizar transforma uma medida x em z = (x − μ) / σ, em que μ e σ são a média e o desvio-padrão dessa medida nos dados de aprendizado. Sem isso, uma diferença de 100 g na massa soma 100² = 10000 a d², e uma diferença de 10 mm no bico só 10² = 100. Depois de padronizar, cada medida conta em desvios-padrão e as duas pesam o mesmo.
Com k = 1, cada exemplo de aprendizado está à distância 0 de si mesmo, então a acurácia no aprendizado é de 100%, desde que dois exemplos com rótulos diferentes não tenham medidas idênticas.
No código (Python)
O mesmo experimento no scikit-learn: as duas medidas do lab em unidades brutas e padronizadas para cada k, k = 1 nos pinguins de aprendizado, k = 1, 9 e 101 em 100 divisões e as quatro medidas com k = 5. O notebook indicado abaixo da saída baixa os dados sozinho.
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
df = pd.read_csv("data/raw/penguins.csv")
cols4 = ["bill_length_mm", "bill_depth_mm", "flipper_length_mm", "body_mass_g"]
d = df.dropna(subset=cols4) # os 342 pinguins com as quatro medidas
y = d["species"]
K = [1, 3, 5, 9, 15, 25, 51, 101]
def knn(k, scale):
model = KNeighborsClassifier(n_neighbors=k)
return make_pipeline(StandardScaler(), model) if scale else model # o escalonador aprende média e desvio só na parte de aprendizado
def score(cols, k, scale, seed):
Xtr, Xte, ytr, yte = train_test_split(d[cols], y, test_size=0.3, stratify=y, random_state=seed)
m = knn(k, scale).fit(Xtr, ytr)
return m.score(Xte, yte), m.score(Xtr, ytr)
lab = ["bill_length_mm", "body_mass_g"] # as duas medidas do lab: milímetros e gramas
for scale in (False, True):
print(("standardized:" if scale else "raw mm and g:"), " ".join(f"k={k} {score(lab, k, scale, 1)[0]:.3f}" for k in K))
print(f"k=1 on the learning penguins: {score(lab, 1, True, 1)[1]:.3f}")
runs = {k: np.array([score(lab, k, True, s)[0] for s in range(100)]) for k in (1, 9, 101)}
print(f"100 splits, standardized: k=1 {runs[1].mean():.3f}, k=9 {runs[9].mean():.3f}, k=101 {runs[101].mean():.3f}; k=9 beats k=1 in {(runs[9] > runs[1]).sum()}, loses in {(runs[9] < runs[1]).sum()}")
four = [[score(cols4, 5, scale, s)[0] for s in range(100)] for scale in (False, True)]
print(f"all 4 measurements, k=5: raw {four[0][1]:.3f}, standardized {four[1][1]:.3f} on split 1; mean of 100 splits {np.mean(four[0]):.3f} and {np.mean(four[1]):.3f}")
Saída
raw mm and g: k=1 0.835 k=3 0.748 k=5 0.777 k=9 0.757 k=15 0.777 k=25 0.748 k=51 0.738 k=101 0.738
standardized: k=1 0.942 k=3 0.922 k=5 0.942 k=9 0.942 k=15 0.942 k=25 0.942 k=51 0.942 k=101 0.835
k=1 on the learning penguins: 1.000
100 splits, standardized: k=1 0.922, k=9 0.942, k=101 0.856; k=9 beats k=1 in 78, loses in 9
all 4 measurements, k=5: raw 0.767, standardized 0.961 on split 1; mean of 100 splits 0.767 and 0.989
Na divisão 1 os resultados são os mesmos do lab. O lab tira a média das suas próprias 20 divisões e este código de 100 divisões do scikit-learn, então as médias diferem um pouco, mas a direção é a mesma: em média, k = 9 supera k = 1.
Baixar o notebook (.ipynb). Abra no Jupyter ou envie para o Google Colab; ele baixa sozinho os dados que faltarem.
Para quem pratica
- Escalone dentro de um pipeline:
make_pipeline(StandardScaler(), KNeighborsClassifier()). Um escalonador ajustado no conjunto inteiro antes da divisão vaza informação do teste para o aprendizado. - Escolha o k com validação cruzada, por exemplo
GridSearchCVsobren_neighbors; com duas classes, um k ímpar evita empates. weights="distance"dá mais peso aos vizinhos mais próximos; isso costuma ajudar com um k grande.- O kNN responde devagar com muitos dados, porque calcula a distância até cada exemplo, e enfraquece com muitas medidas, quando todas as distâncias ficam parecidas.
- Medidas irrelevantes para a tarefa também entram na distância e estragam a vizinhança; escolha os atributos antes de usar o kNN.
Isto foi útil?
Obrigado! Isso nos ajuda a melhorar.
Vá mais fundo no Atlas
Verbetes da enciclopédia sobre as ideias desta aula: