Machine Learning › Features and Support Vector Machines › Day 169
Hands-on lab — Day 169: Support Vector Machines
- ← Back to the Day 169 lesson
- Open the hands-on files on GitHub — clone or download them from the public labs repository
- Local path in your clone:
labs/sections/machine-learning/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-machineswhen 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
- Formulate the soft-margin geometric margin optimization problem.
- Implement vectorized pairwise RBF (Gaussian) kernel Gram matrix computation.
- Code the Pegasos subgradient descent algorithm for linear SVMs with Hinge Loss.
- Explain the role of support vectors and the Karush-Kuhn-Tucker (KKT) conditions.
- 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
- Implement the Polynomial Kernel $K(x, z) = (x^T z + c)^d$ from scratch.
- Implement Platt Scaling (logistic sigmoid fitting on SVM decision values) for probability calibration.
- Benchmark runtime and memory scaling of
LinearSVCvsSVC(kernel='rbf')as $N$ scales from 1,000 to 50,000.
Navigation
- 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,000samples. - Cause: Computing the kernel Gram matrix requires storing and factoring an
N x Nmatrix (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.