Support Vector Machines and Support Vector Regression › Visualizing Support Vector Machines

Michael BrenndoerferOctober 26, 202610 min read
Machine Learning from ScratchAll chapters
Part 7 of 15Supervised Learning Beyond Linear Models
Chapter 56Support Vector Machines and Support Vector Regression › Visualizing Support Vector Machines
Reading section 5 of 10 · Visualizing Support Vector Machines · 10 minSection 5 of 10 · 10 min

Section 5 of 10 · 10 min read

Support Vector Machines and Support Vector Regression › Visualizing Support Vector MachinesSection view · Sections in this chapter5 / 10 ↓
Reading tools Definitions, links & notes

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Notes & comments

The following cell generates a small two-dimensional toy dataset and fits a large-CC linear SVM, producing the decision-score field used by the margin figure below. A large penalty can recover a separating solution on this example; it is still a soft-margin SVC fit. The reference estimator uses labels 0 and 1; our signed mathematical labels are −1-1 and +1+1. We map 0 to −1-1 and 1 to +1+1 when calculating functional margins.

PythonIn [3]
CollapseExpand
rng = np.random.default_rng(7)
X_neg = rng.normal(loc=(-1.0, -1.0), scale=0.35, size=(20, 2))
X_pos = rng.normal(loc=(1.0, 1.0), scale=0.35, size=(20, 2))
X_toy = np.vstack([X_neg, X_pos])
y_toy = np.array([0] * 20 + [1] * 20)

svc_demo = SVC(kernel="linear", C=1e4).fit(X_toy, y_toy)

# Precompute the decision-score field for the margin figure
xx, yy = np.meshgrid(np.linspace(-2.5, 2.5, 400), np.linspace(-2.5, 2.5, 400))
scores = svc_demo.decision_function(np.c_[xx.ravel(), yy.ravel()]).reshape(
    xx.shape
)
VisualizationOut [4]
CollapseExpand
Two clusters with a linear SVM decision boundary and margin lines. The two solver support vectors lie on the margins up to numerical tolerance and are circled.
Large-C linear SVM on separated data. The solid line is the zero-score decision boundary, the dashed lines mark scores of minus one and plus one, and circled points are the reference solver's support vectors lying on the margins up to numerical tolerance.

The dual view gives a second way to read the fitted margin. In the large-C fit above, most training points have zero dual coefficient, while the support vectors carry positive coefficient magnitudes. The next cell records those coefficients and the functional margins used by the figure.

PythonIn [5]
CollapseExpand
y_signed_toy = np.where(y_toy == 1, 1.0, -1.0)
f_toy = svc_demo.decision_function(X_toy)
margins_toy = y_signed_toy * f_toy
alpha_full = np.zeros_like(margins_toy)
alpha_full[svc_demo.support_] = np.abs(svc_demo.dual_coef_.ravel())
support_mask = alpha_full > 0.0
VisualizationOut [6]
CollapseExpand
Dual coefficient magnitude versus margin; nonzero values cluster near margin one.
Absolute dual coefficients for the large-C linear SVM on the toy data, plotted against the training functional margin. Nonzero coefficients occur only for support vectors near margin one; the two support vectors have almost identical margins and coefficients, so their positive-coefficient markers overlap at this scale. Non-support points have margins above one and zero coefficients.

The hinge loss makes the margin behavior quantitative. The next figure compares the hinge, logistic, and zero-one losses as functions of the functional margin m=y f(x)m = y\,f(\mathbf{x}), where y∈{−1,+1}y \in \{-1, +1\} and f(x)=w⊤x+bf(\mathbf{x}) = \mathbf{w}^\top \mathbf{x} + b. All three approach zero for very confident correct predictions, but the hinge loss transitions sharply at m=1m = 1; its flat region for m>1m > 1 removes those points from the hinge subgradient. At an optimum, KKT complementarity also forces their dual coefficients to zero. The resulting support set can be sparse; its size depends on the data and fit.

For the plotted zero-one margin loss, a zero-margin tie counts as an error. The scratch prediction code later chooses class 0 at an exactly zero score; tie handling is a stated convention rather than a property shared by every classifier API.

The following cell computes the loss curves that the figure below renders.

PythonIn [7]
CollapseExpand
m_grid = np.linspace(-2.5, 2.5, 500)
hinge_vals = np.maximum(0.0, 1.0 - m_grid)
logistic_vals = np.log1p(np.exp(-m_grid))
zero_one_vals = (m_grid <= 0).astype(float)

The loss curves are computed in the preceding cell and only rendered here.

VisualizationOut [8]
CollapseExpand
Line plot of three loss curves: hinge loss decreases linearly to zero as the margin rises to one and stays zero above one; logistic loss decays smoothly; zero-one is a step.
Hinge, logistic, and zero-one losses as functions of the functional margin. Hinge loss decreases linearly toward zero at margin one and stays flat beyond it, logistic loss decays smoothly, and zero-one is a step function; a point strictly beyond margin one has zero dual coefficient at an optimum, which links the flat hinge region to the sparse support set.

The kernel trick changes nonlinear behavior substantially. The next figures compare linear and RBF SVMs on data with a curved decision boundary and independently seeded label flips at an 8% probability. Both models use C=3, so the penalty is held constant.

PythonIn [9]
CollapseExpand
X_wave = rng.normal(scale=0.9, size=(120, 2))
y_wave = (X_wave[:, 1] ** 2 + X_wave[:, 0] ** 2 > 1.2).astype(int)
noise_rng = np.random.default_rng(71)
flip_mask = noise_rng.random(y_wave.size) < 0.08
y_wave = np.where(flip_mask, 1 - y_wave, y_wave)
lin_svm = SVC(kernel="linear", C=3.0).fit(X_wave, y_wave)
rbf_svm = SVC(kernel="rbf", C=3.0, gamma=2.0).fit(X_wave, y_wave)
noise_count = int(flip_mask.sum())
linear_wave_accuracy = (lin_svm.predict(X_wave) == y_wave).mean()
rbf_wave_accuracy = (rbf_svm.predict(X_wave) == y_wave).mean()
linear_wave_support = lin_svm.support_.size
rbf_wave_support = rbf_svm.support_.size

# Precompute decision-score fields for the kernel comparison figure
xx_wave, yy_wave = np.meshgrid(np.linspace(-3, 3, 400), np.linspace(-3, 3, 400))
Z_lin = lin_svm.decision_function(
    np.c_[xx_wave.ravel(), yy_wave.ravel()]
).reshape(xx_wave.shape)
Z_rbf = rbf_svm.decision_function(
    np.c_[xx_wave.ravel(), yy_wave.ravel()]
).reshape(xx_wave.shape)
wave_limit = max(np.abs(Z_lin).max(), np.abs(Z_rbf).max())
wave_levels = np.linspace(-wave_limit, wave_limit, 21)
VisualizationsOut [10]
CollapseExpand
Nearly uniform negative linear-SVM score field with no zero-score boundary in the displayed rectangle. Both classes of noisy training points are shown.
Linear SVM score field on seeded noisy observations with C=3. The model predicts class 0 throughout the displayed rectangle, so a zero-score decision boundary does not appear; this illustrates the limited flexibility of a linear boundary for radial structure.
RBF SVM score field with a closed main boundary around the inner class and a small separate region near the lower left, on the same noisy points.
RBF SVM score field on the same seeded noisy observations with C=3. The nonlinear boundary follows the main radial structure and also forms a small separate region, showing how the kernel captures curved class geometry.
ConsoleOut [11]
CollapseExpand
flipped labels: 14/120 (0.117)
linear training accuracy: 0.583; support vectors: 104
RBF training accuracy:    0.875; support vectors: 69

The training diagnostics are computed on the same 120 observations with the same penalty C. They show how each hypothesis class fits this noisy example; they do not estimate performance on new data. The RBF fit has higher training accuracy here, and its plot shows a nonlinear boundary around the main radial structure. The linear model predicts one class across the displayed window. The support counts describe the fitted representations; they do not independently establish which model generalizes better. The independent seed for label flips leaves the earlier data generator's stream unchanged for the later SVR example.

The ε\varepsilon-insensitive loss is zero for ∣r∣≤ε|r| \leq \varepsilon and grows linearly outside that interval. The linear tails reduce the influence of large response residuals relative to squared loss, without guaranteeing resistance to high-leverage inputs. The following cell computes the three loss curves used in the figure below.

PythonIn [12]
CollapseExpand
r_grid = np.linspace(-3, 3, 500)
eps_demo = 0.5
eps_loss_vals = np.maximum(0.0, np.abs(r_grid) - eps_demo)
squared_loss_vals = r_grid**2
abs_loss_vals = np.abs(r_grid)
VisualizationOut [13]
CollapseExpand
Line plot of three regression loss curves: epsilon-insensitive is flat between minus epsilon and plus epsilon and linear outside; squared is quadratic; absolute is V-shaped.
Epsilon-insensitive, squared-error, and absolute-error losses. The epsilon-insensitive loss is flat between minus and plus epsilon and grows linearly outside the tube, whereas squared-error rises quadratically and absolute-error is V-shaped; the flat region makes SVR ignore small residuals while the linear tails limit the influence of large ones relative to squared loss.

An SVR fit shows the tube explicitly. The band of width 2ε2\varepsilon surrounds the model prediction, and training points inside it incur zero data-fit loss. The weight penalty remains part of the full objective. The following cell fits the SVR and computes the prediction curve used in the figure below.

PythonIn [14]
CollapseExpand
x_demo = np.sort(rng.uniform(-3.0, 3.0, 60))
y_demo = np.sin(x_demo) + 0.1 * rng.normal(size=x_demo.size)
X_demo = x_demo.reshape(-1, 1)

svr_demo = SVR(kernel="rbf", C=2.0, gamma=0.9, epsilon=0.2).fit(X_demo, y_demo)
x_grid = np.linspace(-3.0, 3.0, 400)
pred_grid = svr_demo.predict(x_grid.reshape(-1, 1))
VisualizationOut [15]
CollapseExpand
Scatter of noisy sine-wave data with a fitted SVR curve and shaded epsilon band around it. Support vectors on or outside the band up to solver tolerance are circled.
Support Vector Regression with an RBF kernel fit to noisy sine-wave data. The shaded band of half-width epsilon surrounds the prediction, points inside it incur zero loss, and circled support vectors lie on or outside the tube up to solver tolerance, showing how the tube controls which observations shape the fit.

Comments

Add to the conversation

A question, a correction, or an example from your own work.

No comments yet. Be the first to share your thoughts!

Newsletter

Stay up to date

Get new articles, book updates, and notes on AI and technology.

Unsubscribe at any time. Privacy policy