Support Vector Machines and Support Vector Regression › Visualizing Support Vector Machines
Machine Learning from ScratchAll chapters 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
The following cell generates a small two-dimensional toy dataset and fits a large- 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 and . We map 0 to and 1 to when calculating functional margins.
PythonIn [3]Expand
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]Expand

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]Expand
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.0VisualizationOut [6]Expand

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 , where and . All three approach zero for very confident correct predictions, but the hinge loss transitions sharply at ; its flat region for 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]Expand
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]Expand

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]Expand
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]Expand


ConsoleOut [11]Expand
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 -insensitive loss is zero for 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]Expand
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]Expand

An SVR fit shows the tube explicitly. The band of width 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]Expand
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]Expand

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!