The Soft Margin SVM
Page 1203 built the hard margin SVM and page 1202 showed how it fails: move one negative point to and there is no feasible solution at all. Real data is not separable, and a method that returns nothing is not a method.
Section 12.2.4’s repair is one new variable per example.
One slack variable per example
Section titled “One slack variable per example”Give each example–label pair a slack variable , subtract it from the margin requirement, and charge for it in the objective:
is how far example falls short of its own margin, measured in the units where the margin is . Three regimes, and the book names only two of them:
| where the example is | |
|---|---|
| correctly classified, at or beyond its margin — it costs nothing | |
| correct side of the hyperplane, but inside the margin | |
| wrong side of the hyperplane |
is exactly the boundary between the last two, because at the optimum and means the example sits on the hyperplane.
C is the price of a violation
Section titled “C is the price of a violation”The data below is page 1202’s impossible case: the running example plus a negative point at , which is the average of and and therefore sits strictly inside the positive class’s convex hull. No hyperplane can classify it correctly. The hard margin problem has no answer; the soft margin problem always has one.
import numpy as np
from scipy.optimize import minimize
X = np.array([[3.0, 1.0], [3.0, -1.0], [6.0, 1.0], [6.0, -1.0],
[1.0, 0.0], [0.0, 1.0], [0.0, -1.0], [-1.0, 0.0]])
Y = np.array([ 1.0, 1.0, 1.0, 1.0, -1.0, -1.0, -1.0, -1.0])
XB = np.vstack([X, [4.5, 0.0]]) # the intruder
YB = np.r_[Y, -1.0]
def pred(scores):
"""Section 12.1's rule: f(x) >= 0 is class +1, ties included."""
return np.where(scores >= 0.0, 1.0, -1.0)
def solve(XX, YY, C, seed):
n, d = XX.shape
rg = np.random.default_rng(seed)
v0 = np.r_[rg.normal(0, 1, d), rg.normal(0, 1),
np.abs(rg.normal(1, 0.5, n))]
return minimize(lambda v: 0.5 * v[:d] @ v[:d]
+ C * np.maximum(v[d + 1:], 0.0).sum(), v0,
constraints=[
{"type": "ineq",
"fun": lambda v: YY * (XX @ v[:d] + v[d]) - 1 + v[d + 1:]},
{"type": "ineq", "fun": lambda v: v[d + 1:]}],
method="SLSQP", options={"maxiter": 20000, "ftol": 1e-13})
def best(XX, YY, C, tries=30):
out, bv = None, np.inf
d = XX.shape[1]
for s in range(tries):
r = solve(XX, YY, C, s)
if not r.success:
continue
w, b = r.x[:d], r.x[d]
xi = np.maximum(0.0, 1 - YY * (XX @ w + b)) # the tight slack
val = 0.5 * w @ w + C * xi.sum()
if val < bv - 1e-12:
bv, out = val, (w.copy(), float(b), xi)
return outprint(f"{'C':>9} {'margin':>9} {'sum xi':>9} {'xi>0':>6} {'xi>1':>6} "
f"{'wrong':>6} {'0.5||w||^2':>12} {'objective':>12}")
for C in (0.01, 0.1, 0.5, 1.0, 2.0, 10.0, 100.0, 1000.0):
w, b, xi = best(XB, YB, C)
nw = np.linalg.norm(w)
print(f"{C:>9.2f} {1/nw:>9.4f} {xi.sum():>9.4f} "
f"{int((xi > 1e-6).sum()):>6} {int((xi > 1 + 1e-6).sum()):>6} "
f"{int((pred(XB @ w + b) != YB).sum()):>6} "
f"{0.5*nw**2:>12.4f} {0.5*nw**2 + C*xi.sum():>12.4f}") C margin sum xi xi>0 xi>1 wrong 0.5||w||^2 objective
0.01 8.0000 6.4375 8 4 4 0.0078 0.0722
0.10 3.0000 3.8333 4 1 1 0.0556 0.4389
0.50 3.0000 3.8333 4 1 1 0.0556 1.9722
1.00 2.0000 3.7500 4 1 1 0.1250 3.8750
2.00 1.0000 3.5000 1 1 1 0.5000 7.5000
10.00 1.0000 3.5000 1 1 1 0.5000 35.5000
100.00 1.0000 3.5000 1 1 1 0.5000 350.5000
1000.00 1.0000 3.5000 1 1 1 0.5000 3500.5000Read the xi>0 and xi>1 columns against each other. At every one of the nine
examples is paying slack and four are on the wrong side; at only the intruder pays anything
at all. The book’s phrase “within the margin or even on the wrong side” covers two populations that
move independently, and the count of violated margins is not the count of errors.
One training error is the floor, not a failure. The intruder is inside the positive hull, so no hyperplane of any kind gets it right. What buys above is nothing: the solution has already stopped moving.
The two limits
Section titled “The two limits”As the slack becomes infinitely expensive, so on separable data the solver is forced back to the hard margin answer:
W_STAR, B_STAR = np.array([1.0, 0.0]), -2.0
print(f"{'C':>9} {'margin':>11} {'sum xi':>11} {'||w - w*||':>12} {'|b - b*|':>11}")
for C in (0.1, 0.5, 1.0, 5.0, 50.0, 500.0, 5000.0):
w, b, xi = best(X, Y, C) # the SEPARABLE running example
print(f"{C:>9.1f} {1/np.linalg.norm(w):>11.6f} {xi.sum():>11.3e} "
f"{np.linalg.norm(w - W_STAR):>12.3e} {abs(b - B_STAR):>11.3e}") C margin sum xi ||w - w*|| |b - b*|
0.1 2.000000 1.500e+00 5.000e-01 1.000e+00
0.5 1.000000 2.139e-08 1.069e-08 3.208e-08
1.0 1.000000 1.332e-15 5.334e-16 0.000e+00
5.0 1.000000 6.217e-14 3.900e-15 2.043e-14
50.0 1.000000 3.997e-15 4.415e-15 1.110e-14
500.0 1.000000 0.000e+00 6.736e-13 7.665e-13
5000.0 1.000000 0.000e+00 1.415e-15 2.220e-15It does not take a large — by the recovery is exact, and is literally zero. On separable data the hard margin solution is already feasible with zero slack, so any above a threshold reproduces it and there is nothing further to gain.
As the opposite happens: slack is free, weight is not, and the model gives up.
C ||w|| margin sum xi wrong
1e-04 0.001250 800.0057 7.9844 4
1e-03 0.012500 80.0001 7.8438 4
1e-02 0.125000 8.0000 6.4375 4
1e-01 0.333333 3.0000 3.8333 1
2e-01 0.333333 3.0000 3.8333 1falls in proportion to and the margin balloons to — a “margin” that contains the entire dataset and means nothing. At exactly, the optimum is and the classifier is constant. So is not a free knob at one end: it is a regularisation parameter and the book names it so, with the warning that its direction is the reverse of the usual convention — “a large value of implies low regularization”, because multiplies the error term rather than the regulariser.
The remark about b, which is not a remark
Section titled “The remark about b, which is not a remark”The book adds, in small type:
In the formulation of the soft margin SVM (12.26a) is regularized, but is not regularized. We can see this by observing that the regularization term does not contain . The unregularized term complicates theoretical analysis and decreases computational efficiency.
That reads as a minor inconvenience. It is the opposite — leaving out is what makes the method invariant to where you put the origin. Shift every example by a vector and the pair is feasible with the same slacks and the same , so the optimal value cannot change. Measured, with :
| penalised | change in objective | |||
|---|---|---|---|---|
| no | ||||
| yes | ||||
| no | ||||
| yes | ||||
| no | ||||
| yes |
With free the objective value is unchanged to machine precision at every , and does not move. Add to the objective and translating the data changes the answer — at the optimal value moves by and drops from to . The penalised model has been told that a boundary near the origin is cheaper, and the origin is wherever somebody happened to put it.
-
pch.quizShowAnswer
C — On the correct side of the hyperplane but inside the margin — since xi = 1 - y f(x), a value below 1 means y f(x) is still positive
-
pch.quizShowAnswer
B — By C = 1, where |b - b*| is exactly zero — the hard margin solution is already feasible with zero slack, so no further increase changes anything
-
pch.quizShowAnswer
B — No — the norm of w collapses in proportion to C, so the 'margin' 1/||w|| balloons while containing the whole dataset. At C = 0 exactly the optimum is w = 0 and the classifier is constant
-
pch.quizShowAnswer
C — Because penalising b would make the solution depend on where the origin sits — measured, shifting the data by (10, 0) leaves the objective unchanged to 1e-15 with b free, and changes it by 10.48 with b penalised
Exercises
Section titled “Exercises”Exercise 1 – Classify the slack into three regimes
Section titled “Exercise 1 – Classify the slack into three regimes”Exercise 2 – Recover the hard margin by raising C
Section titled “Exercise 2 – Recover the hard margin by raising C”Exercise 3 – Translate the data and see what moves
Section titled “Exercise 3 – Translate the data and see what moves”Recall card
Section titled “Recall card”- Real data is not separable, and page 1203’s hard margin problem simply has no answer on it. One negative point inside the positive class’s convex hull is enough.
- Section 12.2.4’s repair is one slack variable per example, subtracted from that example’s margin requirement and charged for in the objective.
- The slack splits into three regimes, and the book names two. Zero means the example costs nothing, between zero and one means inside the margin, above one means on the wrong side — because xi equals one exactly when the example sits on the hyperplane.
- The count of violated margins is not the count of errors. At C = 0.01 all nine examples pay slack and four are wrong; at C = 10 only one pays and one is wrong.
- C is the price of a violation, and a large C means low regularisation — the reverse of the usual convention, because C multiplies the error term rather than the regulariser.
- Above C = 2 on this data the solution stops moving entirely. The objective grows by a factor of ten across the last three rows and the parameters do not change at all.
- On separable data any large enough C recovers the hard margin exactly — by C = 1 the intercept matches to zero and the weights to 5.3e-16.
- As C tends to zero the weights collapse in proportion to it and the margin balloons to 800 while containing the whole dataset. At C = 0 the optimum is the constant classifier.
- Near a degenerate optimum the training-error count is not reproducible. At C = 0.05 the boundary runs exactly through two training points and their labels are decided by a quantity of size 1e-15.
- Leaving b out of the regulariser is what makes the method invariant to translating the data. Shifting by ten leaves the objective unchanged to 1e-15; penalising b changes it by 10.48 and shrinks the weights from 1.0 to 0.8.
- The origin is wherever somebody put it, and a model that prefers boundaries near it is answering a question about the coordinate system.
Next: The Hinge Loss — §12.2.5, the same optimisation problem written without any constraints at all.
pch.coffeeTagline
pch.coffeeCtapch.feedbackHeading
pch.feedbackSubheading