Instance-Based Learning & k-Nearest Neighbors

Explore the mathematics of non-parametric instance-based learning, distance metric derivations in metric spaces, the geometric curse of dimensionality, and zero-dependency implementations in Python and Rust.

01. Supervised Machine Learning Engineering7 min read

Unlike parametric models that compress training examples into a fixed set of weights during an upfront training phase, Instance-Based Learning (often termed lazy learning) postpones computation until inference time.

The canonical algorithm in this family, k-Nearest Neighbors (k-NN), makes predictions by querying the local geometric neighborhood of a query point in a defined metric space.

Training ComplexityComplexity
O(1)

k-NN has zero upfront training cost; it stores raw feature vectors directly in memory.

1. Distance Metrics in Metric Spaces#

For a function D(x,z)D(x, z) to qualify as a valid metric on a set X\mathcal{X}, it must satisfy four axioms for all x,y,z∈Xx, y, z \in \mathcal{X}:

  1. Non-negativity: D(x,z)≥0D(x, z) \ge 0
  2. Identity of Indiscernibles: D(x,z)=0  ⟺  x=zD(x, z) = 0 \iff x = z
  3. Symmetry: D(x,z)=D(z,x)D(x, z) = D(z, x)
  4. Triangle Inequality: D(x,z)≤D(x,y)+D(y,z)D(x, z) \le D(x, y) + D(y, z)
Architecture Diagram
Rendering diagram...

The Minkowski Distance Family#

The Minkowski distance of order p≥1p \ge 1 generalizes standard geometric distances across coordinate spaces Rd\mathbb{R}^d:

Dp(x,z)=(∑i=1d∣xi−zi∣p) ⁣1/pD_p(x, z) = \left( \sum_{i=1}^d |x_i - z_i|^p \right)^{\!1/p}
1

Step 1: Manhattan Distance ($p = 1$)

Setting p=1p = 1 computes grid-aligned rectilinear distance, corresponding to the L1L_1 norm:

D1(x,z)=∑i=1d∣xi−zi∣D_1(x, z) = \sum_{i=1}^d |x_i - z_i|
2

Step 2: Euclidean Distance ($p = 2$)

Setting p=2p = 2 computes straight-line distance in Euclidean space, corresponding to the L2L_2 norm:

D2(x,z)=∑i=1d(xi−zi)2=(x−z)T(x−z)D_2(x, z) = \sqrt{\sum_{i=1}^d (x_i - z_i)^2} = \sqrt{(x - z)^T (x - z)}
3

Step 3: Chebyshev Distance ($p \to \infty$)

Taking the limit as p→∞p \to \infty isolates the single coordinate axis with the maximum discrepancy, corresponding to the L∞L_\infty norm:

D∞(x,z)=lim⁡p→∞(∑i=1d∣xi−zi∣p) ⁣1/p=max⁡i∈{1,…,d}∣xi−zi∣D_\infty(x, z) = \lim_{p \to \infty} \left( \sum_{i=1}^d |x_i - z_i|^p \right)^{\!1/p} = \max_{i \in \{1, \dots, d\}} |x_i - z_i|

Cosine Distance vs Angular Similarity#

In text retrieval and dense embedding vectors, directional orientation matters more than Euclidean magnitude. The Cosine Similarity measures the angle θ\theta between vectors:

CosineSimilarity(x,z)=xTz∥x∥2∥z∥2=cos⁡(θ)\text{CosineSimilarity}(x, z) = \frac{x^T z}{\|x\|_2 \|z\|_2} = \cos(\theta)

The associated Cosine Distance is:

Dcos⁡(x,z)=1−xTz∥x∥2∥z∥2D_{\cos}(x, z) = 1 - \frac{x^T z}{\|x\|_2 \|z\|_2}
Cosine Distance is a Semi-Metric
Cosine Distance violates the triangle inequality under certain acute angle arrangements. However, the Angular Distance Dang(x,z)=arccos⁡(CosineSimilarity(x,z))πD_{\text{ang}}(x, z) = \frac{\arccos(\text{CosineSimilarity}(x, z))}{\pi} satisfies all four metric axioms.

2. Mathematical Proof: The Curse of Dimensionality#

As the dimensionality dd increases, intuitive geometric properties break down. We prove why distance-based algorithms like k-NN suffer degradation in high-dimensional spaces.

Hypersphere to Hypercube Volume Ratio#

Consider a dd-dimensional hypercube of side length 2r2r, with volume:

Vcube(d)=(2r)d=2drdV_{\text{cube}}(d) = (2r)^d = 2^d r^d

Now consider an inscribed dd-dimensional hypersphere of radius rr. Its volume is given by:

Vsphere(d)=πd/2Γ(d2+1)rdV_{\text{sphere}}(d) = \frac{\pi^{d/2}}{\Gamma\left(\frac{d}{2} + 1\right)} r^d

Where Γ\Gamma denotes Euler's Gamma function (Γ(n)=(n−1)!\Gamma(n) = (n-1)! for positive integers).

Let us compute the ratio of the volume of the inscribed hypersphere to the enclosing hypercube:

Vsphere(d)Vcube(d)=πd/2Γ(d/2+1)rd2drd=πd/22dΓ(d/2+1)\frac{V_{\text{sphere}}(d)}{V_{\text{cube}}(d)} = \frac{\frac{\pi^{d/2}}{\Gamma(d/2 + 1)} r^d}{2^d r^d} = \frac{\pi^{d/2}}{2^d \Gamma(d/2 + 1)}

Applying Stirling's asymptotic formula Γ(k+1)≈2πk(k/e)k\Gamma(k + 1) \approx \sqrt{2\pi k} (k/e)^k with k=d/2k = d/2:

Vsphere(d)Vcube(d)≈πd/22dπd(d2e) ⁣d/2=1πd(2πe4d) ⁣d/2=1πd(πe2d) ⁣d/2\frac{V_{\text{sphere}}(d)}{V_{\text{cube}}(d)} \approx \frac{\pi^{d/2}}{2^d \sqrt{\pi d} \left( \frac{d}{2e} \right)^{\!d/2}} = \frac{1}{\sqrt{\pi d}} \left( \frac{2\pi e}{4d} \right)^{\!d/2} = \frac{1}{\sqrt{\pi d}} \left( \frac{\pi e}{2d} \right)^{\!d/2}

As d→∞d \to \infty, the term (πe2d)→0\left(\frac{\pi e}{2d}\right) \to 0 exponentially:

lim⁡d→∞Vsphere(d)Vcube(d)=0\lim_{d \to \infty} \frac{V_{\text{sphere}}(d)}{V_{\text{cube}}(d)} = 0
Physical Interpretation
In high-dimensional space, virtually all volume resides in the corners of the hypercube. All points become approximately equidistant from each other:
lim⁡d→∞Dmax⁡−Dmin⁡Dmin⁡=0\lim_{d \to \infty} \frac{D_{\max} - D_{\min}}{D_{\min}} = 0
When the distance to the nearest neighbor approaches the distance to the farthest neighbor, the neighborhood concept loses discriminative meaning.

3. KD-Trees: Accelerating Spatial Queries#

Brute-force k-NN requires scanning all NN data points, yielding O(N⋅d)O(N \cdot d) query latency. A k-d Tree (k-dimensional tree) is a space-partitioning binary search tree that indexes data to enable O(log⁡N)O(\log N) expected search time.

Architecture Diagram
Rendering diagram...

Partitioning Rules:#

  1. Cycling Dimensions: At depth mm, split on feature dimension:
axis=m(modd)\text{axis} = m \pmod d
  1. Median Split: Find the median along coordinate axis\text{axis} to partition instances into balanced left and right subtrees.
  2. Branch Pruning: During nearest neighbor queries, if the distance from the query point qq to the splitting hyperplane ∣qaxis−vsplit∣|q_{\text{axis}} - v_{\text{split}}| is greater than the current radius to the kk-th best neighbor, the opposite subtree can be completely pruned.

4. From-Scratch Implementation: Zero Dependencies#

The following implementations demonstrate pure mathematical k-NN classification in Python and Rust without external packages.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import math from typing import List, Tuple from collections import Counter class KNNClassifierFromScratch: def __init__(self, k: int = 3, metric: str = "euclidean"): self.k = k self.metric = metric self.X_train: List[List[float]] = [] self.y_train: List[int] = [] def fit(self, X: List[List[float]], y: List[int]) -> None: # Non-parametric lazy model: store instances directly self.X_train = [list(row) for row in X] self.y_train = list(y) def _compute_distance(self, a: List[float], b: List[float]) -> float: if self.metric == "manhattan": return sum(abs(x_i - y_i) for x_i, y_i in zip(a, b)) # Default: Euclidean distance (L2) return math.sqrt(sum((x_i - y_i) ** 2 for x_i, y_i in zip(a, b))) def predict_single(self, query: List[float]) -> int: distances: List[Tuple[float, int]] = [] for i, sample in enumerate(self.X_train): dist = self._compute_distance(query, sample) distances.append((dist, self.y_train[i])) # Sort by distance in ascending order distances.sort(key=lambda item: item[0]) # Extract top k nearest labels k_nearest_labels = [label for _, label in distances[:self.k]] # Majority vote vote_counts = Counter(k_nearest_labels) most_common = vote_counts.most_common(1)[0][0] return most_common def predict(self, X_test: List[List[float]]) -> List[int]: return [self.predict_single(query) for query in X_test] if __name__ == "__main__": X_train = [[1.0, 2.0], [1.5, 1.8], [5.0, 8.0], [8.0, 8.0], [1.0, 0.6], [9.0, 11.0]] y_train = [0, 0, 1, 1, 0, 1] knn = KNNClassifierFromScratch(k=3, metric="euclidean") knn.fit(X_train, y_train) X_test = [[1.2, 1.5], [6.0, 7.5]] predictions = knn.predict(X_test) print(f"Predicted labels: {predictions}")

5. Active Knowledge Checks#

Concept check

Why does the ratio of an inscribed hypersphere volume to enclosing hypercube volume approach zero as dimensionality d approaches infinity?

Concept check

In a KD-Tree nearest neighbor query, under what exact condition can the search algorithm prune and skip an entire subtree?