Neural Sync Active
k-Nearest Neighbors (k-NN)
Registry Synced
k-Nearest Neighbors (k-NN)
1948 words
10 min read
Reading compass
Now · 🎯 Learning Objectives
k-Nearest Neighbors (k-NN)
🎯 Learning Objectives
- Explain the k-NN algorithm in plain English and why it's called "lazy learning"
- Compute distances using Euclidean, Manhattan, and Minkowski metrics
- Determine the optimal k using cross-validation
- Implement k-NN for classification and regression
- Understand the curse of dimensionality and how to mitigate it
📋 Prerequisites
- Basic geometry — distance between points in n-dimensional space
- Probability — voting/majority rule concepts
- Classification basics — what it means to predict a class
📖 Core Content
5.1 Intuition: "Birds of a Feather Flock Together"
Imagine moving to a new neighborhood. You want to know if it's a safe area. If the three nearest houses have all been burgled in the past year, you'd be worried. If all three nearest houses have had zero incidents, you'd feel safe. k-NN works exactly the same way — it looks at the k closest training examples to a new point and predicts based on their labels.
(Diagram)
This is called "instance-based" or "lazy" learning because the model doesn't actually learn anything during training — it just memorizes all training data. All computation happens at prediction time.
5.2 Formal Definition
Training: Store all m training examples {(x(i),y(i))}i=1m.
Prediction for a new point x:
- Compute distance between x and every training example
- Select the k training examples with smallest distance
- Classification (majority vote):
Where y(i) are the labels of the k nearest neighbors. 4. Regression (average):
5.3 Distance Metrics
Euclidean Distance (L2) — most common:
Manhattan Distance (L1):
Minkowski Distance (generalization):
- p=1: Manhattan distance
- p=2: Euclidean distance
- p→∞: Chebyshev distance
| Metric | Geometry | Best For | Sensitive To |
|---|---|---|---|
| Euclidean | Straight line | Continuous features | Feature scale, outliers |
| Manhattan | City-block grid | High dimensions, sparse | Feature scale |
| Cosine | Angle between vectors | Text data, high-dim | Only direction, not magnitude |
5.4 Worked Example 1: Hand Calculation
Classify a new point (x=3,y=4) with k=3 using the dataset:
| Point | x | y | Label |
|---|---|---|---|
| A | 1 | 2 | Red |
| B | 2 | 3 | Red |
| C | 3 | 5 | Blue |
| D | 5 | 1 | Red |
| E | 6 | 4 | Blue |
Step 1: Compute Euclidean distances from (3,4):
- d(A) = √[(3-1)² + (4-2)²] = √(4 + 4) = √8 = 2.83
- d(B) = √[(3-2)² + (4-3)²] = √(1 + 1) = √2 = 1.41
- d(C) = √[(3-3)² + (4-5)²] = √(0 + 1) = 1.00
- d(D) = √[(3-5)² + (4-1)²] = √(4 + 9) = √13 = 3.61
- d(E) = √[(3-6)² + (4-4)²] = √(9 + 0) = 3.00 Step 2: Sort by distance:
- C (1.00) — Blue
- B (1.41) — Red
- A (2.83) — Red Step 3: Majority vote: Red (2) > Blue (1). Predict: Red. Step 4: What if k=5? Votes: Red (A,B,D) = 3, Blue (C,E) = 2. Still predicts Red.
5.5 Choosing k
(Diagram)
Rule of thumb: k = √N where N is the number of training examples. Then tune using cross-validation.
python# runnable from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import cross_val_score import numpy as np import matplotlib.pyplot as plt # Generate sample data np.random.seed(42) X = np.random.randn(100, 2) y = (X[:, 0]**2 + X[:, 1]**2 > 1).astype(int) # Find optimal k using cross-validation k_range = range(1, 30) cv_scores = [] for k in k_range: knn = KNeighborsClassifier(n_neighbors=k) scores = cross_val_score(knn, X, y, cv=5, scoring='accuracy') cv_scores.append(scores.mean()) optimal_k = k_range[np.argmax(cv_scores)] print(f"Optimal k: {optimal_k}") plt.plot(k_range, cv_scores, marker='o') plt.xlabel('k') plt.ylabel('Cross-validation accuracy') plt.title('Optimal k Selection') plt.grid(True) plt.show()
5.6 The Curse of Dimensionality
As the number of features increases, Euclidean distance becomes a poor measure — all points become equally far apart. With just 10 features of random data, the distance to the nearest neighbor is almost the same as to the farthest neighbor.
python# runnable import numpy as np for dim in [1, 2, 5, 10, 50, 100]: X = np.random.randn(1000, dim) # Distance from first point to all others dists = np.linalg.norm(X - X[0], axis=1) nearest = dists[dists > 0].min() farthest = dists.max() ratio = nearest / farthest print(f"Dim {dim:3d}: nearest={nearest:.3f}, farthest={farthest:.3f}, ratio={ratio:.3f}")
Mitigations:
- Feature selection (remove irrelevant features)
- Dimensionality reduction (PCA before k-NN)
- Use Manhattan distance (more robust in high dimensions)
- Increase k (smooths over the distance problem)
5.7 Weighted k-NN
Instead of giving all neighbors equal votes, we weight by inverse distance:
Where wi=1/d(x,x(i)) or wi=1/(d(x,x(i))2).
This way, closer neighbors have more influence — the 1st neighbor matters more than the 5th.
5.8 Python Implementation
python# runnable from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklearn.preprocessing import StandardScaler # Load data iris = load_iris() X, y = iris.data, iris.target # IMPORTANT: Scale features for k-NN! scaler = StandardScaler() X_scaled = scaler.fit_transform(X) # Split X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.3, random_state=42) # Train k-NN knn = KNeighborsClassifier(n_neighbors=5, weights='distance', metric='euclidean') knn.fit(X_train, y_train) # Evaluate accuracy = knn.score(X_test, y_test) print(f"Accuracy: {accuracy:.3f}") # Predict a new point new_point = scaler.transform(5.0, 3.5, 1.5, 0.3) pred = knn.predict(new_point) probs = knn.predict_proba(new_point) print(f"Predicted class: {pred[0]} ({iris.target_names[pred[0]]})") print(f"Probabilities: {probs[0]}")
5.9 When to Use / Not Use
| ✅ When to Use | ❌ When NOT to Use |
|---|---|
| Decision boundary is irregular | Very large datasets (slow prediction) |
| Low-dimensional data (n < 20) | High-dimensional data (curse of dimensionality) |
| Need a non-parametric model | Memory-constrained environments |
| Quick baseline without assumptions | Features are mostly irrelevant |
| Online learning (new data arrives) | Need fast prediction time |
📐 Key Formulas / Concepts
| Concept | Formula | Notes |
|---|---|---|
| Euclidean Distance | ∑(xj−xj′)2 | Default metric |
| Manhattan Distance | $\sum | x_j - x'_j |
| k-NN Classification | y^=mode of k neighbors | Majority vote |
| k-NN Regression | y^=mean of k neighbors | Average target |
| Weighted k-NN | y^=argmax∑wi⋅1(yi=c) | Distance-weighted voting |
⚠️ Common Pitfalls
Pitfall 1: Not Scaling Features
The mistake: Using raw features when one feature has range [0.1, 0.2] and another [1, 1000].
Why: The second feature dominates distance calculations. k-NN is fundamentally distance-based, so scale inconsistency kills performance.
Fix: Standardize ALL features before using k-NN. Fit scaler on training data only.
Pitfall 2: Choosing k = 1 or k = N
The mistake: Using extreme values of k.
Why: k=1 perfectly interpolates training data but generalizes poorly (high variance). k=N predicts the majority class for everything (high bias).
Fix: Use cross-validation to select k. Typically k between 3 and 20 works well.
Pitfall 3: Ignoring the Curse of Dimensionality
The mistake: Using k-NN with 100+ features.
Why: In high dimensions, all points are approximately equidistant. The nearest neighbor is almost as far as the farthest. k-NN effectively becomes random.
Fix: Reduce dimensionality first (PCA, feature selection) or use a different algorithm.
📝 Practice Questions
Q1: Compute Manhattan distance between (3,4) and (1,2).dmanhattan=∣3−1∣+∣4−2∣=2+2=4Euclidean would be √8 ≈ 2.83. Manhattan is always ≥ Euclidean. Q2: For points A(0,0,Red), B(1,1,Red), C(2,2,Blue), what does k=3 predict for (1.5,1.5)?Distances from (1.5,1.5):
- d(A) = √[(1.5-0)² + (1.5-0)²] = √(2.25+2.25) = √4.5 = 2.12
- d(B) = √[(1.5-1)² + (1.5-1)²] = √(0.25+0.25) = √0.5 = 0.71
- d(C) = √[(1.5-2)² + (1.5-2)²] = √(0.25+0.25) = √0.5 = 0.71
k=3: Red (A=2.12, B=0.71) = 2 votes, Blue (C=0.71) = 1 vote. Predict: Red.With weighted voting, B and C have equal weight (both 0.71), so B (Red) and C (Blue) tie, and A (Red) breaks the tie. Still Red. Q3: What happens to k-NN if all features are on different scales?Features with larger magnitudes dominate the distance calculation. If one feature ranges 0-1 and another ranges 0-1000, the second feature effectively determines the neighbors. This makes the first feature irrelevant. Always scale features to equalize their influence. Q4: Why is k-NN called "lazy learning"?Because it defers all computation to prediction time. During training, it simply memorizes the dataset (no model building). During prediction, it computes distances to ALL training points and finds the k nearest. This is the opposite of "eager" learning (like neural networks) where training is expensive but prediction is cheap. Q5: With 1000 training examples and 20 features, estimate prediction time complexity.For each prediction: O(m × n) = O(1000 × 20) = O(20,000) distance computations. With efficient data structures (KD-tree, Ball tree), this can reduce to O(log m × n) in low dimensions. But in 20 dimensions, tree-based structures degrade. Q6: What's the best k if the data is very noisy?Larger k — averaging over more neighbors smooths out noise. The tradeoff is that the decision boundary becomes less flexible. Use cross-validation to find the optimal balance between noise reduction and underfitting. Q7: How does weighted k-NN differ from standard k-NN?Standard k-NN gives each neighbor one vote regardless of distance. Weighted k-NN gives closer neighbors more votes (weight = 1/distance). This makes the algorithm more robust — distant neighbors have minimal influence. It also eliminates ties more effectively. Q8: Implement k-NN regression for house price prediction.pythonfrom sklearn.neighbors import KNeighborsRegressor from sklearn.preprocessing import StandardScaler import numpy as np # Sample: sq_ft, bedrooms, location_score → price X = np.array([1000, 2, 5], [1500, 3, 7], [2000, 3, 8], [2500, 4, 9](/viewer?path=1000, 2, 5], [1500, 3, 7], [2000, 3, 8], [2500, 4, 9)) y = np.array([200000, 300000, 350000, 450000]) scaler = StandardScaler() X_scaled = scaler.fit_transform(X) knn = KNeighborsRegressor(n_neighbors=2, weights='distance') knn.fit(X_scaled, y) # Predict for a new house new = scaler.transform(1800, 3, 7.5) pred = knn.predict(new) print(f"Predicted price: ${pred[0]:.0f}")Q9: In 2D, draw the decision boundary for k=1 vs k=5 on the same data. What's different?k=1: Decision boundary is very jagged — every training point has its own "territory" (Voronoi diagram). The boundary passes exactly between every pair of points with different labels.k=5: Decision boundary is smooth and wavy — it ignores local noise and follows the broader pattern. Small islands of one class inside another disappear.More neighbors = smoother boundary = higher bias = lower variance. Q10: Your k-NN model has high training accuracy but low test accuracy. What do you do?This is overfitting — the model has memorized noise. Solutions:
- Increase k — smoother decision boundary
- Use weighted voting — reduce influence of far neighbors
- Add more training data — helps all models
- Remove irrelevant features — reduces noise dimensions
Start by increasing k and checking cross-validation score. Q11: Why is cosine distance preferred for text data in k-NN?Cosine distance measures the angle between vectors, ignoring magnitude. In text data, document length varies hugely (a 100-word article vs. a 1000-word article). Cosine similarity captures the content/theme without being dominated by length. Example: two articles about "machine learning" will have high cosine similarity regardless of their lengths. Q12: Compare k-NN and Logistic Regression.
| Aspect | k-NN | Logistic Regression |
|---|---|---|
| Assumptions | None (non-parametric) | Linear decision boundary |
| Decision boundary | Any shape (flexible) | Linear |
| Training speed | Instant (memorize) | Fast (gradient descent) |
| Prediction speed | Slow (compute distances) | Instant (matrix mult) |
| Interpretability | Low (black box) | High (coefficients) |
| Handles non-linearity | Naturally | Needs feature engineering |
| Needs scaling | Critical | Important |
| Small datasets | Good | Good |
Choose k-NN for non-linear, low-dimensional data. Choose Logistic Regression for interpretability or high-dimensional data.
🔗 Cross-References
- Next Topic: Decision Trees — interpretable tree-based models
- Related: Feature Scaling — why scaling matters
- Related: Dimensionality Reduction — mitigating curse of dimensionality
- External: IITM BSCS2004 Week 5, Hands-On ML Ch. 5 (distance-based models) Join Discord PreviousLogistic RegressionNextDecision Trees