AI Knowledge Tree

Hard-margin SVM

Define the geometric margin and derive the hard-margin SVM as a quadratic program.

⏱ 30 minPrerequisites:The perceptronThe KKT conditions

The perceptron settles for any separating hyperplane. The support vector machine (SVM) demands a particular one: the hyperplane that maximizes the distance to the nearest training point[1]. This maximum-margin formulation turns into a convex quadratic program, with a clean geometric story.

The margin

For a hyperplane {x:w⊤x+b=0}\{x : w^\top x + b = 0\} with unit normal w^=w/∥w∥\hat w = w / \lVert w \rVert, the signed distance from a point xx to the hyperplane is (w⊤x+b)/∥w∥(w^\top x + b)/\lVert w\rVert.

For labels yi∈{−1,+1}y_i \in \{-1, +1\}, the geometric margin of a point is

γi=yi (w⊤xi+b)∥w∥.\gamma_i = \frac{y_i\,(w^\top x_i + b)}{\lVert w\rVert}.

It is positive when the point is on the correct side, negative otherwise. The margin of the dataset is γ=min⁡iγi\gamma = \min_i \gamma_i, the distance to the closest point.

The maximum-margin problem

Maximizing the dataset margin subject to all points being correctly classified:

max⁡w,b  γs.t.yi(w⊤xi+b)∥w∥≥γ, ∀i.\max_{w, b}\; \gamma \quad \text{s.t.} \quad \frac{y_i (w^\top x_i + b)}{\lVert w\rVert} \ge \gamma,\ \forall i.

The objective is invariant under positive scaling of (w,b)(w, b), so fix the scale by requiring the closest point to satisfy yi(w⊤xi+b)=1y_i(w^\top x_i + b) = 1. Then γ=1/∥w∥\gamma = 1/\lVert w\rVert, and maximizing γ\gamma is minimizing ∥w∥\lVert w\rVert.

The scaling invariance is worth pinning down, because two "margins" are easy to conflate. The functional margin yi(w⊤xi+b)y_i(w^\top x_i + b) is the raw score; the geometric margin divides it by ∥w∥\lVert w\rVert and is the actual distance. Take the hyperplane w=(1,0)w = (1, 0), b=0b = 0 and a point at x=(2,0)x = (2, 0): the functional margin is 22 and the geometric margin is 2/∥w∥=22/\lVert w\rVert = 2, a real distance of two units. Rescale to w=(5,0)w = (5, 0) (same hyperplane, the line x1=0x_1 = 0 is unchanged): the functional margin balloons to 1010, but the geometric margin is 10/5=210/5 = 2, unchanged. Only the geometric margin means anything; the functional one can be inflated to any value by scaling ww. Pinning the closest functional margin to 11 removes that freedom, after which γ=1/∥w∥\gamma = 1/\lVert w\rVert reads off the geometric margin directly: ∥w∥=2\lVert w\rVert = 2 is a margin of 0.50.5, ∥w∥=0.5\lVert w\rVert = 0.5 is a margin of 22.

The hard-margin SVM

min⁡w,b  12∥w∥2s.t.yi(w⊤xi+b)≥1, ∀i.\min_{w, b}\; \tfrac{1}{2}\lVert w\rVert^2 \quad \text{s.t.}\quad y_i(w^\top x_i + b) \ge 1,\ \forall i.

The factor 12\tfrac{1}{2} is for derivative cleanness. The objective is convex quadratic and the constraints are affine, so this is a convex quadratic program (QP). It has a unique global solution whenever the data are linearly separable; otherwise the constraint set is empty and the problem is infeasible (which is what the next lesson fixes with slack).

KKT conditions and support vectors

Form the Lagrangian with multipliers αi≥0\alpha_i \ge 0:

L=12∥w∥2−∑iαi(yi(w⊤xi+b)−1).\mathcal{L} = \tfrac{1}{2}\lVert w\rVert^2 - \sum_i \alpha_i\big(y_i(w^\top x_i + b) - 1\big).

Stationarity gives

w=∑iαiyixi,∑iαiyi=0.w = \sum_i \alpha_i y_i x_i, \qquad \sum_i \alpha_i y_i = 0.

The first equation says the optimal ww is a linear combination of the training points (with yiy_i-signed weights). Complementary slackness says αi=0\alpha_i = 0 unless the margin constraint is tight, i.e. yi(w⊤xi+b)=1y_i(w^\top x_i + b) = 1.

Points with αi>0\alpha_i > 0 sit exactly on the margin boundary. They are the support vectors, and they are the only data points that contribute to ww. Everything else is irrelevant to the classifier; this is the SVM's celebrated sparsity-in-examples property[2].

What hard-margin can't handle

Three failure modes drive the next lessons:

  • Nonseparable data. No feasible (w,b)(w, b) exists. Soft margin (next lesson) introduces slack.
  • Outliers near the boundary. A single bad label forces the entire hyperplane to compensate, hurting generalization. Soft margin also fixes this.
  • Nonlinear separators. The decision boundary is forced to be linear in xx. The kernel trick lifts the problem to a higher-dimensional space where linear separation suffices.
The hard-margin SVM is the widest band separating two linearly separable classes; only the support vectors (circled) touching the margin determine it.
The hard-margin SVM is the widest band separating two linearly separable classes; only the support vectors (circled) touching the margin determine it.

Check it on arrays

python · numpy
import numpy as np

rng = np.random.default_rng(0)

# Linearly separable 2D data
n_per = 30
X_pos = rng.normal(loc=[2.5, 1.0], scale=0.6, size=(n_per, 2))
X_neg = rng.normal(loc=[-1.0, -1.0], scale=0.6, size=(n_per, 2))
X = np.vstack([X_pos, X_neg])
y = np.array([1.0] * n_per + [-1.0] * n_per)

# Hard-margin SVM via projected gradient on the dual:
#   max sum(alpha) - 0.5 alpha^T (y y^T * K) alpha   s.t. alpha >= 0, sum(y * alpha) = 0
def hard_margin_dual(X, y, lr=1e-3, steps=20000):
    K = X @ X.T                           # linear kernel
    Q = (y[:, None] * y[None, :]) * K
    n = len(y)
    alpha = np.zeros(n)
    for _ in range(steps):
        grad = 1 - Q @ alpha
        alpha = alpha + lr * grad
        alpha = np.maximum(alpha, 0)      # alpha >= 0
        # project to satisfy sum(y * alpha) = 0
        alpha -= y * (y @ alpha) / (y @ y)
        alpha = np.maximum(alpha, 0)
    return alpha

alpha = hard_margin_dual(X, y)

# Recover w and b from the support vectors
w = (alpha * y) @ X
sv_idx = np.where(alpha > 1e-4)[0]
print(f"support vectors: {len(sv_idx)} / {len(y)}")
b = np.mean(y[sv_idx] - X[sv_idx] @ w)
print(f"w = {w}, b = {b:.3f}")

# Margin and accuracy
margin = 1.0 / np.linalg.norm(w)
print(f"geometric margin = {margin:.3f}")
acc = np.mean(np.sign(X @ w + b) == y)
print(f"training accuracy = {acc:.3f}")

# The decision boundary passes between the two classes
print("min positive margin:", np.min(y * (X @ w + b)))

The dual ascent identifies a handful of support vectors; the recovered weight vector classifies every training point correctly with a positive margin.

Exercises

Exercise 1. Two points lie on a line through the origin: x+=(1,1)x_+ = (1, 1) with y+=+1y_+ = +1 and x−=(−1,−1)x_- = (-1, -1) with y−=−1y_- = -1. Find the hard-margin solution (w,b)(w, b) and the geometric margin, by enforcing that both points sit on the margin boundary.

Solution

Step 1. By symmetry the separating hyperplane passes through the origin, so b=0b = 0, and ww points along x+−x−=(2,2)x_+ - x_- = (2, 2), i.e. w=(a,a)w = (a, a) for some a>0a > 0.

Step 2. The scale-fixing convention sets the closest functional margin to 11. At x+x_+: y+(w⊤x++b)=a+a=2a=1y_+(w^\top x_+ + b) = a + a = 2a = 1, so a=1/2a = 1/2 and w=(1/2,1/2)w = (1/2, 1/2).

Step 3. The geometric margin is

γ=1∥w∥=1(1/2)2+(1/2)2=11/2=2.\gamma = \frac{1}{\lVert w\rVert} = \frac{1}{\sqrt{(1/2)^2 + (1/2)^2}} = \frac{1}{1/\sqrt2} = \sqrt2.

Both points are 2\sqrt2 from the line x1+x2=0x_1 + x_2 = 0, which matches: the distance from (1,1)(1,1) to that line is ∣1+1∣/2=2|1+1|/\sqrt2 = \sqrt2.

Exercise 2. From the stationarity condition w=∑iαiyixiw = \sum_i \alpha_i y_i x_i and ∑iαiyi=0\sum_i \alpha_i y_i = 0, prove that the squared norm equals ∥w∥2=∑iαi\lVert w\rVert^2 = \sum_i \alpha_i, so the squared margin is 1/∑iαi1/\sum_i \alpha_i. (Use that for support vectors yi(w⊤xi+b)=1y_i(w^\top x_i + b) = 1.)

Solution

Step 1. Expand ∥w∥2=w⊤w\lVert w\rVert^2 = w^\top w using w=∑iαiyixiw = \sum_i \alpha_i y_i x_i:

∥w∥2=∑iαiyi xi⊤w=∑iαiyi(w⊤xi).\lVert w\rVert^2 = \sum_i \alpha_i y_i\, x_i^\top w = \sum_i \alpha_i y_i (w^\top x_i).

Step 2. Only support vectors have αi>0\alpha_i > 0, and each satisfies the tight constraint yi(w⊤xi+b)=1y_i(w^\top x_i + b) = 1, hence yi w⊤xi=1−yiby_i\, w^\top x_i = 1 - y_i b. Substitute:

∥w∥2=∑iαi(1−yib)=∑iαi−b∑iαiyi.\lVert w\rVert^2 = \sum_i \alpha_i (1 - y_i b) = \sum_i \alpha_i - b\sum_i \alpha_i y_i.

Step 3. The second sum is zero by the stationarity condition ∑iαiyi=0\sum_i \alpha_i y_i = 0, so ∥w∥2=∑iαi\lVert w\rVert^2 = \sum_i \alpha_i. The geometric margin is γ=1/∥w∥\gamma = 1/\lVert w\rVert, so γ2=1/∥w∥2=1/∑iαi\gamma^2 = 1/\lVert w\rVert^2 = 1/\sum_i \alpha_i. A larger total multiplier mass means a smaller margin.

Exercise 3 (code). Take the two points of Exercise 1, run the projected- gradient dual ascent, and confirm it recovers w=(1/2,1/2)w = (1/2, 1/2), b=0b = 0, a margin of 2\sqrt2, and equal multipliers α+=α−\alpha_+ = \alpha_-.

Solution
python · numpy
import numpy as np

X = np.array([[1.0, 1.0], [-1.0, -1.0]])
y = np.array([1.0, -1.0])

def hard_margin_dual(X, y, lr=1e-3, steps=50000):
    K = X @ X.T
    Q = (y[:, None] * y[None, :]) * K
    alpha = np.zeros(len(y))
    for _ in range(steps):
        grad = 1 - Q @ alpha
        alpha = np.maximum(alpha + lr * grad, 0)
        alpha -= y * (y @ alpha) / (y @ y)     # project onto sum(y*alpha)=0
        alpha = np.maximum(alpha, 0)
    return alpha

alpha = hard_margin_dual(X, y)
w = (alpha * y) @ X
sv = alpha > 1e-4
b = np.mean(y[sv] - X[sv] @ w)
margin = 1.0 / np.linalg.norm(w)

print(f"w = {w}, b = {b:.3f}, margin = {margin:.4f}, alpha = {alpha}")
assert np.allclose(w, [0.5, 0.5], atol=1e-2)
assert np.isclose(margin, np.sqrt(2), atol=1e-2)
assert np.isclose(alpha[0], alpha[1], atol=1e-3)   # symmetric multipliers

The dual reproduces the hand solution: equal multipliers on the two symmetric support vectors, weight (1/2,1/2)(1/2, 1/2), and margin 2≈1.414\sqrt2 \approx 1.414.

The next lesson relaxes the hard constraints with slack variables so the SVM survives noisy and nonseparable data.


References

  1. [1]Boser, B. E., Guyon, I. M., & Vapnik, V. N. (1992). "A training algorithm for optimal margin classifiers". COLT. Introduces the maximum-margin classifier (doi:10.1145/130385.130401).↩
  2. [2]Vapnik, V. N. (1998). Statistical Learning Theory. Wiley. The foundational treatment of margins, support vectors, and generalization.↩