K-nearest neighbors
Aprenda como o algoritmo KNN funciona, como escolher K, escalar features e criar modelos de classificação e regressão em Python.
K-Nearest Neighbors (KNN) é um dos algoritmos de aprendizado de máquina mais simples e intuitivos. Para classificar um novo ponto de dados, ele analisa os K exemplos de treinamento mais próximos e faz uma votação — a classe majoritária vence. Para regressão, ele calcula a média dos valores dos K vizinhos no lugar disso.
Este capítulo aborda:
- Como o algoritmo KNN funciona passo a passo
- Métricas de distância: Euclidiana, Manhattan e Minkowski
- Por que o escalonamento de features é crítico para o KNN
- Como escolher o valor correto de
K - Classificação e regressão com scikit-learn
- Avaliação de um classificador KNN com uma matriz de confusão
- Pontos fortes, limitações e quando usar o KNN
Como o KNN Funciona
KNN é um aprendiz preguiçoso — ele não realiza nenhum "treinamento" real. Em vez disso, ele memoriza todo o conjunto de dados e adia todo o processamento para o momento da predição.
Dado um novo ponto, o algoritmo:
- Calcula a distância do novo ponto para cada ponto de treinamento.
- Seleciona os
Kpontos de treinamento com as menores distâncias (os "vizinhos mais próximos"). - Agrega seus rótulos:
- Classificação — o novo ponto recebe o rótulo de classe mais comum.
- Regressão — o novo ponto recebe a média (ou média ponderada) dos valores alvo dos vizinhos.
Como não há modelo para treinar, adicionar novos dados é trivial — basta acrescentar ao conjunto de dados. A contrapartida é que a predição é lenta para conjuntos de dados grandes, pois o cálculo completo de distâncias é executado a cada vez.
Métricas de Distância
O "mais próximo" no KNN é definido por uma função de distância. O padrão no scikit-learn é a distância Euclidiana, a distância em linha reta no espaço n-dimensional:
d(p, q) = sqrt( (p1-q1)² + (p2-q2)² + … + (pn-qn)² )Duas alternativas comuns:
| Métrica | Fórmula | Melhor para |
|---|---|---|
| Euclidiana | sqrt(Σ(pᵢ-qᵢ)²) | Features contínuas, baixas dimensões |
| Manhattan | `Σ | pᵢ-qᵢ |
| Minkowski | `(Σ | pᵢ-qᵢ |
Você pode alterar a métrica no scikit-learn com o parâmetro metric:
from sklearn.neighbors import KNeighborsClassifier
knn = KNeighborsClassifier(n_neighbors=5, metric='manhattan')Por Que o Escalonamento de Features É Crítico
O KNN calcula distâncias brutas. Uma feature medida em milhares (como salário) dominará completamente uma feature medida em dígitos simples (como anos de experiência), mesmo que a feature menor seja mais informativa.
Sempre escalone as features antes de usar o KNN. Veja o capítulo de escalonamento de features para uma explicação completa; a versão resumida é:
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train) # fit on training data only
X_test_scaled = scaler.transform(X_test) # apply same transform to test dataNunca chame fit_transform no conjunto de teste — isso vazaria as estatísticas do conjunto de teste para o scaler.
Escolhendo K
K controla o trade-off entre viés e variância:
- K pequeno (por exemplo, K=1) — muito flexível, se ajusta firmemente aos dados de treinamento, mas ruidoso e propenso a overfitting. Um único vizinho outlier pode alterar a predição.
- K grande — fronteira de decisão mais suave, menor variância, mas pode causar underfitting e borrar fronteiras reais entre classes.
A abordagem padrão é experimentar uma faixa de valores de K e escolher o que tiver a melhor acurácia validada cruzadamente:
import numpy as np
from sklearn.neighbors import KNeighborsClassifier
from sklearn.model_selection import cross_val_score
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler
iris = load_iris()
X, y = iris.data, iris.target
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
k_range = range(1, 31)
cv_scores = []
for k in k_range:
knn = KNeighborsClassifier(n_neighbors=k)
scores = cross_val_score(knn, X_scaled, y, cv=5, scoring='accuracy')
cv_scores.append(scores.mean())
best_k = k_range[np.argmax(cv_scores)]
print(f"Best K: {best_k}, CV Accuracy: {max(cv_scores):.4f}")Saída esperada:
Best K: 6, CV Accuracy: 0.9667Para mais detalhes sobre validação cruzada, veja o capítulo de validação cruzada.
Regras práticas:
- Prefira
Kímpar para classificação binária para evitar empates. - Um ponto de partida comum é
K = sqrt(n), ondené o número de amostras de treinamento. - Sempre valide com validação cruzada em vez de adivinhar.
Classificação com KNN e scikit-learn
O exemplo a seguir usa o conjunto de dados Iris — um problema multiclasse real — e percorre o fluxo de trabalho completo: divisão, escalonamento, treinamento, predição e avaliação.
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import accuracy_score, classification_report
# 1. Load a real dataset
iris = load_iris()
X, y = iris.data, iris.target
# 2. Split into training and test sets
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 3. Scale features — critical for KNN
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 4. Train the classifier
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X_train_scaled, y_train)
# 5. Predict and evaluate
y_pred = knn.predict(X_test_scaled)
print(f"Accuracy: {accuracy_score(y_test, y_pred):.2f}")
print()
print(classification_report(y_test, y_pred, target_names=iris.target_names))Saída esperada:
Accuracy: 0.93
precision recall f1-score support
setosa 1.00 1.00 1.00 10
versicolor 0.83 1.00 0.91 10
virginica 1.00 0.80 0.89 10
accuracy 0.93 30
macro avg 0.94 0.93 0.93 30
weighted avg 0.94 0.93 0.93 30O KNN com K=5 alcança 93% de acurácia neste conjunto de teste de 30 amostras. Setosa é classificada perfeitamente porque é linearmente separável das outras duas; versicolor e virginica se sobrepõem um pouco, causando algumas classificações incorretas.
Observe o argumento stratify=y em train_test_split — ele preserva as proporções de classe em cada divisão, o que é especialmente importante para conjuntos de dados desbalanceados. Veja train/test split para mais detalhes.
Avaliação com Matriz de Confusão
Uma matriz de confusão mostra exatamente quais classes o modelo confunde entre si:
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.metrics import confusion_matrix
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X_train_s, y_train)
y_pred = knn.predict(X_test_s)
cm = confusion_matrix(y_test, y_pred)
print(cm)Saída esperada:
[[10 0 0]
[ 0 10 0]
[ 0 2 8]]Cada linha é uma classe verdadeira; cada coluna é uma classe prevista. Os valores na diagonal são predições corretas; os valores fora da diagonal são classificações incorretas. Aqui, 2 amostras de virginica foram classificadas incorretamente como versicolor. Veja o capítulo de matriz de confusão para uma explicação mais aprofundada.
Regressão com KNN e scikit-learn
Para regressão, o KNN prevê a média dos valores alvo dos K vizinhos mais próximos. Substitua KNeighborsClassifier por KNeighborsRegressor:
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsRegressor
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np
# Load a real regression dataset (a subset for speed)
housing = fetch_california_housing()
X, y = housing.data[:2000], housing.target[:2000]
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
knn_reg = KNeighborsRegressor(n_neighbors=5)
knn_reg.fit(X_train_s, y_train)
y_pred = knn_reg.predict(X_test_s)
rmse = np.sqrt(mean_squared_error(y_test, y_pred))
r2 = r2_score(y_test, y_pred)
print(f"RMSE: {rmse:.4f}")
print(f"R²: {r2:.4f}")Saída esperada:
RMSE: 0.4217
R²: 0.8053O RMSE está nas mesmas unidades que o alvo (valor mediano das casas em $100k). Um R² de 0,81 significa que o modelo explica cerca de 81% da variância neste subconjunto de 2.000 amostras — um resultado sólido para uma linha de base KNN sem ajuste.
KNN Ponderado
Por padrão, todos os K vizinhos têm peso igual, independentemente de quão próximos estejam. Definir weights='distance' faz com que vizinhos mais próximos tenham mais influência:
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
iris = load_iris()
X, y = iris.data, iris.target
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
knn_uniform = KNeighborsClassifier(n_neighbors=5, weights='uniform')
knn_distance = KNeighborsClassifier(n_neighbors=5, weights='distance')
knn_uniform.fit(X_train_s, y_train)
knn_distance.fit(X_train_s, y_train)
print(f"Uniform weights accuracy: {accuracy_score(y_test, knn_uniform.predict(X_test_s)):.2f}")
print(f"Distance weights accuracy: {accuracy_score(y_test, knn_distance.predict(X_test_s)):.2f}")Saída esperada:
Uniform weights accuracy: 0.93
Distance weights accuracy: 0.97A ponderação por distância melhora a acurácia aqui de 0,93 para 0,97 — vizinhos mais próximos exercem mais influência, o que ajuda a resolver casos ambíguos na fronteira.
Pontos Fortes e Limitações
Quando usar KNN
- O conjunto de dados é de pequeno a médio porte (dezenas de milhares de amostras).
- Você precisa de uma linha de base rápida e interpretável — KNN é fácil de explicar e depurar.
- Você tem features bem escalonadas e de baixa dimensionalidade.
- A fronteira de decisão é complexa e não linear.
Quando evitar KNN
- Conjuntos de dados grandes. O tempo de predição escala com o número de amostras de treinamento (
O(n·d)por consulta). Para milhões de amostras, considere bibliotecas de vizinhos mais próximos aproximados (Faiss, Annoy) ou mude para um algoritmo mais rápido. - Dados de alta dimensionalidade. Em muitas dimensões, todos os pontos tornam-se aproximadamente equidistantes — a "maldição da dimensionalidade". O KNN degrada rapidamente além de ~20 features. Aplique redução de dimensionalidade primeiro (PCA, seleção de features).
- Features irrelevantes. Cada feature participa no cálculo da distância. Features ruidosas ou irrelevantes diluem o sinal. Remova-as ou reduza-as antes do treinamento.
- Ambientes com restrição de memória. O KNN armazena todo o conjunto de treinamento; um conjunto de dados com milhões de linhas ocupa RAM significativa.
Resumo
| Propriedade | Detalhe |
|---|---|
| Tipo | Aprendiz baseado em instâncias (lazy) |
| Tarefas | Classificação, Regressão |
| Hiperparâmetro principal | K (número de vizinhos) |
| Métrica padrão | Distância Euclidiana |
| Pré-processamento necessário | Escalonamento de features (sempre) |
| Pontos fortes | Simples, sem fase de treinamento, não paramétrico |
| Pontos fracos | Predição lenta, consome muita memória, sensível a features irrelevantes |
Capítulos relacionados:
- Escalonamento de features — por que e como escalonar antes do KNN
- Train/test split — como dividir os dados corretamente
- Validação cruzada — escolhendo K com k-fold CV
- Matriz de confusão — interpretando resultados de classificação
- Grid search — ajuste sistemático de hiperparâmetros