Skip to content
ExponaBLOG
PRACTICAL AI & MACHINE LEARNING · DAY 092 / 100

k-nearest neighbors in Practical AI & Machine Learning

Predict from the most similar examples.

Expona Admin7 min read

k-nearest neighbors in Practical AI & Machine Learning

k-nearest neighbors (kNN) is one of the simplest yet surprisingly effective algorithms you'll reach for when you need predictions based on similarity. Unlike most machine learning models, kNN requires no training phase—you simply store your data and compare new examples to find the closest neighbors, making it ideal for rapid prototyping and scenarios where your training set changes frequently.

Why kNN feels different: no training step, slow prediction

The appeal of kNN is immediate: there's nothing to train. You load your dataset into memory, and you're done. When a prediction request arrives, the algorithm calculates the distance from your query point to every example in your dataset, sorts by proximity, and takes a vote or average from the k nearest neighbors.

This simplicity comes with a trade-off. While models like logistic regression or random forests make predictions in milliseconds, kNN predictions scale with your dataset size. If you have a million examples, each prediction requires a million distance calculations. We've seen this become a real bottleneck in production recommendation systems handling thousands of daily requests.

Here's a practical example: imagine you're building a fraud detection system. You store historical transactions labeled as fraudulent or legitimate. When a new transaction arrives, kNN finds the five most similar historical transactions and flags the new one as fraud if most neighbors are fraudulent. No training loop, no hyperparameter tuning beyond choosing k—but every prediction touches your entire history.

python
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris

X, y = load_iris(return_X_y=True)

# Initialize and predict—no fit() call needed for illustrative purposes
# In practice, you still call fit(), but there's minimal computation
knn = KNeighborsClassifier(n_neighbors=5)
knn.fit(X, y)

# Prediction hits all training data
prediction = knn.predict([[5.1, 3.5, 1.4, 0.2]])
print(prediction)

In production, we've optimized this using approximate nearest neighbor libraries like FAISS or locality-sensitive hashing (LSH) to avoid full-dataset scans, but that's a layer on top of the core algorithm.

Scale features first

kNN measures distance—typically Euclidean distance in the raw feature space. If one feature ranges from 0 to 1 (say, a normalized score) and another ranges from 0 to 100,000 (say, transaction amount), the larger-scale feature will dominate every distance calculation. Your kNN model will effectively ignore the small-scale feature.

We always standardize or normalize features before using kNN. Standardization (z-score normalization) subtracts the mean and divides by standard deviation. Normalization (min-max scaling) rescales features to [0, 1]. Both are quick preprocessing steps with enormous impact.

Consider a real scenario: predicting customer churn based on account age (in days, range 0–10,000), average monthly spend (in dollars, range 0–10,000), and support ticket count (range 0–50). Without scaling, the first two features swamp the third. With scaling, all three contribute meaningfully to similarity.

python
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
import numpy as np

# Example: account age (days), monthly spend (dollars), support tickets
X = np.array([
    [100, 500, 2],
    [500, 1200, 5],
    [200, 800, 3],
    [1000, 2500, 8]
])
y = np.array([0, 1, 0, 1])  # 0 = retained, 1 = churned

# Scale features
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

knn = KNeighborsClassifier(n_neighbors=3)
knn.fit(X_scaled, y)

new_customer = np.array([[150, 600, 2]])
new_customer_scaled = scaler.transform(new_customer)
prediction = knn.predict(new_customer_scaled)
print(f"Prediction: {prediction[0]}")

We've debugged many kNN models that looked broken—predicted wrong neighbors, unreliable results—only to find that feature scaling was missing. It's a one-line fix that prevents hours of confusion.

Choose k with cross-validation

k is your only real hyperparameter. k=1 means your prediction is the single closest neighbor; this often overfits, echoing training noise. k=number of examples means every prediction is the majority class; this underfits, ignoring local patterns. The right k lives somewhere in between.

Cross-validation finds that sweet spot. You split your data into folds, train kNN models with different k values on each fold combination, and measure accuracy on held-out data. The k with the highest average cross-validation score is your choice.

We typically test k values like [1, 3, 5, 7, 10, 15, 20] and pick the best performer. For datasets with 10,000+ examples, we might jump by larger increments. For datasets under 1,000, smaller increments matter more.

python
from sklearn.model_selection import cross_val_score
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_iris
from sklearn.preprocessing import StandardScaler

X, y = load_iris(return_X_y=True)

# Scale first
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)

# Test different k values
k_values = [1, 3, 5, 7, 10]
results = {}

for k in k_values:
    knn = KNeighborsClassifier(n_neighbors=k)
    scores = cross_val_score(knn, X_scaled, y, cv=5)
    results[k] = scores.mean()
    print(f"k={k}: {scores.mean():.3f} (+/- {scores.std():.3f})")

best_k = max(results, key=results.get)
print(f"\nBest k: {best_k}")

In a production recommendation system, we ran this grid search monthly as our user base grew. We discovered that k=7 worked best for 50,000 users but k=15 was better at 500,000 users. Adapting k to dataset size paid off in recommendation quality.

Vector search—finding semantically similar items in a high-dimensional space—is kNN applied to embeddings. You embed documents, images, or queries into vectors (using transformers, CLIP, or OpenAI embeddings), then use approximate nearest neighbor search to find similar items without brute-force comparison.

The core logic is identical: store vectors, measure distance (usually cosine similarity in embedding space), return the k closest neighbors. The difference is scale and approximation. With millions of vectors, exact kNN is infeasible, so you use algorithms like HNSW or IVF to prune the search space dramatically while maintaining accuracy.

We've built recommendation engines, semantic search, and anomaly detection systems on this foundation. A typical flow: embed user behavior or documents into vectors, build an approximate nearest neighbor index, then serve recommendations by finding the k nearest vectors to the user's current state.

Here's a conceptual sketch using a popular library:

python
import numpy as np
from scipy.spatial.distance import cosine

# Simulated embeddings: 100 documents, 768-dimensional vectors
np.random.seed(42)
doc_embeddings = np.random.randn(100, 768)
doc_ids = [f"doc_{i}" for i in range(100)]

# Query embedding (e.g., from a new document or user embedding)
query_embedding = np.random.randn(768)

# Compute cosine distances to all docs
distances = [cosine(query_embedding, doc_embeddings[i]) for i in range(100)]

# Find k=5 nearest neighbors
k = 5
nearest_indices = np.argsort(distances)[:k]
nearest_docs = [doc_ids[i] for i in nearest_indices]

print(f"Top {k} nearest documents: {nearest_docs}")

In production, you'd use FAISS, Pinecone, Weaviate, or Milvus to handle scale. But the underlying principle—find examples most similar to the query—is pure kNN.

Common mistakes

  • Forgetting to scale features: This is the #1 mistake. Larger-magnitude features dominate. Always standardize or normalize before fitting.
  • Not tuning k: Defaulting to k=5 or k=3 without cross-validation often leaves performance on the table. Test a range.
  • Using kNN on huge datasets without approximation: If you have millions of points and need millisecond predictions, approximate nearest neighbor methods are non-negotiable.
  • Ignoring class imbalance: If one class is rare, kNN's majority vote can be misleading. Consider weighted voting or balancing your training set.
  • Treating kNN as a black box for distance metrics: Euclidean distance assumes continuous features. For categorical data or specific domains, cosine similarity or Manhattan distance might be better.

Key takeaways

  • kNN requires no training but scales poorly with dataset size; exact predictions require computing distances to all stored examples.
  • Feature scaling is mandatory—standardize or normalize before using kNN to prevent large-magnitude features from dominating.
  • Cross-validate to choose k; testing k values like [1, 3, 5, 7, 10, 15] across folds reveals the best generalization.
  • Vector search applies kNN to embeddings, using approximate nearest neighbor algorithms for production-scale similarity search.
  • For rapid prototyping and small-to-medium datasets, kNN is fast to implement and often effective; for large-scale systems, layer it with approximation methods like FAISS or LSH.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles