Quiz 2

k-Nearest Neighbors (k-NN)

1948 words
10 min read
Python Week 1: the first filter for runtime behavior
Visual companion
Python
Type and operator map

Python Week 1: the first filter for runtime behavior

View
Revision summary

What this note is really saying

Short form

# 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 - Understa...

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 mm training examples {(x(i),y(i))}i=1m\{(x^{(i)}, y^{(i)})\}_{i=1}^m. Prediction for a new point xx:
  1. Compute distance between xx and every training example
  2. Select the kk training examples with smallest distance
  3. Classification (majority vote):
y^=mode{y(1),y(2),,y(k)}\hat{y} = \text{mode}\{y^{(1)}, y^{(2)}, \dots, y^{(k)}\}
Where y(i)y^{(i)} are the labels of the kk nearest neighbors. 4. Regression (average):
y^=1ki=1ky(i)\hat{y} = \frac{1}{k} \sum_{i=1}^{k} y^{(i)}

5.3 Distance Metrics

Euclidean Distance (L2) — most common:
d(x,x)=j=1n(xjxj)2d(x, x') = \sqrt{\sum_{j=1}^{n} (x_j - x'_j)^2}
Manhattan Distance (L1):
d(x,x)=j=1nxjxjd(x, x') = \sum_{j=1}^{n} |x_j - x'_j|
Minkowski Distance (generalization):
d(x,x)=(j=1nxjxjp)1/pd(x, x') = \left(\sum_{j=1}^{n} |x_j - x'_j|^p\right)^{1/p}
  • p=1p = 1: Manhattan distance
  • p=2p = 2: Euclidean distance
  • pp \to \infty: Chebyshev distance
MetricGeometryBest ForSensitive To
EuclideanStraight lineContinuous featuresFeature scale, outliers
ManhattanCity-block gridHigh dimensions, sparseFeature scale
CosineAngle between vectorsText data, high-dimOnly direction, not magnitude

5.4 Worked Example 1: Hand Calculation

Classify a new point (x=3,y=4)(x=3, y=4) with k=3 using the dataset:
PointxyLabel
A12Red
B23Red
C35Blue
D51Red
E64Blue
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:
  1. C (1.00) — Blue
  2. B (1.41) — Red
  3. 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:
  1. Feature selection (remove irrelevant features)
  2. Dimensionality reduction (PCA before k-NN)
  3. Use Manhattan distance (more robust in high dimensions)
  4. Increase k (smooths over the distance problem)

5.7 Weighted k-NN

Instead of giving all neighbors equal votes, we weight by inverse distance:
y^=argmaxci=1kwi1(y(i)=c)\hat{y} = \text{argmax}_c \sum_{i=1}^{k} w_i \cdot \mathbb{1}(y^{(i)} = c)
Where wi=1/d(x,x(i))w_i = 1/d(x, x^{(i)}) or wi=1/(d(x,x(i))2)w_i = 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 UseWhen NOT to Use
Decision boundary is irregularVery large datasets (slow prediction)
Low-dimensional data (n < 20)High-dimensional data (curse of dimensionality)
Need a non-parametric modelMemory-constrained environments
Quick baseline without assumptionsFeatures are mostly irrelevant
Online learning (new data arrives)Need fast prediction time

📐 Key Formulas / Concepts

ConceptFormulaNotes
Euclidean Distance(xjxj)2\sqrt{\sum(x_j - x'_j)^2}Default metric
Manhattan Distance$\sumx_j - x'_j
k-NN Classificationy^=mode of k neighbors\hat{y} = \text{mode of k neighbors}Majority vote
k-NN Regressiony^=mean of k neighbors\hat{y} = \text{mean of k neighbors}Average target
Weighted k-NNy^=argmaxwi1(yi=c)\hat{y} = \text{argmax}\sum w_i \cdot \mathbb{1}(y_i=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=31+42=2+2=4d_{manhattan} = |3-1| + |4-2| = 2 + 2 = 4
Euclidean 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.
python
from 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](/courses/bscs2004/notes/1000%2C%202%2C%205%5D%2C%20%5B1500%2C%203%2C%207%5D%2C%20%5B2000%2C%203%2C%208%5D%2C%20%5B2500%2C%204%2C%209))
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:
  1. Increase k — smoother decision boundary
  2. Use weighted voting — reduce influence of far neighbors
  3. Add more training data — helps all models
  4. 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.
Aspectk-NNLogistic Regression
AssumptionsNone (non-parametric)Linear decision boundary
Decision boundaryAny shape (flexible)Linear
Training speedInstant (memorize)Fast (gradient descent)
Prediction speedSlow (compute distances)Instant (matrix mult)
InterpretabilityLow (black box)High (coefficients)
Handles non-linearityNaturallyNeeds feature engineering
Needs scalingCriticalImportant
Small datasetsGoodGood
Choose k-NN for non-linear, low-dimensional data. Choose Logistic Regression for interpretability or high-dimensional data.

🔗 Cross-References

Document outline

Keep your place and jump directly to a heading.

Table of Contents
System Normal // Awaiting Context

Intelligence Hub

Navigate the knowledge graph to generate context. The Hub adapts dynamically to surface backlinks, related notes, and metadata insights.