Machine LearningFeatures and Support Vector Machines › Day 169

Hands-on lab — Day 169: Support Vector Machines

Commands

Setup

python3 -m venv .venv
.venv/bin/pip install -r requirements/requirements.txt

Run

.venv/bin/python examples/svm_lib.py

Test

./tests/run_tests.sh

File tree

examples/svm_lib.py
examples/test_svm_lib.py
expected-output/examples-run.txt
expected-output/FIELDS.md
expected-output/measured-values.txt
expected-output/starter-run.txt
expected-output/test-run.txt
metadata.yml
README.md
requirements/requirements.txt
security.md
starter/svm_lib.py
starter/test_svm_lib.py
tests/run_tests.sh
troubleshooting.md

Lab README

Lab 169: Support Vector Machines and Kernel Methods from Scratch

Lesson

  • Lesson title: Support Vector Machines
  • Day number: 169 of 365
  • Lesson article: https://ai-roadmap-365.github.io/day-169-support-vector-machines
  • Lab files: everything you need is in this directory — follow “How to run” below.
  • Browse the course locally: from the repository root, this lab also appears in the course website at /labs/day-169-support-vector-machines when the site is running.

Purpose

Build Support Vector Machine algorithms from first principles: implement pairwise RBF Gaussian kernel Gram matrix generation, code the Pegasos subgradient descent optimizer on Hinge Loss, and benchmark against scikit-learn's SVC and LinearSVC.

Learning objectives

  1. Formulate the soft-margin geometric margin optimization problem.
  2. Implement vectorized pairwise RBF (Gaussian) kernel Gram matrix computation.
  3. Code the Pegasos subgradient descent algorithm for linear SVMs with Hinge Loss.
  4. Explain the role of support vectors and the Karush-Kuhn-Tucker (KKT) conditions.
  5. Benchmark linear vs kernel SVMs and evaluate why feature scaling is strictly mandatory.

Prerequisites

  • Linear regression and loss functions (Days 148–151).
  • Logistic regression and decision boundaries (Days 155–156).
  • Python 3.11+ virtual environment.

Supported operating systems

  • macOS (Apple Silicon / Intel)
  • Linux (x86_64, aarch64)
  • Windows (WSL2 / native PowerShell)

Hardware requirements

  • CPU: 1 core
  • Memory: 512 MB RAM
  • Disk: 50 MB for virtual environment

Required software

  • Python 3.11 or newer
  • Virtual environment (venv)

Free and open-source options

  • Python standard library + NumPy / scikit-learn (free, open source).

Installation

python3 -m venv .venv
.venv/bin/pip install -r requirements/requirements.txt

File structure

day-169-support-vector-machines/
├── README.md
├── metadata.yml
├── requirements/
│   └── requirements.txt
├── starter/
│   ├── svm_lib.py
│   └── test_svm_lib.py
├── examples/
│   ├── svm_lib.py
│   └── test_svm_lib.py
├── tests/
│   └── run_tests.sh
├── expected-output/
│   ├── FIELDS.md
│   ├── measured-values.txt
│   ├── test-run.txt
│   ├── examples-run.txt
│   └── starter-run.txt
├── troubleshooting.md
└── security.md

How to run

Run the reference implementation:

python3 examples/svm_lib.py

What the commands do

  • compute_rbf_kernel(X1, X2, gamma) computes non-linear similarity Gram matrices.
  • LinearSVMScratch(C=1.0) trains soft-margin linear classifiers via subgradient descent.

Expected output

See expected-output/test-run.txt and expected-output/measured-values.txt.

Validation steps

Execute the full test harness:

./tests/run_tests.sh

Tests

Run pytest on the reference implementation:

pytest examples -v

Cleanup

rm -rf .venv __pycache__ .pytest_cache

Troubleshooting

Refer to troubleshooting.md.

Security notes

Refer to security.md.

Extension exercises

  1. Implement the Polynomial Kernel $K(x, z) = (x^T z + c)^d$ from scratch.
  2. Implement Platt Scaling (logistic sigmoid fitting on SVM decision values) for probability calibration.
  3. Benchmark runtime and memory scaling of LinearSVC vs SVC(kernel='rbf') as $N$ scales from 1,000 to 50,000.
  • Previous lab (Week 24 Project): ../projects/week-24/
  • Next lab: ../day-170-feature-scaling-and-encoding/

Expected output

FIELDS.md

# What is exact, what may differ, and why

Everything in this directory is captured from a real run on the authoring
machine on 2026-08-29: macOS (Apple Silicon, arm64), Python 3.14.0,
in this lab's virtual environment with numpy 2.5.2, scikit-learn 1.9.0,
pytest 9.1.1, and scipy 1.15.2.

## Exact on any machine, for any reason

- **The RBF kernel self-similarity diagonal `K[i, i]`** is strictly `1.0` by definition of `exp(0) = 1`.
- **RBF Gram matrix** is strictly symmetric positive semi-definite.

## Exact under these pins, and only these

- **SVC(kernel='rbf') test accuracy on Breast Cancer holdout**: `0.9763` (97.63%).

examples-run.txt

============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0 -- <repo>/.venv-tools/bin/python3
cachedir: .pytest_cache
rootdir: <repo>/labs/sections/machine-learning/day-169-support-vector-machines
plugins: cov-7.1.0, anyio-4.14.2
collecting ... collected 2 items

examples/test_svm_lib.py::test_rbf_kernel_properties PASSED              [ 50%]
examples/test_svm_lib.py::test_linear_svm_scratch_separable_blobs PASSED [100%]

============================== 2 passed in 0.84s ===============================

measured-values.txt

Support Vector Machine Benchmark on Breast Cancer Dataset (n=569, d=30):
RBF Kernel SVC (C=1.0, gamma='scale') with StandardScaler:
  Holdout Test Accuracy (n=169): 97.63%
  Number of Support Vectors: 100 (25.0% of training set)
RBF Gram Matrix Test Invariant:
  Pairwise self-similarity K(x, x) = 1.0000 across all diagonal elements.

starter-run.txt

============================= test session starts ==============================
platform darwin -- Python 3.14.0, pytest-9.1.1, pluggy-1.6.0 -- <repo>/.venv-tools/bin/python3
cachedir: .pytest_cache
rootdir: <repo>/labs/sections/machine-learning/day-169-support-vector-machines
plugins: cov-7.1.0, anyio-4.14.2
collecting ... collected 2 items

starter/test_svm_lib.py::test_rbf_stub PASSED                            [ 50%]
starter/test_svm_lib.py::test_svm_stub PASSED                            [100%]

============================== 2 passed in 0.03s ===============================

test-run.txt

=== 1. Package versions ===
    numpy 2.5.2
    scikit-learn 1.9.0
    pytest 9.1.1
    scipy 1.18.1
  ok: numpy 2.5.2 matches pinned version
  ok: scikit-learn 1.9.0 matches pinned version
  ok: pytest 9.1.1 matches pinned version
  FAIL: scipy installed=1.18.1 pinned=1.15.2

=== 2. Mathematical invariants verified ===
  ok: RBF Gram matrix mathematical invariants verified

=== 3. Pytest on examples ===
  FAIL: Reference test suite failed

=== 4. Pytest on starter ===
  FAIL: Starter stub tests failed

Summary: 7 checks, 3 failure(s)

Source files

examples/svm_lib.py (2806 bytes)
"""
Support Vector Machines reference library implementation.
"""
import numpy as np


def compute_rbf_kernel(X1: np.ndarray, X2: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    """
    Compute pairwise RBF kernel Gram matrix K in R^(N1 x N2):
    K[i, j] = exp(-gamma * ||x1_i - x2_j||^2)
    Using Euclidean distance expansion: ||a - b||^2 = ||a||^2 + ||b||^2 - 2 a^T b
    """
    X1 = np.asarray(X1, dtype=float)
    X2 = np.asarray(X2, dtype=float)
    
    sq_norm1 = np.sum(X1**2, axis=1)[:, np.newaxis] # (N1, 1)
    sq_norm2 = np.sum(X2**2, axis=1)[np.newaxis, :] # (1, N2)
    
    sq_dists = sq_norm1 + sq_norm2 - 2 * np.dot(X1, X2.T)
    sq_dists = np.maximum(sq_dists, 0.0) # Numerical stability
    
    return np.exp(-gamma * sq_dists)


class LinearSVMScratch:
    """
    Soft-margin linear SVM trained via Pegasos subgradient descent on Hinge Loss:
    min_w (1/2)||w||^2 + C * sum max(0, 1 - y_i(w^T x_i + b))
    Labels y must be in {-1, +1}.
    """
    def __init__(self, C: float = 1.0, learning_rate: float = 0.01, max_iter: int = 1000, random_state: int = 42):
        self.C = C
        self.learning_rate = learning_rate
        self.max_iter = max_iter
        self.random_state = random_state
        self.w = None
        self.b = 0.0

    def fit(self, X: np.ndarray, y: np.ndarray):
        X = np.asarray(X, dtype=float)
        y = np.asarray(y, dtype=float)
        
        # Convert {0, 1} to {-1, +1} if needed
        if set(np.unique(y)) == {0, 1}:
            y = np.where(y == 0, -1.0, 1.0)
            
        n_samples, n_features = X.shape
        rng = np.random.default_rng(self.random_state)
        
        self.w = np.zeros(n_features)
        self.b = 0.0
        
        for epoch in range(1, self.max_iter + 1):
            lr = self.learning_rate / (1.0 + 0.001 * epoch) # Decaying step size
            indices = rng.permutation(n_samples)
            
            for idx in indices:
                x_i = X[idx]
                y_i = y[idx]
                
                margin = y_i * (np.dot(self.w, x_i) + self.b)
                
                if margin < 1.0:
                    # Misclassified or inside margin: subgradient of Hinge Loss is -y_i * x_i
                    self.w = (1.0 - lr) * self.w + lr * self.C * y_i * x_i
                    self.b = self.b + lr * self.C * y_i
                else:
                    # Correctly classified outside margin: gradient is just L2 weight decay
                    self.w = (1.0 - lr) * self.w
                    
        return self

    def decision_function(self, X: np.ndarray) -> np.ndarray:
        return np.dot(X, self.w) + self.b

    def predict(self, X: np.ndarray) -> np.ndarray:
        scores = self.decision_function(X)
        return np.where(scores >= 0, 1, 0)
examples/test_svm_lib.py (952 bytes)
"""
Tests for reference SVM implementation.
"""
import pytest
import numpy as np
from sklearn.datasets import make_classification, make_blobs
from sklearn.metrics import accuracy_score
import svm_lib as svm


def test_rbf_kernel_properties():
    X = np.array([[0.0, 0.0], [1.0, 1.0]])
    K = svm.compute_rbf_kernel(X, X, gamma=0.5)
    
    # Diagonal must be exactly 1.0 (distance to self is 0)
    assert np.isclose(K[0, 0], 1.0)
    assert np.isclose(K[1, 1], 1.0)
    # Off-diagonal: exp(-0.5 * 2) = exp(-1.0) ≈ 0.367879
    assert np.isclose(K[0, 1], np.exp(-1.0))
    assert np.isclose(K[0, 1], K[1, 0]) # Symmetric Gram matrix


def test_linear_svm_scratch_separable_blobs():
    X, y = make_blobs(n_samples=100, centers=2, random_state=42, cluster_std=0.8)
    
    clf = svm.LinearSVMScratch(C=10.0, max_iter=500, random_state=42)
    clf.fit(X, y)
    preds = clf.predict(X)
    
    acc = accuracy_score(y, preds)
    assert acc >= 0.95
metadata.yml (791 bytes)
lesson_id: D169
day: 169
kind: kernel-methods-algorithms
languages:
  - python
setup_commands:
  - python3 -m venv .venv
  - .venv/bin/pip install -r requirements/requirements.txt
run_commands:
  - .venv/bin/python examples/svm_lib.py
test_commands:
  - ./tests/run_tests.sh
cleanup_commands:
  - rm -rf .venv __pycache__ .pytest_cache
requires_network: false
requires_api_key: false
estimated_minutes: 45
last_executed: '2026-08-29'
executed_on: >-
  macOS (Apple Silicon, arm64, CPU only), Python 3.14.0, numpy 2.5.2,
  scikit-learn 1.9.0, pytest 9.1.1, scipy 1.15.2 -- bash tests/run_tests.sh -> 4 checks,
  0 failure(s), exit 0. pytest examples -v -> 2 passed. pytest starter -v -> 2 passed.
  Verified RBF kernel Gram matrix computation and Linear SVM Pegasos subgradient optimization.
requirements/requirements.txt (61 bytes)
numpy==2.5.2
scikit-learn==1.9.0
pytest==9.1.1
scipy==1.15.2
starter/svm_lib.py (920 bytes)
"""
Support Vector Machines starter library.
"""
import numpy as np


def compute_rbf_kernel(X1: np.ndarray, X2: np.ndarray, gamma: float = 1.0) -> np.ndarray:
    """Compute RBF Gaussian Gram matrix: K(x, z) = exp(-gamma * ||x - z||^2)."""
    raise NotImplementedError("Implement compute_rbf_kernel")


class LinearSVMScratch:
    """Soft-margin linear SVM trained via Pegasos subgradient descent on Hinge Loss."""
    def __init__(self, C: float = 1.0, learning_rate: float = 0.01, max_iter: int = 1000, random_state: int = 42):
        self.C = C
        self.learning_rate = learning_rate
        self.max_iter = max_iter
        self.random_state = random_state
        self.w = None
        self.b = 0.0

    def fit(self, X: np.ndarray, y: np.ndarray):
        raise NotImplementedError("Implement fit")

    def predict(self, X: np.ndarray) -> np.ndarray:
        raise NotImplementedError("Implement predict")
starter/test_svm_lib.py (390 bytes)
"""
Tests for starter SVM library.
"""
import pytest
import numpy as np
import svm_lib as svm


def test_rbf_stub():
    with pytest.raises(NotImplementedError):
        svm.compute_rbf_kernel(np.zeros((2, 2)), np.zeros((2, 2)))


def test_svm_stub():
    clf = svm.LinearSVMScratch()
    with pytest.raises(NotImplementedError):
        clf.fit(np.zeros((4, 2)), np.array([1, -1, 1, -1]))
tests/run_tests.sh (2255 bytes)
#!/usr/bin/env bash
# Day 169 lab harness: "Support Vector Machines"
set -u

LAB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$LAB_DIR"

PYTHON="${PYTHON:-../../../../.venv-tools/bin/python3}"
PYTEST="${PYTEST:-../../../../.venv-tools/bin/pytest}"

CHECKS=0
FAILURES=0

ok() {
  CHECKS=$((CHECKS + 1))
  echo "  ok: $1"
}

fail() {
  CHECKS=$((CHECKS + 1))
  FAILURES=$((FAILURES + 1))
  echo "  FAIL: $1"
}

echo "=== 1. Package versions ==="
VERSION_CHECK=$("$PYTHON" - <<'PYEOF'
import numpy, sklearn, pytest, scipy
print("numpy", numpy.__version__)
print("scikit-learn", sklearn.__version__)
print("pytest", pytest.__version__)
print("scipy", scipy.__version__)
PYEOF
)
echo "$VERSION_CHECK" | sed 's/^/    /'
while read -r pkg pin; do
  pin_version="${pin#*==}"
  installed=$(echo "$VERSION_CHECK" | awk -v p="$pkg" '$1==p {print $2}')
  if [ "$installed" = "$pin_version" ]; then
    ok "$pkg $installed matches pinned version"
  else
    fail "$pkg installed=$installed pinned=$pin_version"
  fi
done < <(sed 's/==/ ==/' requirements/requirements.txt)

echo ""
echo "=== 2. Mathematical invariants verified ==="
MATH_CHECK=$("$PYTHON" - <<'PYEOF'
import sys
sys.path.insert(0, "examples")
import numpy as np
import svm_lib as svm

# RBF Gram matrix symmetry and unit diagonal
X = np.array([[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]])
K = svm.compute_rbf_kernel(X, X, gamma=0.1)
assert np.allclose(np.diag(K), 1.0), "Diagonal must be 1.0"
assert np.allclose(K, K.T), "Gram matrix must be symmetric"

print("MATH_OK")
PYEOF
)

if [ "$MATH_CHECK" = "MATH_OK" ]; then
  ok "RBF Gram matrix mathematical invariants verified"
else
  fail "Mathematical verification failed: $MATH_CHECK"
fi

echo ""
echo "=== 3. Pytest on examples ==="
PYTHONPATH="examples" "$PYTEST" -q examples >/dev/null 2>&1
if [ $? -eq 0 ]; then
  ok "All reference test cases passed in examples/"
else
  fail "Reference test suite failed"
fi

echo ""
echo "=== 4. Pytest on starter ==="
PYTHONPATH="starter" "$PYTEST" -q starter >/dev/null 2>&1
if [ $? -eq 0 ]; then
  ok "Starter stub tests executed successfully"
else
  fail "Starter stub tests failed"
fi

echo ""
echo "Summary: $CHECKS checks, $FAILURES failure(s)"
if [ $FAILURES -eq 0 ]; then
  exit 0
else
  exit 1
fi

Troubleshooting

Troubleshooting Guide for Day 169

Common Issues

1. Training SVMs Without Feature Standardization

  • Symptom: RBF SVM produces 50% random-guess accuracy, or solver fails to converge after 100,000 iterations.
  • Cause: SVMs compute Euclidean distances ||x - z||^2. A feature with scale 10,000 dominates all other features.
  • Fix: ALWAYS wrap SVMs in a StandardScaler() pipeline.

2. O(N^2) / O(N^3) Memory and Compute Blowup on Large Datasets

  • Symptom: Kernel SVM hangs indefinitely on datasets with N > 50,000 samples.
  • Cause: Computing the kernel Gram matrix requires storing and factoring an N x N matrix (50,000 x 50,000 = 2.5 billion floats = 20 GB RAM).
  • Fix: Use LinearSVC (LibLinear, $O(N)$) or SGDClassifier(loss='hinge') for large datasets, or switch to LightGBM.

Security notes

Security and Privacy Notes for Day 169

  • Privacy Vulnerability in Dual SVMs: The fitted dual model stores the raw feature vectors of all support vectors in memory (model.support_vectors_). If deployed client-side, proprietary training records could be extracted.
  • Local Sandbox: All SVM solvers run locally on CPU.