PROMETHEUS OS
NATIVE AI OPERATING SYSTEM
[INIT] Prometheus OS loading...
[CORE] Neural interface initializing...
[AI] LLM router connecting...
[MEM] Memory graph loading...
[AGT] Agent chains on standby...
[SEC] Security protocols active...
[NET] Provider mesh established...
[UI] Workspace rendering...
[DONE] All systems nominal.
Welcome to PROMETHEUS OS.
READY · OFFLINE · SECURE · PRIVATE
🎨 THEME ENGINE
PROMETHEUS OS · 23 THEMES
Trial version: Dark + Light themes only.
Full theme access with PROMETHEUS OS license.
Dark theme active
Experience all 23 themes in PROMETHEUS OS

Classic Classifiers: kNN, Naive Bayes and SVMs

Intermediate 20 min· 5 exercises

Apply k-nearest neighbours, naive Bayes and support vector machines correctly, compute their predictions by hand, and choose among them based on data size, dimensionality and the need for probabilities.

By the end of this lesson you will be able to
  • Compute k-nearest-neighbour predictions under different distance metrics and explain why feature scaling and the choice of k matter.
  • Explain the curse of dimensionality and quantify how neighbourhoods and distances behave in high dimensions.
  • Derive the naive Bayes decision rule, apply Laplace smoothing, and classify a text example by hand in probability and log space.
  • Formulate the soft-margin SVM with hinge loss, compute margins and losses, and explain the kernel trick and the roles of C and gamma.
  • Choose among kNN, naive Bayes, SVMs and logistic regression for a given problem and justify the choice.

Before this: The ML Workflow: Framing, Data, Splits and Leakage, Linear and Logistic Regression, Probability and Statistics for AI

Before deep learning and gradient boosting, three classifiers carried much of applied machine learning: k-nearest neighbours, naive Bayes and support vector machines. They are still worth mastering. k-nearest neighbours is the conceptual ancestor of today's vector search and retrieval-augmented generation; naive Bayes is a fast, strong baseline for text that trains in milliseconds; and support vector machines introduced the margin, hinge loss and kernel ideas that run throughout modern ML. Each embodies a different philosophy, and knowing when each fits is part of a practitioner's toolkit.

Three philosophies in one picture

Imagine sorting fruit at a market stall.

  • k-nearest neighbours keeps every fruit it has ever seen. Given a new one, it finds the kk most similar fruits on the table and goes with the majority. No model, just memory and a notion of "similar".
  • Naive Bayes keeps a tally per fruit type: how often oranges are orange, round, heavy. For a new fruit it asks which type would most probably produce these characteristics, pretending each characteristic is independent of the others given the type.
  • A support vector machine draws the widest possible street between apples and oranges on the table, and only the fruits standing at the curb (the support vectors) determine where the street runs.

k-nearest neighbours (kNN)

The algorithm

Store the training set {(xi,yi)}i=1n\{(x_i, y_i)\}_{i=1}^{n}. To classify a query xx:

  1. Compute the distance d(x,xi)d(x, x_i) to every training point.
  2. Take the set Nk(x)N_k(x) of the kk closest.
  3. Predict the majority label among them (classification), or their average target (regression). Class probabilities are the label fractions within Nk(x)N_k(x).

A variant weights each neighbour's vote by 1/d1/d, so closer points count more. There is no training step beyond storing data (kNN is a lazy or instance-based learner), so all the cost moves to prediction: a naive query is O(nd)O(nd). Tree indexes (KD-trees, ball trees) speed this up in low dimensions, and approximate nearest-neighbour indexes (such as HNSW graphs) make it practical for millions of high-dimensional vectors; this is exactly the machinery inside vector databases (see vector-databases-and-indexing).

Cover and Hart (1967) proved a classic guarantee: as the training set grows, the error rate of the 1-nearest-neighbour rule is at most twice the Bayes error rate, the lowest error achievable by any classifier on that problem. That is a striking result for such a simple method, but it is asymptotic; with finite data in many dimensions kNN can do badly, as we will see.

Distance metrics

For vectors x,z∈Rdx, z \in \mathbb{R}^d, the Minkowski distance of order pp is

dp(x,z)=(∑j=1d∣xj−zj∣p)1/p.d_p(x, z) = \Big(\sum_{j=1}^{d} |x_j - z_j|^p\Big)^{1/p}.

p=2p = 2 is Euclidean distance (straight line); p=1p = 1 is Manhattan distance (sum of absolute differences), somewhat more robust to a single large coordinate difference. For text and embeddings, cosine distance 1−x⊤z∥x∥∥z∥1 - \frac{x^\top z}{\lVert x\rVert \lVert z\rVert} compares direction and ignores length. For categorical data, Hamming distance counts mismatches. The metric encodes your notion of similarity, so choose it deliberately.

Worked example

Training points: A(1,1)A(1,1) red, B(2,3)B(2,3) red, C(4,4)C(4,4) blue, D(5,3)D(5,3) blue, E(3,6)E(3,6) blue. Query q=(3,3)q = (3, 3).

PointEuclidean distanceManhattan distanceLabel
B1+0=1.00\sqrt{1 + 0} = 1.001red
C1+1=1.41\sqrt{1 + 1} = 1.412blue
D4+0=2.00\sqrt{4 + 0} = 2.002blue
A4+4=2.83\sqrt{4 + 4} = 2.834red
E0+9=3.00\sqrt{0 + 9} = 3.003blue

With k=1k = 1 the prediction is red (B). With k=3k = 3 (B, C, D) it is blue, with estimated probability 2/32/3. The choice of kk is a bias-variance knob: small kk gives jagged, high-variance boundaries that react to individual noisy points; large kk gives smooth, high-bias boundaries. Choose it by cross-validation, and prefer odd kk in binary problems to avoid ties.

Feature scaling is mandatory

Distances add up differences across features, so a feature measured in large units swamps the others. Query: age 30, income $50,000. Neighbour P1: age 31, income $52,000. Neighbour P2: age 60, income $50,500. Unscaled Euclidean distances: P1 =12+20002≈2000= \sqrt{1^2 + 2000^2} \approx 2000; P2 =302+5002≈501= \sqrt{30^2 + 500^2} \approx 501. P2 is "nearer" despite a 30-year age gap, because income dominates. After standardising (say age standard deviation 10 years, income $20,000): P1 =0.12+0.12=0.14= \sqrt{0.1^2 + 0.1^2} = 0.14; P2 =32+0.0252≈3.0= \sqrt{3^2 + 0.025^2} \approx 3.0. Now P1 is correctly the near neighbour. Standardise (or otherwise scale) features inside a pipeline before any distance-based method.

The curse of dimensionality

In high dimensions, intuitions about "near" break down, for two reasons.

First, neighbourhoods stop being local. Suppose data are spread uniformly in the unit hypercube [0,1]d[0,1]^d and you want a neighbourhood that captures 10% of the data. A sub-cube with that volume needs edge length 0.11/d0.1^{1/d}: 0.1 when d=1d = 1, 0.32 when d=2d = 2, 0.79 when d=10d = 10 and 0.98 when d=100d = 100. To capture even 10% of the points you must span almost the whole range of every feature, so the "neighbours" are not similar at all.

Second, distances concentrate: the nearest and farthest points become almost equally far away. This short simulation measures the ratio of the nearest to the farthest distance from a random query to 1,000 random points:

import numpy as np
rng = np.random.default_rng(0)
for d in [2, 10, 100, 1000]:
    X = rng.uniform(size=(1000, d))
    q = rng.uniform(size=d)
    dist = np.linalg.norm(X - q, axis=1)
    print(f"d={d:5d}  nearest/farthest distance ratio = {dist.min() / dist.max():.3f}")

The ratio climbs from about 0.01 in 2 dimensions to about 0.22 at 10, 0.72 at 100 and 0.90 at 1,000. When everything is roughly equally far, "nearest" carries little information. kNN works in practice in high dimensions only when the data actually lie near a lower-dimensional structure (as images and learned embeddings do), or after dimensionality reduction or a learned embedding has done the hard work.

Naive Bayes

The model

Bayes' theorem gives the posterior probability of class cc given features x=(x1,…,xd)x = (x_1, \dots, x_d):

P(c∣x)=P(c) P(x∣c)P(x)  ∝  P(c) P(x∣c).P(c \mid x) = \frac{P(c)\,P(x \mid c)}{P(x)} \;\propto\; P(c)\,P(x \mid c).

Estimating P(x∣c)P(x \mid c) for all combinations of dd features is hopeless. The naive assumption is that features are conditionally independent given the class:

P(x∣c)=∏j=1dP(xj∣c)⟹c^=arg⁡max⁡c[log⁡P(c)+∑j=1dlog⁡P(xj∣c)].P(x \mid c) = \prod_{j=1}^{d} P(x_j \mid c) \quad\Longrightarrow\quad \hat c = \arg\max_c \Big[\log P(c) + \sum_{j=1}^{d} \log P(x_j \mid c)\Big].

We work with sums of logs because a product of hundreds of small probabilities underflows to zero in floating point. Each factor is estimated by counting, so training is a single pass over the data.

The variants differ in how they model P(xj∣c)P(x_j \mid c):

  • Multinomial NB: features are counts (word counts in a document). P(word w∣c)P(\text{word } w \mid c) is the share of all word tokens in class cc that are ww.
  • Bernoulli NB: features are binary (word present or absent), and absence is also evidence.
  • Gaussian NB: continuous features, each modelled as a normal distribution with a per-class mean and variance.

Laplace smoothing

If a word never appeared in spam during training, its estimated probability is 0, and a single occurrence would veto the spam class no matter what else the email says. Additive (Laplace) smoothing adds a pseudo-count α\alpha (usually 1) to every word:

P(w∣c)=count(w,c)+α∑w′count(w′,c)+α∣V∣,P(w \mid c) = \frac{\text{count}(w, c) + \alpha}{\sum_{w'} \text{count}(w', c) + \alpha |V|},

where ∣V∣|V| is the vocabulary size.

Worked example: a tiny spam filter

Vocabulary V={free,win,meeting,report}V = \{\text{free}, \text{win}, \text{meeting}, \text{report}\}, so ∣V∣=4|V| = 4. Training data: 4 spam and 6 ham (legitimate) emails, so the priors are P(spam)=0.4P(\text{spam}) = 0.4 and P(ham)=0.6P(\text{ham}) = 0.6. Word counts:

WordSpam countHam countP(w∣spam)P(w \mid \text{spam}), α=1\alpha = 1P(w∣ham)P(w \mid \text{ham}), α=1\alpha = 1
free829/20=0.4509/20 = 0.4503/28=0.1073/28 = 0.107
win516/20=0.3006/20 = 0.3002/28=0.0712/28 = 0.071
meeting192/20=0.1002/20 = 0.10010/28=0.35710/28 = 0.357
report2123/20=0.1503/20 = 0.15013/28=0.46413/28 = 0.464
total1624denominator 16+4=2016 + 4 = 20denominator 24+4=2824 + 4 = 28

New email: "free win meeting".

  • Spam score: 0.4×0.450×0.300×0.100=0.005400.4 \times 0.450 \times 0.300 \times 0.100 = 0.00540.
  • Ham score: 0.6×0.107×0.071×0.357=0.001640.6 \times 0.107 \times 0.071 \times 0.357 = 0.00164.
  • Normalising: P(spam∣email)=0.00540/(0.00540+0.00164)=0.767P(\text{spam} \mid \text{email}) = 0.00540 / (0.00540 + 0.00164) = 0.767.

In log space, the same computation compares log⁡0.4+log⁡0.45+log⁡0.3+log⁡0.1=−5.22\log 0.4 + \log 0.45 + \log 0.3 + \log 0.1 = -5.22 with −6.41-6.41 for ham; spam wins. "free" and "win" each favour spam by a factor of about 4, and "meeting" favours ham by about 3.6, not enough to overturn them. Note what the model ignores: word order, and the fact that "free" and "win" tend to co-occur. Because co-occurring words are counted as independent evidence, naive Bayes tends to produce overconfident probabilities (pushed toward 0 or 1), even when its ranking of emails is good. Use it for decisions or ranking, and calibrate before trusting its probabilities.

The same idea in scikit-learn, with real tokenisation:

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import make_pipeline

emails = ["win free money now", "free prize win win", "claim your free reward",
          "meeting moved to monday", "quarterly report attached", "lunch meeting tomorrow?",
          "please review the report", "free pizza at the team meeting"]
labels = [1, 1, 1, 0, 0, 0, 0, 0]          # 1 = spam

nb = make_pipeline(CountVectorizer(), MultinomialNB(alpha=1.0))  # alpha = Laplace smoothing
nb.fit(emails, labels)
for msg in ["free money", "report for the meeting", "win a free meeting"]:
    p = nb.predict_proba([msg])[0, 1]
    print(f"{msg!r:28s} P(spam) = {p:.2f}")

This prints roughly 0.78, 0.03 and 0.69. Words never seen in training ("for", "a") are simply ignored by CountVectorizer. Eight emails is of course far too few for a real filter; production spam filtering today combines many signals and models, but naive Bayes on word counts remains a fast, hard-to-beat baseline for text classification.

Support vector machines (SVMs)

Maximum margin

Take binary labels yi∈{−1,+1}y_i \in \{-1, +1\} and a linear classifier f(x)=w⊤x+bf(x) = w^\top x + b that predicts sign⁡f(x)\operatorname{sign} f(x). If the classes are linearly separable, infinitely many hyperplanes separate them. The SVM picks the one with the largest margin, the distance from the hyperplane to the closest training points. Scaling ww and bb so that the closest points satisfy yif(xi)=1y_i f(x_i) = 1, that distance is 1/∥w∥1/\lVert w\rVert and the full width of the "street" between the two margin hyperplanes f(x)=±1f(x) = \pm 1 is 2/∥w∥2/\lVert w\rVert, and the hard-margin SVM solves

min⁡w,b 12∥w∥2subject toyi(w⊤xi+b)≥1  for all i.\min_{w, b}\ \tfrac12 \lVert w \rVert^2 \quad \text{subject to}\quad y_i (w^\top x_i + b) \ge 1 \ \text{ for all } i .

A wide margin means small changes in the data are less likely to flip predictions, which is the geometric intuition for good generalisation. The solution depends only on the points lying on the margin, the support vectors; you could delete every other training point and get the same classifier.

Soft margin and hinge loss

Real data overlap, so we allow violations. The soft-margin SVM (Cortes and Vapnik, 1995) minimises

12∥w∥2+C∑i=1nmax⁡(0, 1−yif(xi)).\tfrac12 \lVert w \rVert^2 + C \sum_{i=1}^{n} \max\big(0,\ 1 - y_i f(x_i)\big).

The second term is the hinge loss: zero for points correctly classified with functional margin yif(xi)≥1y_i f(x_i) \ge 1, growing linearly as a point enters the margin or crosses to the wrong side. Compare logistic regression, whose log-loss never reaches exactly zero; the hinge's flat zero region is why only some points (those on or inside the margin) end up as support vectors. C>0C > 0 trades margin width against violations: large CC punishes violations heavily (narrow margin, fits the training data closely, risk of overfitting); small CC tolerates them (wide margin, smoother boundary, more support vectors). CC plays the role of an inverse regularisation strength, like C in logistic regression.

Worked example. Take w=(2,1)w = (2, 1), b=−4b = -4, so the margin width is 2/∥w∥=2/5=0.8942/\lVert w\rVert = 2/\sqrt5 = 0.894.

Point xxLabel yyf(x)=2x1+x2−4f(x) = 2x_1 + x_2 - 4yf(x)y f(x)Hinge loss
(3,1)(3, 1)+1+13.03.00 (safely outside margin)
(2,1)(2, 1)+1+11.01.00 (exactly on margin: support vector)
(2.25,0)(2.25, 0)+1+10.50.50.5 (inside margin, correct side)
(2,2)(2, 2)−1-12.0−2.0-2.03.0 (misclassified)

Kernels

A linear boundary is often too simple. We could map inputs to a richer feature space ϕ(x)\phi(x) and find a linear separator there, but explicit maps can be huge. The key fact is that the SVM's dual optimisation problem and its decision function use the data only through dot products:

f(x)=∑i∈SVαi yi K(xi,x)+b,K(x,z)=ϕ(x)⊤ϕ(z),f(x) = \sum_{i \in \text{SV}} \alpha_i\, y_i\, K(x_i, x) + b, \qquad K(x, z) = \phi(x)^\top \phi(z),

where the αi≥0\alpha_i \ge 0 are learned dual coefficients, non-zero only for support vectors. A kernel KK computes the dot product in feature space without building ϕ\phi at all: the kernel trick. Common kernels:

  • Linear: K(x,z)=x⊤zK(x, z) = x^\top z.
  • Polynomial: K(x,z)=(γ x⊤z+r)qK(x, z) = (\gamma\, x^\top z + r)^q, for degree qq.
  • RBF (Gaussian): K(x,z)=exp⁡(−γ∥x−z∥2)K(x, z) = \exp(-\gamma \lVert x - z \rVert^2), corresponding to an infinite-dimensional feature space.

For instance, in two dimensions (x⊤z)2=ϕ(x)⊤ϕ(z)(x^\top z)^2 = \phi(x)^\top \phi(z) with ϕ(x)=(x12,2 x1x2,x22)\phi(x) = (x_1^2, \sqrt2\, x_1 x_2, x_2^2): the kernel evaluates a three-dimensional dot product using a two-dimensional one.

For the RBF kernel, γ\gamma sets how far each support vector's influence reaches. Large γ\gamma means each point influences only a tiny neighbourhood: wiggly boundaries that can wrap around individual points (overfitting). Small γ\gamma means broad influence and nearly linear boundaries (underfitting). CC and γ\gamma interact, so tune them jointly on a logarithmic grid (for example 10−310^{-3} to 10310^{3}) with cross-validation. RBF SVMs are distance-based, so feature scaling is essential.

Costs and practicalities

Kernel SVM training (scikit-learn's SVC, built on LIBSVM) scales at least quadratically with the number of samples, which becomes impractical beyond a few tens of thousands of rows. For large or very high-dimensional sparse data (text), use a linear SVM (LinearSVC or SGDClassifier with hinge loss), which scales roughly linearly. SVMs output scores, not probabilities; SVC(probability=True) fits an extra calibration step using internal cross-validation, which is slow and can disagree slightly with predict. Multiclass problems are handled by combining binary SVMs (scikit-learn's SVC uses one-vs-one internally).

Putting them side by side

The following compares the three on scikit-learn's wine dataset (178 wines, 13 chemical measurements on very different scales, 3 cultivars), with and without scaling:

from sklearn.datasets import load_wine
from sklearn.model_selection import cross_val_score, StratifiedKFold
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.neighbors import KNeighborsClassifier
from sklearn.naive_bayes import GaussianNB
from sklearn.svm import SVC
from sklearn.linear_model import LogisticRegression

X, y = load_wine(return_X_y=True)
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=0)
models = {
    "kNN, raw features":    KNeighborsClassifier(n_neighbors=5),
    "kNN, scaled":          make_pipeline(StandardScaler(), KNeighborsClassifier(5)),
    "Gaussian NB":          GaussianNB(),
    "SVM RBF, raw":         SVC(kernel="rbf", C=1.0),
    "SVM RBF, scaled":      make_pipeline(StandardScaler(), SVC(kernel="rbf", C=1.0)),
    "Logistic reg, scaled": make_pipeline(StandardScaler(), LogisticRegression(max_iter=1000)),
}
for name, m in models.items():
    s = cross_val_score(m, X, y, cv=cv)
    print(f"{name:21s} {s.mean():.3f} +/- {s.std():.3f}")

Typical results: kNN on raw features about 0.66, and 0.96 after scaling; the RBF SVM about 0.66 raw and 0.98 scaled; Gaussian naive Bayes about 0.97 (per-feature Gaussians are unaffected by scale); scaled logistic regression about 0.98. The lesson is less about which model wins on this small, easy dataset than about the 30-point swing that scaling causes for distance-based methods.

kNNNaive BayesSVM (kernel)Logistic regression
Training costNone (store data)One counting pass, very fastAt least quadratic in nnFast, iterative
Prediction costHigh: compare with stored pointsVery lowProportional to number of support vectorsVery low
Needs feature scalingYesNoYesYes, if regularised
Decision boundaryArbitrary, localLinear in log-space for multinomial/Bernoulli; quadratic for GaussianLinear or flexible (via kernel)Linear
High-dimensional sparse data (text)Poor with raw countsStrong baselineLinear SVM strongStrong
Probability qualityCoarse (fractions of kk)Often overconfidentNot native; needs calibrationUsually reasonable
Key hyperparameterskk, metric, weightingSmoothing α\alphaCC, kernel, γ\gammaCC (regularisation)
Best fitLow-dimensional data, embeddings with an ANN index, simple baselinesText, very small data, speed-critical baselinesSmall-to-medium data with complex boundariesDefault linear baseline

Common pitfalls

  • Forgetting to scale features for kNN and SVMs, and fitting the scaler outside the cross-validation pipeline.
  • Using kNN on raw high-dimensional features and blaming the algorithm when distances become meaningless.
  • Choosing kk, CC or γ\gamma by test-set performance instead of cross-validation.
  • Zero probabilities in naive Bayes from skipping smoothing, or multiplying probabilities instead of summing logs.
  • Trusting naive Bayes probabilities as calibrated risks.
  • Running kernel SVMs on hundreds of thousands of rows; use linear SVMs, kernel approximations or gradient boosting instead.
  • Ignoring class imbalance: all three can be dominated by the majority class; use class weights (class_weight="balanced" for SVMs) or appropriate priors and metrics.

In practice

  • For text classification, start with TF-IDF features and a linear model (logistic regression or a linear SVM) plus multinomial naive Bayes as a baseline; then compare with fine-tuned language-model embeddings.
  • For small tabular datasets with complex boundaries, a scaled RBF SVM tuned over CC and γ\gamma can still compete; on larger tabular data, gradient-boosted trees usually win.
  • kNN's modern life is retrieval: with learned embeddings and approximate nearest-neighbour indexes, nearest-neighbour search powers semantic search, recommendation and RAG systems (embeddings-and-rag).
  • Always compare against a logistic regression baseline under the same pipeline and cross-validation scheme, and report variability across folds.
  • When a stakeholder needs probabilities, calibrate the chosen model and check calibration on held-out data (model-evaluation-and-metrics).

Key takeaways

  • kNN stores the data and votes among the k closest points; it needs scaled features and suffers in high dimensions unless the data lie near a low-dimensional structure.
  • In high dimensions neighbourhoods stop being local and distances concentrate, which is the curse of dimensionality.
  • Naive Bayes multiplies per-feature likelihoods under a conditional-independence assumption; it is fast and strong for text but its probabilities are usually overconfident.
  • SVMs maximise the margin; hinge loss makes the solution depend only on support vectors, and C trades margin width against violations.
  • Kernels let SVMs learn non-linear boundaries through dot products; the RBF gamma controls each support vector's reach and must be tuned jointly with C.
  • Distance-based methods can lose tens of accuracy points without feature scaling inside a pipeline.

Terms in this lesson

k-nearest neighbours
A classifier that predicts the majority label (or mean target) of the k training points closest to the query.
Minkowski distance
The p-th root of the sum of absolute coordinate differences raised to the power p; p=2 is Euclidean and p=1 is Manhattan distance.
Curse of dimensionality
The collection of effects by which data become sparse and distances less informative as the number of dimensions grows.
Bayes error rate
The lowest error rate achievable by any classifier on a given data distribution.
Naive Bayes
A probabilistic classifier that applies Bayes' theorem assuming features are independent given the class.
Laplace smoothing
Adding a pseudo-count to every feature-class count so that unseen features never receive zero probability.
Margin
The distance from a classifier's decision boundary to the closest training points; for a scaled SVM it is 1/||w||, and the full width between the two margin hyperplanes is 2/||w||.
Hinge loss
The loss max(0, 1 - y f(x)), zero for points classified correctly with functional margin at least 1.
Support vectors
The training points on or inside the margin that alone determine an SVM's decision boundary.
Kernel trick
Computing dot products in a high-dimensional feature space through a kernel function, without constructing the feature vectors.

Check your understanding

  1. 1.

    kNN with Euclidean distance is applied to features 'age in years' and 'annual income in dollars' without scaling. What happens?

  2. 2.

    Data are uniform in a 10-dimensional unit hypercube. What edge length must a sub-cube have to contain 1% of the data on average?

  3. 3.

    In a naive Bayes spam filter without smoothing, the word 'invoice' never appeared in any spam email during training. What happens when a new email containing 'invoice' is classified?

  4. 4.

    An SVM has w = (3, 4) and b = -2. What is the margin width 2/||w||, and what is the hinge loss for the point x = (1, 0) with label y = +1?

  5. 5.

    Your RBF SVM reaches 100% training accuracy but 70% cross-validated accuracy. Which change is most likely to help?

Exercises

Work each problem before opening the solution.

  1. Exercise 1Core

    Using the training points from the lesson, A(1,1)A(1,1) red, B(2,3)B(2,3) red, C(4,4)C(4,4) blue, D(5,3)D(5,3) blue, E(3,6)E(3,6) blue, classify the query q=(2,2)q = (2, 2) with k=1k = 1 and k=3k = 3 under Euclidean distance, and with k=3k = 3 under Manhattan distance.

    Show solution

    Euclidean distances from (2,2)(2,2): A=1+1=1.41A = \sqrt{1 + 1} = 1.41; B=0+1=1.00B = \sqrt{0 + 1} = 1.00; C=4+4=2.83C = \sqrt{4 + 4} = 2.83; D=9+1=3.16D = \sqrt{9 + 1} = 3.16; E=1+16=4.12E = \sqrt{1 + 16} = 4.12.

    • k=1k = 1: nearest is B, so red.
    • k=3k = 3: B, A, C give red, red, blue, so red (probability 2/3).

    Manhattan distances: A=2A = 2, B=1B = 1, C=4C = 4, D=4D = 4, E=5E = 5. The three nearest are B (1), A (2), and then C and D tie at 4. Either way the vote is two red to one blue, so red. (Ties at the boundary of the neighbourhood are broken by implementation details, e.g. training order in scikit-learn; another reason to prefer settings that avoid them.)

  2. Exercise 2Core

    Using the lesson's spam table (Laplace smoothing α=1\alpha = 1, priors 0.4 spam / 0.6 ham), compute P(spam∣"free report")P(\text{spam} \mid \text{"free report"}). Explain the result in terms of the per-word likelihood ratios.

    Show solution

    Spam score: 0.4×P(free∣spam)×P(report∣spam)=0.4×0.450×0.150=0.02700.4 \times P(\text{free}\mid\text{spam}) \times P(\text{report}\mid\text{spam}) = 0.4 \times 0.450 \times 0.150 = 0.0270.

    Ham score: 0.6×(3/28)×(13/28)=0.6×0.1071×0.4643=0.02980.6 \times (3/28) \times (13/28) = 0.6 \times 0.1071 \times 0.4643 = 0.0298.

    P(spam∣email)=0.0270/(0.0270+0.0298)=0.475P(\text{spam} \mid \text{email}) = 0.0270 / (0.0270 + 0.0298) = 0.475, so the email is classified as ham, narrowly.

    Likelihood ratios (spam : ham): "free" 0.450/0.107=4.20.450 / 0.107 = 4.2; "report" 0.150/0.464=0.320.150 / 0.464 = 0.32. Their product is about 1.36, which favours spam, but the prior odds are 0.4/0.6=0.670.4/0.6 = 0.67. Posterior odds =0.67×1.36=0.91<1= 0.67 \times 1.36 = 0.91 < 1, consistent with 0.475.

  3. Exercise 3Core

    Write a scikit-learn script that tunes a kNN classifier on the wine dataset over k∈{1,3,5,7,9,15,25}k \in \{1, 3, 5, 7, 9, 15, 25\}, uniform vs distance weighting, and Manhattan vs Euclidean distance, with scaling inside the pipeline and a held-out test set. Report the best settings, CV accuracy and test accuracy.

    Show solution
    from sklearn.datasets import load_wine
    from sklearn.model_selection import GridSearchCV, train_test_split
    from sklearn.pipeline import make_pipeline
    from sklearn.preprocessing import StandardScaler
    from sklearn.neighbors import KNeighborsClassifier
    
    X, y = load_wine(return_X_y=True)
    X_tr, X_te, y_tr, y_te = train_test_split(X, y, stratify=y, random_state=0)
    pipe = make_pipeline(StandardScaler(), KNeighborsClassifier())
    grid = {"kneighborsclassifier__n_neighbors": [1, 3, 5, 7, 9, 15, 25],
            "kneighborsclassifier__weights": ["uniform", "distance"],
            "kneighborsclassifier__p": [1, 2]}            # p=1 Manhattan, p=2 Euclidean
    search = GridSearchCV(pipe, grid, cv=5).fit(X_tr, y_tr)
    print(search.best_params_, "CV acc %.3f" % search.best_score_)
    print("test acc %.3f" % search.score(X_te, y_te))
    

    In our run the search selected Manhattan distance with uniform weights and a fairly large kk (25), with CV accuracy about 0.96 and test accuracy about 0.98. Your exact selection may differ: many settings score within one or two points of each other on a dataset this small, so the tuned values are not strongly determined. Because the scaler sits inside the pipeline, it is re-fit on each training fold and the test set is used only once.

  4. Exercise 4Core

    For the SVM with w=(2,1)w = (2, 1), b=−4b = -4, compute f(x)f(x), the functional margin yf(x)y f(x) and the hinge loss for: (4,0)(4, 0) with y=+1y = +1; (1.5,0.5)(1.5, 0.5) with y=−1y = -1; (1,3)(1, 3) with y=−1y = -1. Which are support vectors (on or inside the margin)?

    Show solution

    f(x)=2x1+x2−4f(x) = 2x_1 + x_2 - 4.

    • (4,0)(4, 0), y=+1y = +1: f=4f = 4, yf=4yf = 4, loss 00. Outside the margin, not a support vector.
    • (1.5,0.5)(1.5, 0.5), y=−1y = -1: f=3+0.5−4=−0.5f = 3 + 0.5 - 4 = -0.5, yf=0.5yf = 0.5, loss 1−0.5=0.51 - 0.5 = 0.5. Correctly classified but inside the margin: a (margin-violating) support vector.
    • (1,3)(1, 3), y=−1y = -1: f=2+3−4=1f = 2 + 3 - 4 = 1, yf=−1yf = -1, loss 22. Misclassified: also a support vector.

    In the soft-margin solution, every point with yf(x)<1y f(x) < 1 (inside the margin or misclassified) is a support vector with dual coefficient αi=C\alpha_i = C; points exactly on the margin (yf(x)=1y f(x) = 1) have 0≤αi≤C0 \le \alpha_i \le C and are support vectors whenever αi>0\alpha_i > 0, which is the usual case. Points with yf(x)>1y f(x) > 1 have αi=0\alpha_i = 0.

  5. Exercise 5Challenge

    (a) Show that for x,z∈R2x, z \in \mathbb{R}^2 the kernel K(x,z)=(x⊤z)2K(x, z) = (x^\top z)^2 equals ϕ(x)⊤ϕ(z)\phi(x)^\top\phi(z) for ϕ(x)=(x12,2x1x2,x22)\phi(x) = (x_1^2, \sqrt2 x_1 x_2, x_2^2). (b) Verify numerically for x=(1,2)x = (1, 2), z=(3,1)z = (3, 1), by hand and with sklearn.metrics.pairwise.polynomial_kernel. (c) For degree qq in dd dimensions, the explicit feature map of (x⊤z)q(x^\top z)^q has (d+q−1q)\binom{d+q-1}{q} monomials. How many for d=100d = 100, q=3q = 3, and what does that say about the kernel trick?

    Show solution

    (a) (x⊤z)2=(x1z1+x2z2)2=x12z12+2x1x2z1z2+x22z22=(x12)(z12)+(2x1x2)(2z1z2)+(x22)(z22)=ϕ(x)⊤ϕ(z)(x^\top z)^2 = (x_1 z_1 + x_2 z_2)^2 = x_1^2 z_1^2 + 2 x_1 x_2 z_1 z_2 + x_2^2 z_2^2 = (x_1^2)(z_1^2) + (\sqrt2 x_1 x_2)(\sqrt2 z_1 z_2) + (x_2^2)(z_2^2) = \phi(x)^\top \phi(z).

    (b) x⊤z=3+2=5x^\top z = 3 + 2 = 5, so K=25K = 25. ϕ(x)=(1,2.828,4)\phi(x) = (1, 2.828, 4), ϕ(z)=(9,4.243,1)\phi(z) = (9, 4.243, 1); dot product =9+12+4=25= 9 + 12 + 4 = 25.

    import numpy as np
    from sklearn.metrics.pairwise import polynomial_kernel
    
    def phi(v):                       # explicit feature map for (x . z)^2 in 2-D
        return np.array([v[0]**2, np.sqrt(2) * v[0] * v[1], v[1]**2])
    
    x, z = np.array([1.0, 2.0]), np.array([3.0, 1.0])
    print(phi(x) @ phi(z))                                           # 25.0
    print(polynomial_kernel(x[None], z[None], degree=2, gamma=1, coef0=0)[0, 0])  # 25.0
    

    (scikit-learn's polynomial kernel is (γx⊤z+c0)q(\gamma x^\top z + c_0)^q; with γ=1\gamma = 1, c0=0c_0 = 0 it is our kernel.)

    (c) (1023)=102×101×1006=171,700\binom{102}{3} = \frac{102 \times 101 \times 100}{6} = 171{,}700 features. The kernel computes the same dot product with one 100-dimensional dot product and a cube, about 100 multiplications instead of building two 171,700-dimensional vectors. For the RBF kernel the feature space is infinite-dimensional, so the kernel trick is the only way to use it. The price is that kernel methods work with the n×nn \times n matrix of pairwise kernel values, which is why they scale poorly with the number of samples.

Further reading

Go further

Sources

Fact-checked and last reviewed 2026-11-05

Stay in the loop
Prometheus OS™ · Native AI OS · Built for the future © 2025 - 2026