You move to a new neighborhood. You want to know: what kind of area is this?
You don't read the city report. You don't analyze census data.
You look at the five houses nearest to you.
Three are families with children. One is a young couple. One is a retired person.
You conclude: this is a family neighborhood. Probably safe. Probably quiet.
You just ran K-Nearest Neighbors with K = 5.
01 — The Laziest Model in Machine Learning
KNN has a radical design philosophy: do absolutely nothing during training. No weights to learn, no equations to fit, no optimization loop. Just take the entire training dataset and store it in memory. Every other algorithm we've seen — linear regression, logistic regression — spends training time compressing the data into a small set of learned parameters. KNN refuses to compress anything. It keeps everything.
All the real work happens at prediction time. When a new data point arrives asking for a prediction, KNN searches through every stored training point, finds the K closest ones, and asks them to vote:
— Classification: the K neighbors vote. The most common class among them wins.
— Regression: take the average of the K neighbors' target values.
This is why KNN is called a lazy learner or instance-based learner — it defers all computation to prediction time, and the training data itself is the model. There are no parameters. There is no "learned function." Just data, and the assumption that similar inputs produce similar outputs.
A doctor with 30 years of experience sees a new patient. Rather than applying a rigid rulebook, she mentally searches her memory: "I've seen patients with these exact symptoms before. Most of them had condition X." Her memory of past cases is her model. Their similarity to the current patient guides her prediction. KNN reasons the same way — by analogy, not by rules.
02 — Distance: The Heart of KNN
Everything in KNN depends on one question: how do you define "closeness" between two data points? The answer is a distance metric — a mathematical formula that takes two points and returns a number. Smaller number = more similar.
Let's make this concrete. Suppose you have two people described by two features: Age and Income (in thousands).
Person A: (30, 50) · Person B: (34, 53)
d = √( (x₂−x₁)² + (y₂−y₁)² )Example: √( (34−30)² + (53−50)² ) = √( 16 + 9 ) = √25 = 5.0
Think of it as the length of the straight line connecting two dots on graph paper. Best for continuous, geometrically meaningful features where the shortest path between two points is a straight line.
d = |x₂−x₁| + |y₂−y₁|Example: |34−30| + |53−50| = 4 + 3 = 7.0
Think of it as navigating a city grid — you can only travel along streets, not diagonally through buildings. Better when features are independent and diagonal movement isn't meaningful (e.g. pixel coordinates, checklist features).
similarity = (A·B) / (|A| × |B|)Measures the angle between two vectors, ignoring their length. Score of 1 = same direction (identical). Score of 0 = perpendicular (unrelated). Score of −1 = opposite directions.
When to use it: text and embedding spaces, where magnitude carries no useful information. A short tweet and a long article about the same topic should be "similar" regardless of word count. Euclidean distance would call them far apart. Cosine similarity correctly calls them close.
d = ( Σ|xᵢ−yᵢ|ᵖ )^(1/p)A single formula with a tunable exponent p. When p=1 it becomes Manhattan. When p=2 it becomes Euclidean. Larger p gives more weight to the biggest difference between any two dimensions. Useful when you want to tune how much outlier dimensions are penalized.
KNN computes distances across all features simultaneously. If one feature has a large numerical range and another has a small one, the large-range feature will completely dominate every distance calculation — the other feature might as well not exist.
Concrete example: predicting house prices with two features — number of rooms (range: 1–10) and square footage (range: 500–5000). Without scaling:
House A: (3 rooms, 800 m²) · House B: (3 rooms, 810 m²) · House C: (8 rooms, 801 m²)
Euclidean distance A→B: √(0² + 10²) = 10.0
Euclidean distance A→C: √(5² + 1²) = 5.1
KNN thinks C is closer to A than B is — because the 5-room difference (5 units) looks smaller than the 10 m² difference (10 units). This is backwards. Apply StandardScaler first and the problem disappears: every feature gets mean=0 and std=1, so all dimensions contribute equally to distance.
03 — Choosing K: The Bias-Variance Dial
K is the only hyperparameter KNN has, and it controls the fundamental tradeoff between overfitting and underfitting. Think of K as a dial that adjusts how much you trust individual neighbors vs the crowd.
Imagine asking for a restaurant recommendation from one random passerby — you might get great advice, or you might get their single worst experience.
Imagine asking every person in a stadium for a restaurant recommendation — the result is always "McDonald's" because it's the most popular, no matter what cuisine you want.
04 — From Scratch: KNN in Pure Python
KNN is one of the easiest algorithms to implement from scratch — no calculus, no matrices, just loops and distance formulas. Reading this makes the sklearn version completely transparent.
import numpy as np
from collections import Counter
def euclidean(a, b):
"""Straight-line distance between two points."""
return np.sqrt(np.sum((a - b) ** 2))
def knn_predict(X_train, y_train, x_new, k=3):
"""Classify x_new using the k nearest neighbors in X_train."""
# Step 1: compute distance from x_new to every training point
distances = [euclidean(x_new, x_train) for x_train in X_train]
# Step 2: get the indices of the k smallest distances
k_indices = np.argsort(distances)[:k]
# Step 3: look up the labels of those k neighbors
k_labels = [y_train[i] for i in k_indices]
# Step 4: majority vote — return the most common label
most_common = Counter(k_labels).most_common(1)
return most_common[0][0]
# Simple 2D dataset: two clusters
# Class 0 = bottom-left region
# Class 1 = top-right region
X_train = np.array([[1,2],[2,3],[3,1],[6,5],[7,8],[8,6]])
y_train = np.array([0, 0, 0, 1, 1, 1])
x_new = np.array([5, 4]) # unknown point — which cluster?
prediction = knn_predict(X_train, y_train, x_new, k=3)
print(f"Nearest 3 neighbors vote: class {prediction}")
# Show the distances explicitly
distances = [(i, euclidean(x_new, X_train[i]), y_train[i])
for i in range(len(X_train))]
distances.sort(key=lambda d: d[1])
print("\nAll training points sorted by distance from (5,4):")
for idx, dist, label in distances:
print(f" Point {X_train[idx]} | dist={dist:.2f} | class={label}")
The new point (5,4) is closest to [6,5] (class 1), then [2,3] (class 0), then [7,8] (class 1). With K=3, the vote is 2 class-1 vs 1 class-0 → prediction: class 1. You can see exactly how every distance was computed. There's no mystery here — just arithmetic.
05 — KNN in sklearn (Real Dataset)
The sklearn version handles scaling, cross-validation, and K selection cleanly. This is the production pattern.
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split, cross_val_score
from sklearn.preprocessing import StandardScaler
import numpy as np
# Iris dataset: 150 flowers, 4 features, 3 species
X, y = load_iris(return_X_y=True)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42)
# ── Scale first — mandatory for KNN ─────────────────────────────────
# fit_transform on train: learn mean/std from training data only
# transform on test: apply the SAME scaling (never refit on test!)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_test = scaler.transform(X_test)
# ── Find best K via 5-fold cross-validation ──────────────────────────
cv_scores = [cross_val_score(KNeighborsClassifier(k), X_train, y_train, cv=5).mean()
for k in range(1, 21)]
best_k = np.argmax(cv_scores) + 1
print(f"Best K = {best_k}")
print(f"CV accuracy at best K = {cv_scores[best_k-1]:.2%}")
# ── Final evaluation on held-out test set ───────────────────────────
model = KNeighborsClassifier(best_k).fit(X_train, y_train)
print(f"Test accuracy = {model.score(X_test, y_test):.2%}")
When you call fit_transform, the scaler learns the mean and standard deviation from that data, then applies the scaling. If you were to refit on the test set, the test data would be scaled using its own statistics — which is a form of data leakage. The test set must be scaled with the exact same mean and std learned from the training set. This simulates what happens in production: the scaler never sees future data.
06 — The Curse of Dimensionality
KNN's single biggest weakness is high-dimensional data. As the number of features grows, something counterintuitive and devastating happens to the concept of distance.
In 1 dimension: imagine a line of length 1. Place two random points on it. On average they're about 0.33 apart — relatively close.
In 2 dimensions: a unit square. Two random points average ~0.52 apart. A bit farther.
In 100 dimensions: a unit hypercube. Two random points are now about 4.1 apart — and crucially, almost all pairs of random points are approximately the same distance from each other.
When every point is roughly equidistant from every other point, the concept of "nearest neighbor" becomes meaningless. Your closest neighbor is almost as far away as your farthest one. The algorithm can no longer discriminate.
Practically: KNN works well with 2–50 features. Above 100 features, performance degrades noticeably. Above 500, it often becomes useless without intervention.
FAISS, Pinecone, Chroma, and Weaviate — the vector databases powering production RAG systems — are all implementations of approximate K-Nearest Neighbor search in high-dimensional embedding space.
Here's what that means concretely: when a language model reads a document, it converts it into a list of 768 or 1536 numbers — a vector embedding that encodes the document's meaning as a point in high-dimensional space. Documents about similar topics land near each other in that space. When you ask a RAG system a question, it converts your question into an embedding vector, then finds the K training documents whose embeddings are nearest — the K most semantically similar chunks.
That retrieval step is KNN. The vector database is a highly optimized, approximate KNN index. KNN isn't old technology — it's the retrieval backbone of every production LLM-powered application.
07 — When KNN Wins (and When It Loses)
You have few, well-scaled features (2–50)
The decision boundary is complex and non-linear
You need a quick, interpretable baseline with no training time
The data has clear local structure (clusters)
You have many features (100+) — curse of dimensionality
Features are on very different scales and you can't preprocess
You need fast real-time predictions — KNN must scan the entire dataset
You have many irrelevant features — they poison the distance calculation
08 — A Thought to Sit With
KNN assumes that similar inputs produce similar outputs. But "similar" depends entirely on the space you measure distance in. Two photos of the same person in different lighting conditions are very "distant" in raw pixel space — millions of pixels differ. But in a learned embedding space (like a face recognition model produces), they are "close" — they represent the same identity.
What does this tell you about where the real intelligence lives — in the algorithm, or in the representation it operates on? And what does that imply about what "learning" really means?
09 — Coming Up Next
Tomorrow — Day 7: Naive Bayes. A probabilistic model that classifies spam before you even open it. It makes an absurd mathematical assumption — that all features are completely independent of each other — and somehow works brilliantly anyway. Understanding why it works despite being "wrong" teaches something deep about the relationship between model assumptions and real-world performance.
10 — Go Deeper
▶ StatQuest: K-Nearest Neighbors, Clearly Explained
YouTube · 9 min · The clearest short introduction to KNN — great for cementing the intuition after reading this post.
▶ Kaggle: Intro to Machine Learning
Free interactive course · Apply KNN and other classifiers to real datasets in your browser. The fastest way to build hands-on intuition.
▶ Coursera: Deep Learning Specialization — Andrew Ng
5-course specialization · Covers embeddings and vector representations that make KNN powerful at scale — the bridge from this post to modern AI retrieval systems.
- Lazy learner — No training. Stores all data. All computation happens at prediction time.
- Prediction — Find the K nearest training points. Vote (classification) or average (regression).
- Distance metrics — Euclidean (straight line), Manhattan (city block), Cosine (angle, ignores magnitude), Minkowski (generalizes both).
- Feature scaling is mandatory — Large-range features dominate distance. Apply StandardScaler before KNN, always.
- Choosing K — K=1 overfits. K=N underfits. Use cross-validation on odd values 1–20 to find the best K.
- Complexity — Training: O(1). Prediction: O(N·D) per query — slow at scale.
- Curse of dimensionality — Above ~100 features, all points become equidistant. Apply PCA/UMAP or feature selection first.
- Cosine similarity — Use when magnitude is noise and direction is signal: text, embeddings, recommendation systems.
- In modern AI — Vector databases (FAISS, Pinecone, Chroma) are optimized approximate KNN indices. RAG retrieval is KNN on embeddings.
- Best for — Small datasets, low–medium dimensions, complex non-linear boundaries, quick interpretable baselines.
- Why must you scale features before using KNN?
KNN relies purely on distance. Features with larger numerical ranges dominate the calculation — the model effectively ignores smaller-range features. StandardScaler normalizes every feature to mean=0 and std=1, ensuring equal contribution to every distance. - What is the curse of dimensionality and how does it affect KNN?
In high dimensions, all data points become approximately equidistant. The ratio of nearest-to-farthest neighbor distance approaches 1, making "nearest neighbor" meaningless. KNN degrades sharply above ~100 features. Solutions: PCA/UMAP dimensionality reduction or feature selection before applying KNN. - What is the effect of K=1 vs K=N?
K=1: perfect memorization, 0% training error, maximum overfitting — every outlier creates its own prediction region. K=N: always predicts the majority class, completely ignores input — maximum underfitting. The right K balances these extremes; find it with cross-validation. - When would you choose cosine similarity over Euclidean distance?
When vector magnitude carries no useful information but direction does — text, embeddings, recommendation systems. Two documents about the same topic but different lengths have similar direction (topic distribution) but very different magnitudes (word counts). Cosine similarity correctly identifies them as close; Euclidean distance would not. - What are the time complexity tradeoffs of KNN?
Training: O(1) — just store data. Prediction: O(N·D) per query where N is training size and D is dimensions — must scan all training examples. This makes KNN fast to train but slow at prediction scale. Approximate methods (FAISS, Annoy, HNSW) reduce prediction cost to O(log N) at the cost of some accuracy.
No attachments for this topic yet.