The SVM dual and support vectors
Form the SVM dual, identify support vectors via complementary slackness, and read sparsity off KKT.
The SVM primal is a constrained QP over and . The dual is a QP over one multiplier per training example, with a structure that makes two things explicit: the solution is sparse in , and it depends on the data only through inner products[1]. The second fact is what kernel methods exploit.
Forming the dual
Start with the soft-margin primal:
Introduce multipliers for the margin constraints and for the nonnegativity of slack. The Lagrangian is
Stationarity:
So , , and (eliminating ).
Substituting back gives the dual QP:
The data enters only through the inner products , which is the hook the kernel trick will exploit.
Three regimes of (KKT)
The complementary slackness conditions classify every training example into exactly one bucket:
- : the constraint is slack (strict inequality). The point sits outside the margin and contributes nothing to .
- : the constraint is tight, . The point lies exactly on the margin boundary. These are the margin support vectors.
- : the constraint is tight with positive slack, so the point is inside the margin or misclassified. These are the bound support vectors, the noisy or hard-to-classify points.
Only the support vectors have , and only they contribute to . The classifier is sparse in examples: most training points are forgotten after fitting[2].
Recovering and
The weight vector is
a linear combination of the support vectors. The bias is recovered from any margin support vector (): such a point satisfies , so . In practice average across all margin support vectors for stability.
Why the dual is interesting
Three reasons:
- Sparsity: only support vectors matter at prediction time, so the model size is the number of SVs, not the dataset size.
- Kernelization: the dual depends on data only via inner products, so replacing with a kernel lifts the SVM to any reproducing kernel Hilbert space without ever forming the lifted features.
- Algorithms: dual coordinate ascent (SMO) is the basis of LIBSVM and remains competitive on small to medium problems.
Check it on arrays
import numpy as np
rng = np.random.default_rng(0)
# Mildly nonseparable data
n_per = 40
X_pos = rng.normal([1.2, 0.5], scale=0.8, size=(n_per, 2))
X_neg = rng.normal([-1.2, -0.5], scale=0.8, size=(n_per, 2))
X = np.vstack([X_pos, X_neg])
y = np.array([1.0] * n_per + [-1.0] * n_per)
def svm_dual(X, y, C=1.0, lr=1e-3, steps=20000):
K = X @ X.T
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.clip(alpha, 0, C)
# project onto sum(y * alpha) = 0
s = (y @ alpha) / (y @ y)
alpha -= s * y
alpha = np.clip(alpha, 0, C)
return alpha
C = 1.0
alpha = svm_dual(X, y, C=C)
w = (alpha * y) @ X
margin_sv = (alpha > 1e-4) & (alpha < C - 1e-4)
b = np.mean(y[margin_sv] - X[margin_sv] @ w)
# Classify and check the three buckets
n_zero = int(np.sum(alpha < 1e-4))
n_margin = int(np.sum(margin_sv))
n_bound = int(np.sum(alpha > C - 1e-4))
print(f"alpha = 0 : {n_zero} / {len(y)} (outside margin)")
print(f"0 < alpha < C : {n_margin} (on margin)")
print(f"alpha = C : {n_bound} (inside margin or misclassified)")
print(f"accuracy: {np.mean(np.sign(X @ w + b) == y):.3f}")
print(f"margin = 1/||w||: {1.0 / np.linalg.norm(w):.3f}")The training set splits cleanly into the three KKT buckets; only the support vectors (margin + bound) shape the decision boundary.
Exercises
Exercise 1. A soft-margin SVM with returns multipliers on four training points. Classify each point into its KKT bucket and state which points contribute to .
Solution
Step 1. Compare each against the endpoints and :
- : the margin constraint is slack; the point is outside the margin and contributes nothing to .
- : a bound support vector; the point is inside the margin or misclassified.
- , strictly between and : a margin support vector, lying exactly on the margin boundary .
- : outside the margin, like point 1.
Step 2. Since , only points with enter the sum. Points 2 and 3 contribute; points 1 and 4 do not. Of these, only the margin support vector (point 3) can be used to recover .
Exercise 2. Given the optimal dual solution and a margin support vector (with ), derive the bias .
Solution
Step 1. A margin support vector satisfies the tight constraint . Multiply both sides by and use :
Step 2. Substitute the stationarity expression , so :
Step 3. Solve for :
The inner products are the only way the data enters, which is the exact hook the kernel trick replaces with . Averaging this over all margin support vectors reduces the numerical noise in .
Exercise 3 (code). Run the dual coordinate-ascent on a mildly nonseparable dataset and verify the equality constraint holds and that the three KKT buckets partition all training points.
Solution
import numpy as np
rng = np.random.default_rng(0)
n_per = 40
X = np.vstack([rng.normal([1.2, 0.5], 0.8, (n_per, 2)),
rng.normal([-1.2, -0.5], 0.8, (n_per, 2))])
y = np.array([1.0] * n_per + [-1.0] * n_per)
def svm_dual(X, y, C=1.0, lr=1e-3, steps=20000):
Q = (y[:, None] * y[None, :]) * (X @ X.T)
alpha = np.zeros(len(y))
for _ in range(steps):
grad = 1 - Q @ alpha
alpha = np.clip(alpha + lr * grad, 0, C)
alpha -= (y @ alpha) / (y @ y) * y # project onto sum(y*alpha)=0
alpha = np.clip(alpha, 0, C)
return alpha
C = 1.0
alpha = svm_dual(X, y, C)
assert abs(y @ alpha) < 1e-2 # equality constraint
n_zero = int(np.sum(alpha < 1e-4))
n_margin = int(np.sum((alpha > 1e-4) & (alpha < C - 1e-4)))
n_bound = int(np.sum(alpha > C - 1e-4))
print(f"buckets: zero={n_zero}, margin={n_margin}, bound={n_bound}")
assert n_zero + n_margin + n_bound == len(y) # exact partition
w = (alpha * y) @ X
sv = alpha > 1e-4
b = np.mean(y[sv] - X[sv] @ w)
acc = np.mean(np.sign(X @ w + b) == y)
print(f"accuracy: {acc:.3f}")
assert acc > 0.85The equality constraint holds to within solver tolerance, the three buckets account for every one of the 80 points, and the recovered classifier separates most of the overlapping data. Only the margin and bound support vectors carried any of that information.
The next lesson exploits the dual's inner-product structure to lift the SVM into nonlinear feature spaces via the kernel trick.
References
- [1]Cortes, C., & Vapnik, V. (1995). "Support-vector networks". Machine Learning, 20(3), 273-297. Derives the dual QP and the inner-product structure (doi:10.1007/BF00994018).↩
- [2]Vapnik, V. N. (1998). Statistical Learning Theory. Wiley. Treats the KKT conditions and support-vector sparsity in depth.↩