Support Vector Machines and Support Vector Regression › Implementation in Scikit-learn
Machine Learning from ScratchAll chapters Support Vector Machines and Support Vector Regression › Implementation in Scikit-learnSection view · Sections in this chapter7 / 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
We build the linear hinge-loss classifier from primitives first, then compare it with scikit-learn's SVC on the same data. Working from first principles helps us see where the subgradient comes from and how sensitive the solution is to feature scaling. We standardize the numerical inputs here so that both optimizers use the same feature representation.
PythonIn [16]Expand
def hinge_objective(X, y_signed, w, b, C):
margins = y_signed * (X @ w + b)
return 0.5 * (w @ w) + C * np.maximum(0.0, 1.0 - margins).sum()
def hinge_subgradient(X, y_signed, w, b, C):
margins = y_signed * (X @ w + b)
active = (1.0 - margins) > 0
dw = w - C * (X[active].T @ y_signed[active])
db = -C * y_signed[active].sum()
return dw, db
def train_linear_svm(X, y_signed, C=1.0, lr=0.05, n_iter=3000):
w = np.zeros(X.shape[1])
b = 0.0
for _ in range(n_iter):
dw, db = hinge_subgradient(X, y_signed, w, b, C)
w = w - lr * dw
b = b - lr * db
return w, bWe reuse the toy two-dimensional dataset from the visualization section. The full toy set acts as training data here; this example has no held-out performance estimate. We compute the means and standard deviations from those training inputs only, then supply the same standardized features to both optimizers. Scaling changes the geometry of the weight penalty and kernel distances. A feature with large numerical units can contribute heavily to a distance calculation, but that does not imply that an SVM must ignore every smaller-scale feature.
PythonIn [17]Expand
mu = X_toy.mean(axis=0)
sd = X_toy.std(axis=0)
X_std = (X_toy - mu) / sd
y_signed = np.where(y_toy == 1, 1.0, -1.0)
w_scratch, b_scratch = train_linear_svm(
X_std, y_signed, C=2.0, lr=0.05, n_iter=3000
)
train_scores = X_std @ w_scratch + b_scratch
train_pred = (train_scores > 0).astype(int)
margins_scratch = y_signed * (X_std @ w_scratch + b_scratch)
n_active = int(((1.0 - margins_scratch) > 0).sum())
train_accuracy_scratch = (train_pred == y_toy).mean()This fixed-step subgradient run uses a budget of 3,000 updates; that budget does not certify convergence to the optimum. We print the training accuracy, the weight norm , and the count of hinge-active examples. The count records strict margin violations, , at the current iterate. It includes misclassified points as well as correctly classified points too close to the boundary.
ConsoleOut [18]Expand
scratch training accuracy: 1.000 scratch ||w||: 1.228 hinge-active examples: 0/40 hinge-active fraction: 0.000
Now compare with the reference SVC, using the same and standardized inputs. Its printed margin width is measured in the standardized feature coordinates supplied to this fit. The reference solver works with the dual quadratic program, while our scratch implementation descends on the primal hinge objective. They target the same normalized hinge objective, but matching training labels alone does not establish that their parameter estimates or objective values agree.
PythonIn [19]Expand
svc_ref = SVC(kernel="linear", C=2.0).fit(X_std, y_toy)
svc_pred = svc_ref.predict(X_std)
svc_margin = 2.0 / np.linalg.norm(svc_ref.coef_.ravel())
scratch_objective = hinge_objective(
X_std, y_signed, w_scratch, b_scratch, C=2.0
)
reference_objective = hinge_objective(
X_std, y_signed, svc_ref.coef_.ravel(), svc_ref.intercept_[0], C=2.0
)
objective_gap = scratch_objective - reference_objective
reference_support_count = svc_ref.support_.sizeConsoleOut [20]Expand
sklearn training accuracy: 1.000 sklearn ||w||: 1.142 sklearn margin width: 1.752 agreement (scratch, sklearn): 1.000 sklearn support vectors: 2/40 sklearn support fraction: 0.050 scratch hinge objective: 0.754472 reference hinge objective: 0.651577 objective gap: 0.102895
The printed label agreement and objective gap answer different questions. Matching labels on this easy training set can coexist with a noticeable objective gap, because a fixed step size can keep the scratch iterate moving around the nonsmooth optimum. The scratch objective uses , matching the reference SVC normalization. The scratch hinge-active fraction counts strict margin violations at its current iterate; it is not the dual support-vector fraction. The reference support count comes separately from svc_ref.support_.
The SVR side follows the same pattern. We reuse the noisy sine dataset from the visualization and inspect epsilon-insensitive loss and tube occupancy alongside the reference support-vector set. Points strictly inside the tube have zero epsilon-insensitive loss and zero dual coefficients. Points on the tube edges can have nonzero coefficients, as can points outside; the support set is not limited to strict outside points.
PythonIn [21]Expand
def epsilon_insensitive_loss(residuals, epsilon):
return np.maximum(0.0, np.abs(residuals) - epsilon)
svr_epsilon = 0.2
svr_ref = SVR(kernel="rbf", C=2.0, gamma=0.9, epsilon=svr_epsilon).fit(
X_demo, y_demo
)
in_sample_pred = svr_ref.predict(X_demo)
res = y_demo - in_sample_pred
inside_tube = np.abs(res) <= svr_epsilon
inside_count = int(inside_tube.sum())
inside_fraction = inside_tube.mean()
mean_epsilon_loss = epsilon_insensitive_loss(res, svr_epsilon).mean()
mean_squared_residual = (res**2).mean()ConsoleOut [22]Expand
SVR support-vector count: 8/60 mean epsilon-insensitive loss: 0.0000 mean squared residual: 0.0114 points with |residual| <= epsilon: 55/60 (0.917)
The printed tube occupancy directly counts residuals with , using the fitted predictions. The mean epsilon-insensitive loss and mean squared residual summarize different loss functions and scale differently when target units change; their relative magnitudes do not reveal how many observations lie inside the tube. The reference support set can include points on the tube boundary, and finite solver tolerance can put their computed residuals slightly inside or outside it. These in-sample diagnostics describe the fit; they do not establish that the model generalizes or that a particular tube width is optimal.
The tube width is a tuning choice, not a fixed property of the data. Refitting SVR on the same sine training set over a small epsilon grid shows how the support fraction and the fraction of in-sample residuals inside the tube vary. Here the support fraction decreases across the grid, while the measured inside-tube fraction rises overall and is unchanged between epsilon 0.2 and 0.4. These counts use the fitted residuals and the exact comparison shown in the code, so solver tolerance can affect points near the tube edges. This is a training-set diagnostic, not a validation comparison.
PythonIn [23]Expand
epsilon_grid = np.array([0.05, 0.10, 0.20, 0.40, 0.80])
support_fraction = []
inside_fraction = []
for eps_val in epsilon_grid:
svr_scan = SVR(kernel="rbf", C=2.0, gamma=0.9, epsilon=float(eps_val)).fit(
X_demo, y_demo
)
pred_scan = svr_scan.predict(X_demo)
support_fraction.append(len(svr_scan.support_) / len(y_demo))
inside_fraction.append(np.mean(np.abs(y_demo - pred_scan) <= eps_val))
support_fraction = np.array(support_fraction)
inside_fraction = np.array(inside_fraction)VisualizationOut [24]Expand

The implementation uses C, kernel, gamma, and epsilon directly; these are summarized jointly with the reference-library controls in the Key Methods and Key Parameters sections that follow.
Key Methods
These methods support fitting, prediction, and inspection of the decision scores.
SVC(...).fit(X, y): trains a soft-margin classifier. The linear-kernel version can be compared with dedicated linear solvers for larger datasets.SVR(...).fit(X, y): trains an -insensitive regressor, usingC, the selected kernel's settings, andepsilon. Theclass_weightparameter belongs toSVC; both estimators acceptsample_weightinfitto rescale individual violation penalties.decision_function(X): in the binary example, returns the score . Positive scores select the positive class. For a nonzero linear weight vector, divide by to obtain signed perpendicular distance. For multiclassSVC, training uses one-vs-one classifiers; the defaultdecision_function_shape="ovr"converts their outputs into one score per class. Scores are not calibrated probabilities.predict(X): returns class labels forSVCand continuous predictions forSVR.predict_proba(X): forSVC, enableprobability=Truebefore fitting. libsvm estimates probabilities using additional internal five-fold cross-validation and Platt scaling for the binary models. This slows fitting, and the most probable class can differ frompredict(X). Assess probability calibration on held-out data.
Key Parameters
Below are some of the main parameters that affect how SVMs work and perform, using SVC and SVR in scikit-learn.
C: controls the penalty for margin or tube violations. Under the mean-loss convention , ; largerCmeans weaker relative regularization.kernel: chooses the feature-space inner product. A linear kernel keeps a linear boundary in the supplied features; polynomial and RBF kernels can produce nonlinear boundaries.gamma: controls RBF locality, with characteristic distance . Tune it jointly withC; taking it to zero at fixedCdoes not generally recover a linear SVM.epsilon: sets the SVR tube's half-width in target units. A wider tube ignores more residual values for a fixed fitted function and can encourage a sparser fit. Atepsilon=0the loss is absolute error, rather than squared error.class_weight: changes the class-specific violation penalties inSVC. Choose weights using class frequencies, error costs, and validation; weighting and resampling are options whose suitability depends on the task.
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!