4Classical Machine Learning 4.5Classical Machine Learning Models
4.5.5 K-Nearest Neighbors
Predict a new sample from the labels or values of the training samples closest to it.
What K-NN does
K-nearest neighbors is fundamentally a different kind of model than the usual models we have studied yet. K-nearest neighbors, usually shortened to K-NN, predicts a new sample by looking at the training samples most similar to it.
new sample
→ find the closest training examples
→ look at their answers
→ make a prediction
For example, to identify a new flower, K-NN finds known flowers with similar measurements and predicts the species that appears most often among them.
K-NN does almost no learning when fit is called. It mainly stores the training examples. Most of the work happens later, when predict calculates distances and searches for neighbors. This makes K-NN simple to start with, but it also means prediction can become slow when the stored dataset is large.
One prediction by hand
Suppose four labelled points are stored:
| Point | Coordinates | Label |
|---|---|---|
| A | red | |
| B | red | |
| C | blue | |
| D | blue |
We want to classify the new point . Its distances from the stored points are:
| Stored point | Distance from | Label |
|---|---|---|
| B | red | |
| A | red | |
| C | blue | |
| D | blue |
If , the three nearest labels are red, red and blue. Red has the majority, so K-NN predicts red.
That is the complete basic idea: calculate distances, take the nearest , and combine their answers.
Classification and regression
The neighbor search is identical for classification and regression. Only the final step changes.
K-NN classification
For classification, the neighbors vote and the most common label wins:
This is what happened in the red-and-blue example above.
K-NN regression
For regression, the targets are numbers, so the model averages them:
For example, to estimate the price of a new house, K-NN can find the most similar houses and average their prices. Scikit-learn provides KNeighborsClassifier for voting and KNeighborsRegressor for averaging.
OptionalOptional notation: a formal definition
Let the training pairs be , let be a query, and let be a distance function. Write for the indices of the training points with the smallest .
For classification,
where equals 1 when neighbor has class and 0 otherwise. This expression simply counts the votes for each class.
What does "nearest" mean?
K-NN needs a way to measure how different two samples are. For ordinary numeric features, the usual choice is Euclidean distance:
This is the straight-line distance between two points. Similar feature values produce a small distance; very different values produce a large one.
K-NN therefore depends on two closely connected choices:
features → decide what information describes a sample
distance → decides what “similar” means in that feature space
OptionalOptional: other distance measures
Euclidean distance is not the only choice.
| Distance | Formula | Typical use |
|---|---|---|
| Manhattan | Reduces the effect of one very large feature difference | |
| Minkowski | General family: is Manhattan and is Euclidean | |
| Cosine | Text and embedding vectors where direction matters more than length |
Scikit-learn uses Minkowski distance with , which is Euclidean distance, by default. metric="cosine" is also available and uses brute-force search.
Why feature scaling matters so much
Imagine a dataset with these two features:
- age ranges from 10 to 80;
- income ranges from 20,000 to 2,000,000.
A difference of 10 years is numerically tiny beside a difference of 100,000 in income. If raw values are placed in the distance formula, income almost completely decides which samples are nearest, even if age is equally important to the task.
Standardisation puts each feature on a comparable scale:
Fit the scaler only on the training data, then reuse it for validation and test data. A scikit-learn pipeline handles this safely. Scikit-learn's preprocessing guide describes other scaling methods.
Choosing k
The number of neighbors controls how local the decision is.
Small k
A small follows individual training points closely. This creates a flexible, detailed decision boundary, but it is sensitive to noise and can overfit.
At the extreme, gives every training point its own label because the point is its own nearest neighbor. Training accuracy is therefore usually 100%, even when predictions on new data are poor.
Large k
A large looks at a wider region. Its predictions are smoother and less sensitive to one noisy sample, but useful local patterns can be washed away, causing underfitting.
At the other extreme, if equals the number of training samples, every query sees the whole dataset. Classification then predicts the overall majority class almost everywhere.
In bias–variance language, small has lower bias and higher variance; large has higher bias and lower variance. The important practical lesson is simpler: do not choose from training accuracy.
How to choose k
Try several values on validation data or with cross-validation, then keep the value that performs best. On a small dataset, trying is inexpensive. With two classes, an odd also reduces the chance of a tied vote.
Do not use the test set to choose . The test set should be used only after the model choices have been made.
The Cross-Validation module will show how to automate the comparison of several values of and different voting styles.
Letting closer neighbors count more
Ordinary K-NN gives every neighbor one equal vote. Distance-weighted K-NN gives closer neighbors more influence, using a weight roughly like
A very close example can then matter more than one near the edge of the neighborhood. In scikit-learn, use weights="distance"; the default weights="uniform" gives equal votes.
Using K-NN in practice
Here is a complete first model on the Iris dataset:
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.neighbors import KNeighborsClassifier
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
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, stratify=y, random_state=0
)
model = make_pipeline(
StandardScaler(),
KNeighborsClassifier(n_neighbors=5),
)
model.fit(X_train, y_train)
predictions = model.predict(X_test)
The calls hide very little:
fit → scale and store the training examples
predict → scale each query, calculate distances,
find five neighbors, and take a vote
K-NN is sometimes called lazy learning because most work waits until prediction time.
Evaluating K-NN
As with every classifier, evaluate on examples that were not used to fit or tune the model. Accuracy is the fraction classified correctly:
from sklearn.metrics import accuracy_score
print(round(accuracy_score(y_test, predictions), 3))
# 0.967
Accuracy alone may not be enough when classes are imbalanced or different mistakes have different costs. Model Evaluation Metrics covers confusion matrices, precision, recall and F1.
The training accuracy here is also 0.967, but that does not make training accuracy a safe way to choose . At , it would rise to 1.000 automatically.
Useful K-NN settings
| Setting | Default | Meaning |
|---|---|---|
n_neighbors |
5 |
Number of neighbors that vote or are averaged |
weights |
"uniform" |
How neighbor influence is calculated |
metric |
"minkowski" |
Distance function used to compare samples |
p |
2 |
Power used by Minkowski distance |
algorithm |
"auto" |
Search method used at prediction time |
Where K-NN can fail
K-NN works only when nearby samples really are similar in a useful way.
Irrelevant features
Suppose only two features matter, but another 50 random features are included. Differences in those random columns still contribute to distance, so genuinely similar samples may stop appearing close. Feature selection or a better representation can help.
High-dimensional spaces
K-NN relies on meaningful local neighborhoods. As the number of dimensions grows, the nearest and farthest samples can become surprisingly similar in distance. This is part of the curse of dimensionality.
OptionalOptional experiment: distances in high dimensions
The table uses 1,000 points drawn uniformly from the unit cube and averages over 200 random queries.
| Dimension | Nearest ÷ farthest distance |
|---|---|
| 2 | 0.016 |
| 10 | 0.271 |
| 100 | 0.697 |
| 1,000 | 0.894 |
As the ratio approaches 1, the nearest and farthest points are almost equally far away, so “nearest” carries less information. Real data often lies near a much lower-dimensional structure than uniform noise, which is one reason K-NN can still work reasonably well on 784-pixel digit images.
Reducing dimension with PCA or using learned embeddings can make distance more useful.
Large datasets
Fitting is cheap, but brute-force prediction compares each query with all training samples across features. This costs roughly time per query and memory for the stored training set.
For 60,000 MNIST training images with 784 pixels each, one query requires about 47 million pixel subtractions. This is the central K-NN tradeoff:
cheap fit → expensive prediction
OptionalOptional: faster neighbor search
- Brute force compares a query with every stored sample. It remains effective when the dataset is not too large or the feature dimension is high.
- KD-trees and ball trees divide the space so whole regions can be skipped. They work well in fairly low dimensions—often fewer than about 20—but lose their advantage as the number of dimensions grows. Scikit-learn's
algorithm="auto"chooses an approach automatically. - Approximate nearest-neighbor search gives up a little exactness for much faster queries. Libraries such as FAISS use this approach to search millions of embedding vectors.
K-NN and embeddings
Raw features do not always describe useful similarity. Two sentences can have nearly the same meaning while using completely different words. Two images can show the same object while differing in lighting, background and pixel values.
A pretrained model can turn an input into an embedding vector:
input → pretrained model → embedding vector
Good embeddings place semantically similar inputs near one another. K-NN can then work in that space:
embedding → nearest labelled embeddings → predicted class
This applies to text, image and audio embeddings. K-NN becomes much more useful when the feature space has been designed so that distance represents meaningful similarity.
OptionalOptional terminology
K-NN is described with several related names:
- lazy learning: little work happens during
fit; most work happens duringpredict; - instance-based learning: predictions directly use stored training instances; and
- non-parametric: the task is not compressed into a fixed small set of learned coefficients, as it is in linear or logistic regression.
Because the stored examples form the model, adding more useful examples can let K-NN follow decision boundaries of many shapes.
Optional: K-NN from scratch
import numpy as np
def knn_predict(X_train, y_train, X_query, k=5):
# Squared distance from every query to every training point: (q, n)
d2 = ((X_query[:, None, :] - X_train[None, :, :]) ** 2).sum(axis=2)
# Indices of the k smallest distances in each row
nearest = np.argpartition(d2, kth=k - 1, axis=1)[:, :k]
votes = y_train[nearest]
return np.array([np.bincount(row).argmax() for row in votes])
On the Iris split above, this makes the same predictions as KNeighborsClassifier(algorithm="brute") for all 30 test samples.
OptionalOptional implementation details
- The square root is skipped because it does not change which distances are smallest.
np.argpartitionfinds the smallest values in time per row instead of fully sorting in time.np.bincount(row).argmax()requires integer labels and breaks ties towards the smaller label.- Broadcasting builds an array with shape . For large inputs, the identity
computes distances with a matrix product while needing only a distance array.
K-NN in olympiad tasks
K-NN is useful in two main roles.
As a baseline. On a small dataset, scaling the features and fitting K-NN takes little time. Its validation score tells you how much a more complicated model needs to improve.
On top of embeddings. A pretrained model can provide a useful representation, while K-NN supplies the final class without retraining the pretrained model.
When should I try K-NN?
K-NN is a good choice when:
- the dataset is fairly small;
- the features are few, meaningful and can be scaled sensibly;
- strong embeddings are already available; or
- you need a quick baseline.
Be cautious when:
- the training set is enormous;
- there are many noisy raw features;
- distance does not match real similarity; or
- prediction must be extremely fast.
The whole idea in one view
prediction: new sample → calculate distances → take nearest k
→ vote or average → prediction
what matters: features → what information is available
scaling → whether features contribute fairly
distance → what “similar” means
k → how local the prediction is
generalisation: store training examples → choose k on validation data
→ evaluate once on unseen test data
limitations: many dimensions → distance becomes less meaningful
much training data → prediction becomes slow
poor features → nearest does not mean similar
modern use: input → pretrained embedding → K-NN
Resources
| Source | Title | Why read it |
|---|---|---|
| scikit-learn | User Guide: Nearest Neighbors | Classification and regression with neighbors, and when to use brute force, KD-trees or ball trees. |
| Stanford CS231n | Image Classification: k-Nearest Neighbor | Builds K-NN on raw CIFAR-10 pixels, tunes k on a validation set, and shows why pixel distance is a weak measure for images. |
| James et al. | An Introduction to Statistical Learning, chapters 2 and 3 | Free book. Uses K-NN to explain the bias-variance tradeoff and compares K-NN regression with linear regression. |
| scikit-learn | Importance of Feature Scaling | Runs K-NN on the Wine dataset with and without scaling, with plots of the decision boundaries. |
Practice problems
| Solved | Source | Problem | Difficulty | Tags |
|---|---|---|---|---|
| IOAI 2024 | Help BOBAI: More classification in an unknown language | Medium | nlp, embeddings | |
| Kaggle | Digit Recognizer | Easy | vision, baseline | |
| Kaggle | Titanic | Easy | tabular, preprocessing |
Finished this module? Mark it done.
Comments
Stuck on something, spotted a mistake, or found a resource that helped you? Tell us below.
Loading comments