CO5 Topic 33 of 35
K-nearest neighbours
To classify something new, look at the most similar examples you have seen and take a vote. There is no training, just remembering, and the choice of k and scale matters.
You will be able to
- Explain the k-nearest neighbour algorithm
- Classify a point by hand
- Choose k using cross-validation
- Show why feature scaling matters
- Describe the curse of dimensionality
Read
- Textbook 1, §8.2 k-Nearest neighbor learning
- Textbook 2, Ch. on nearest neighbour methods
Intuition
"You are the average of your five closest friends." KNN uses this idea directly. A new point takes the most common label among its k closest training points. It has no training step and builds no model, which is why it is called lazy or instance-based learning.
How it works
to classify x:
compute the distance from x to every training example
take the k nearest
return the majority label (or, for regression, the average)
Euclidean distance:
d(a, b) = √ Σ (aᵢ − bᵢ)²
A worked example. Query (3, 3):
| Point | Label | Distance |
|---|---|---|
| (1, 1) | A | √8 = 2.83 |
| (2, 3) | A | 1.00 |
| (4, 3) | B | 1.00 |
| (5, 5) | B | √8 = 2.83 |
| (4, 4) | B | √2 = 1.41 |
For k = 1 it is a tie (A and B at 1.00). For k = 3, the nearest are (2,3) A, (4,3) B and (4,4) B, so the answer is B.
Choosing k. A small k fits noise, because one strange neighbour decides. A large k blurs the boundary. Use cross-validation, and for two classes choose an odd k to avoid ties.
Weighted KNN. Closer neighbours count for more, usually with weight 1/d².
Scaling is essential. In a distance, a feature ranging up to 100,000 swamps one ranging up to 1.
The curse of dimensionality. With many features, nearly all points are about equally far away, so "nearest" means little. Irrelevant features make it worse.
| Good | Bad |
|---|---|
| No training time | Slow prediction on big data |
| Handles complicated boundaries | Must store all the data |
| Simple to understand | Sensitive to scale and irrelevant features |
Run it: KNN from scratch, then finding k
import numpy as np
from collections import Counter
from sklearn.datasets import load_wine
from sklearn.model_selection import cross_val_score, train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
def knn(train_X, train_y, x, k):
distances = np.sqrt(((train_X - x) ** 2).sum(axis=1))
nearest = np.argsort(distances)[:k]
return Counter(train_y[nearest]).most_common(1)[0][0]
points = np.array([[1, 1], [2, 3], [4, 3], [5, 5], [4, 4]], dtype=float)
labels = np.array(["A", "A", "B", "B", "B"])
print("Query (3, 3) with k = 3:", knn(points, labels, np.array([3.0, 3.0]), 3))
X, y = load_wine(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0, stratify=y)
print("\nWhy scaling matters (k = 5)")
raw = KNeighborsClassifier(5).fit(X_train, y_train).score(X_test, y_test)
scaled = make_pipeline(StandardScaler(), KNeighborsClassifier(5)).fit(X_train, y_train).score(X_test, y_test)
print(f"Raw features: {raw:.3f}")
print(f"Scaled features: {scaled:.3f}")
print("\nChoosing k by cross-validation on the training data")
best_k, best = 1, 0
for k in [1, 3, 5, 7, 9, 15, 25, 51]:
score = cross_val_score(make_pipeline(StandardScaler(), KNeighborsClassifier(k)), X_train, y_train, cv=5).mean()
print(f"k = {k:2}: {score:.3f}")
if score > best:
best_k, best = k, score
final = make_pipeline(StandardScaler(), KNeighborsClassifier(best_k)).fit(X_train, y_train)
print(f"\nBest k = {best_k}. Test accuracy = {final.score(X_test, y_test):.3f}")
Try this: add 50 random noise columns to X using np.hstack, and run it again. Watch the curse of dimensionality and irrelevant features at work.
Common mistakes
- Forgetting to scale. The program shows the cost.
- Choosing k by test accuracy. Choose by cross-validation on training data.
- Using KNN on huge datasets without an index. Every prediction compares with everything, unless you use a KD-tree or similar.
Problems
- Using the table above, classify the query (2, 2) with k = 3.
- Calculate the distance between (1, 2, 3) and (4, 6, 3).
- Implement weighted voting with weight 1/d².
- Why does k = 1 always give 100% on the training data, if the points are distinct?
- Change the program to a regression, using
KNeighborsRegressoronload_diabetes.
Flashcards
Say the meaning to yourself, then select the card to check.
- Lazy learning
- No training step: the work is done at prediction time.
- Small k
- Fits noise.
- Large k
- Blurs boundaries towards the majority class.
- Curse of dimensionality
- With many features, distances become less meaningful.