SVM Part 3: Soft Margin Classifier and the Cost Parameter (C)
SVM Part 3: Soft Margin Classifier and the Cost Parameter (C)
This is Part 3 of the SVM mini-series. It assumes you’ve read Part 2: Maximal Margin Classifier, which ended with a problem: real data is rarely perfectly separable.
1. The problem from Part 2
The Maximal Margin Classifier requires the two classes to be perfectly separable by a straight line, and it is extremely sensitive to outliers. One noisy point can break it entirely.
We need a version of SVM that:
- Tolerates a few misclassified or borderline points, and
- Is more robust — less sensitive to individual noisy data points.
This is exactly what the Soft Margin Classifier (also called the Support Vector Classifier) provides.
Two very different kinds of “not perfectly separable”
It’s worth distinguishing two situations, because the fix for each is different:
Nearly separable (a handful of Totally intermingled (no straight
points cross over, but a straight line, no matter how you draw it,
line still roughly works): can ever separate these):
● ● ● ● ■ ●
╲ ■ (a couple of outliers) ■ ● ■ ●
╲ ● ■ ●
■ ■ ■ ■ ● ■
- Nearly separable — the two classes are mostly on opposite sides, with just a few stray points crossing over. A straight-line hyperplane still makes sense here; it just needs to tolerate a handful of mistakes. This is what the Soft Margin Classifier in this topic solves.
- Totally intermingled — there is no hope for any straight line, no matter how you tilt or shift it, because the classes are thoroughly mixed or arranged in a non-linear pattern (like a ring). Tolerating misclassifications won’t fix this — you need a fundamentally different, non-linear separator. This is what kernels solve, covered in Part 4.
The Soft Margin Classifier’s job is specifically the first case: keep the hyperplane, but relax the “zero mistakes allowed” rule from Part 2.
The modified goal
Instead of Part 2’s strict rule (“find the hyperplane that separates every point perfectly, then maximize the margin”), the goal becomes:
Find the hyperplane that maximizes the margin, while keeping the number and severity of misclassifications as low as possible — but not necessarily zero.
Critically, this formulation does not assume zero misclassifications the way the Maximal Margin Classifier did. Some points are allowed to fall on the wrong side, as long as the total amount of “damage” stays controlled.
2. Step 1: Allow some points to be on the wrong side
Instead of insisting that every point be correctly classified and outside the margin, the Soft Margin Classifier allows some observations to fall inside the margin, or even on the wrong side of the hyperplane entirely.
● ● ●
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ← margin boundary
───────────────── ← hyperplane
┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ ← margin boundary
■ ■ ● ■ ← one ● point allowed inside the margin
By tolerating a few “mistakes” on the training data, the model becomes far more robust on new, unseen data — this is the same overfitting-vs-generalization idea from Bias and Variance.
Only the points close to the hyperplane matter for building it — these are still called support vectors, exactly as in Part 2, but now the definition includes points that violate the margin too.
3. Step 2: The slack variable (epsilon, ε)
Deriving it from Part 2’s constraint
Recall Part 2’s strict constraint for the Maximal Margin Classifier, using the concise W · Yi notation:
li · (W · Yi) ≥ M for every point i
This says every point must be at least a margin’s distance M away, on the correct side — no exceptions. To relax this, introduce a slack variable εi (one per point) and modify the constraint to:
li · (W · Yi) ≥ M · (1 − εi) for every point i
Since εi can range from 0 to +∞, this single formula reproduces every case from the table below automatically:
εi = 0→ right-hand side is exactlyM→ identical to Part 2’s original constraint (correctly classified, at least a margin’s distance away).0 < εi < 1→(1 − εi)is between0and1, so the required distance shrinks belowMbut stays positive → the point can be inside the margin whileli·(W·Yi)is still positive (correct side).εi = 1→ right-hand side becomes0→ the point sits exactly on the hyperplane.εi > 1→ right-hand side goes negative →li·(W·Yi)is allowed to be negative too, meaning the point can fall on the wrong side of the hyperplane entirely (misclassified).
import numpy as np
# Reuse the rescaled W and margin M from Part 2's worked example
W = np.array([-3.18201573, 0.7068946, 0.7073189])
M = 1.0607662006606353
def implied_epsilon(xi, li):
Yi = np.array([1, xi[0], xi[1]])
value = li * np.dot(W, Yi)
# solve li*(W.Yi) = M*(1 - eps) for eps
return 1 - value / M
points = [
(np.array([3, 3]), 1), # a support vector — sits exactly at the margin
(np.array([2.5, 2.5]), 1), # correctly classified but inside the margin
(np.array([1.5, 1.5]), 1), # deep on the wrong side of the hyperplane
]
for xi, li in points:
eps = implied_epsilon(xi, li)
print(f"point={xi} implied epsilon={eps:.4f}")
Running this gives epsilon ≈ 0, ≈ 0.67, and ≈ 2.0 respectively — exactly matching the three regions described above.
The slack variable, summarized
The slack variable tells you where a point sits relative to the margin and the hyperplane:
| Condition | Meaning |
|---|---|
ε = 0 |
Point is correctly classified and outside the margin (perfect) |
0 < ε < 1 |
Point violates the margin but is still on the correct side of the hyperplane |
ε > 1 |
Point is on the wrong side of the hyperplane (misclassified) |
Class ● (correct side, ε=0) ● ● ●
┄┄┄┄┄┄┄┄┄┄┄┄┄ ← margin
● (0 < ε < 1 : inside margin, still correct side)
─────────────────────────────────────── ← hyperplane
┄┄┄┄┄┄┄┄┄┄┄┄┄ ← margin
● (ε > 1 : wrong side entirely)
Class ■ (correct side, ε=0) ■ ■ ■
- Slack is always
≥ 0. - Lower slack is always better.
ε = 0is a perfect classification; largerεmeans a bigger violation.
import numpy as np
def slack_status(epsilon):
if epsilon == 0:
return "correctly classified, outside margin"
elif 0 < epsilon < 1:
return "inside the margin, but correct side"
else:
return "misclassified — wrong side of the hyperplane"
for eps in [0, 0.4, 1.5]:
print(f"epsilon={eps}: {slack_status(eps)}")
4. Step 3: The cost parameter, C
Now compare two different Soft Margin Classifiers by summing the slack of every point:
∑ ε ≤ C
The total allowed “budget” of misclassification across all training points is bounded by a hyperparameter called C (short for cost).
Think of C as a dial:
Small C ←──────────────────────────────→ Large C
(loose budget for slack) (strict budget for slack)
wide margin narrow margin
more misclassifications allowed fewer misclassifications allowed
simpler, more general model complex, tightly-fit model
high bias, low variance low bias, high variance
underfitting risk overfitting risk
This is the exact same bias-variance tradeoff from the Bias and Variance topic, expressed through a single knob:
- Large C → the model tries very hard to classify every training point correctly, allowing only a small total slack. This produces a narrow margin and a model that can overfit — low bias, high variance.
- Small C → the model tolerates a larger total slack, allowing more points to violate the margin. This produces a wide margin and a model that can underfit — high bias, low variance.
A worked example: one outlier ruining the whole model
Here’s a concrete illustration of why forcing zero errors can backfire. Imagine two clearly separated clusters, but one single point is mislabeled — it belongs to class -1 by every reasonable judgment (it sits right at the edge of the -1 cluster), but was recorded in the data as +1.
● ● ●
● ● ●
← if C is large, the boundary bends all
the way down here just to get the
outlier "correct"
■ ■ +(outlier)
■ ■ ■
If C is very large, the model refuses to accept any misclassification — including the outlier — so it contorts the boundary to wrap around and correctly classify that single point. This comes at a cost: the boundary is now in the wrong place for every future point that lands near where the outlier was.
import numpy as np
from sklearn.svm import SVC
X = np.array([
[5, 5], [6, 5], [6, 6], [7, 5], [5, 6], [6, 7], # class +1 cluster
[1, 1], [1, 2], [2, 1], [0, 0], [2, 2], [0, 1], # class -1 cluster
[2.3, 2.3], # mislabeled outlier, recorded as +1
])
y = np.array([1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1])
# A brand-new point that should obviously be classified as -1
future_point = np.array([[2.5, 2.5]])
for C in [1e6, 0.01]:
model = SVC(kernel='linear', C=C)
model.fit(X, y)
train_acc = model.score(X, y)
prediction = model.predict(future_point)
print(f"C={C}: train accuracy={train_acc:.2f}, predicts new point as {prediction[0]} (true label is -1)")
Running this: with C=1e6, the model achieves 100% training accuracy — it successfully “explains” the outlier — but then misclassifies the brand-new point that lands nearby. With C=0.01, training accuracy is lower (it accepts the outlier as a mistake), but the new point is classified correctly.
This is the central lesson of the cost parameter: a model with zero training errors is not necessarily the best model. Tolerating a known mistake on the training set, in exchange for a more sensible overall boundary, usually generalizes far better to unseen data.
5. Step 4: Seeing C in action
from sklearn.svm import SVC
from sklearn.datasets import make_classification
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score
X, y = make_classification(
n_samples=200, n_features=2, n_redundant=0,
n_clusters_per_class=1, flip_y=0.1, random_state=42
)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.3, random_state=42
)
scaler = StandardScaler()
X_train_s = scaler.fit_transform(X_train)
X_test_s = scaler.transform(X_test)
for C in [0.01, 1, 100]:
model = SVC(kernel='linear', C=C)
model.fit(X_train_s, y_train)
train_acc = accuracy_score(y_train, model.predict(X_train_s))
test_acc = accuracy_score(y_test, model.predict(X_test_s))
n_sv = sum(model.n_support_)
print(f"C={C:6.2f} train_acc={train_acc:.3f} test_acc={test_acc:.3f} support_vectors={n_sv}")
You will typically see: very small C gives more support vectors (a wider, looser margin), and very large C gives fewer support vectors (a narrower, tighter margin) — with test accuracy often peaking somewhere in between.
Visualizing the bias-variance curve across C
The pattern is much easier to see by plotting train and test accuracy across a wide range of C values:
import matplotlib.pyplot as plt
Cs = [0.001, 0.01, 0.1, 1, 10, 100, 1000]
train_accs, test_accs = [], []
for C in Cs:
model = SVC(kernel='linear', C=C)
model.fit(X_train_s, y_train)
train_accs.append(accuracy_score(y_train, model.predict(X_train_s)))
test_accs.append(accuracy_score(y_test, model.predict(X_test_s)))
plt.plot(Cs, train_accs, marker='o', label='Train accuracy')
plt.plot(Cs, test_accs, marker='o', label='Test accuracy')
plt.xscale('log')
plt.xlabel('C')
plt.ylabel('Accuracy')
plt.legend()
plt.title('Train vs. test accuracy across C')
plt.show()
Reading the resulting curve tells the whole story of the bias-variance tradeoff:
- Very small
C(left side): both train and test accuracy are low — the model is too simple to even fit the training data well (high bias / underfitting). - A middle range of
C: both accuracies rise together and test accuracy peaks — this is the best generalizing region. - Very large
C(right side): train accuracy keeps climbing toward 100%, but test accuracy plateaus or drops — the growing gap between the two curves is the signature of overfitting (high variance).
This single plot is often the fastest way to sanity-check whether a chosen C is in a sensible range, before running a full GridSearchCV.
6. Step 5: How to choose C in practice
You rarely pick C by intuition alone — you search for the value that gives the best result on held-out data:
from sklearn.model_selection import GridSearchCV
param_grid = {'C': [0.001, 0.01, 0.1, 1, 10, 100, 1000]}
grid = GridSearchCV(SVC(kernel='linear'), param_grid, cv=5, scoring='accuracy')
grid.fit(X_train_s, y_train)
print("Best C:", grid.best_params_)
print("Best cross-validated accuracy:", grid.best_score_)
This connects directly back to the practical fix from Bias and Variance: when you suspect overfitting (train accuracy much higher than test accuracy), try lowering C. When you suspect underfitting (both accuracies low), try raising C.
Optimizing for something other than accuracy
GridSearchCV’s scoring argument doesn’t have to be 'accuracy'. If the two types of mistakes aren’t equally costly — for example, letting a spam email through to the inbox is far less costly than accidentally sending a legitimate email to spam — you can optimize C for a different metric entirely, such as 'recall':
grid_recall = GridSearchCV(SVC(kernel='linear'), param_grid, cv=5, scoring='recall')
grid_recall.fit(X_train_s, y_train)
print("Best C for recall:", grid_recall.best_params_)
The C that maximizes plain accuracy is not necessarily the same C that maximizes recall (or any other metric) — always pick the scoring metric that reflects what actually matters for your problem.
7. What we still can’t do
The Soft Margin Classifier handles data that is almost linearly separable, with some noise. But it still fundamentally draws a straight line (or flat hyperplane). It cannot handle data shaped like this:
● ● ●
● ●
● ■ ■ ■ ●
● ●
● ● ●
No amount of slack tolerance will make a straight line separate a ring shape from a cluster inside it. That is the job of the kernel trick, covered next.
8. Practice questions
- What does a slack variable
ε = 0mean for a data point? - What does
ε > 1mean? - If you increase
C, does the margin get wider or narrower? Does the model become more or less prone to overfitting? - You train an SVM and see train accuracy = 99%, test accuracy = 70%. Should you increase or decrease
C? - In the single-outlier example, why does achieving 100% training accuracy actually make the model worse?
Answers:
- The point is correctly classified and lies outside the margin — a perfect classification with no violation.
- The point is on the wrong side of the hyperplane entirely — it is misclassified.
- Increasing
Cmakes the margin narrower (less slack allowed) and makes the model more prone to overfitting (low bias, high variance). - Decrease
C. The large gap between train and test accuracy signals overfitting (high variance), and a smallerCallows more slack, widening the margin and improving generalization. - Getting the mislabeled outlier “correct” forces the hyperplane to bend away from where it should sensibly sit for the bulk of the data. This distorted boundary then misclassifies new, legitimate points that happen to land near where the outlier was — trading one training-set win for many real-world losses.
9. Summary
- The Soft Margin Classifier allows some training points to violate the margin or be misclassified, controlled by slack variables (ε).
ε = 0is perfect,0 < ε < 1violates the margin but is still correct,ε > 1is misclassified.- The cost parameter C bounds the total allowed slack (
∑ε ≤ C) and directly controls the bias-variance tradeoff. - Large C → narrow margin, less tolerant, risk of overfitting. Small C → wide margin, more tolerant, risk of underfitting.
- Use
GridSearchCV(or similar) to find the bestCfor your data rather than guessing. - The Soft Margin Classifier still only draws straight-line boundaries — non-linear data needs the kernel trick.
Next topic: Kernels and the Kernel Trick — how SVM handles data that no straight line can separate.