SVR (Support Vector Regression): Principles, Examples, and Python Implementation

SVR (Support Vector Regression) : Guide Complet — Principes, Exemples et Implémentation Python

SVR (Support Vector Regression): Complete Guide — Principles, Examples, and Python Implementation

SVR (Support Vector Regression) transposes the philosophy of support vector machines to the domain of regression. Instead of seeking a maximum margin between classes, SVR seeks to contain a maximum number of points in a tube of thickness ε around the prediction function. Only errors outside this tube are penalized.

This guide explores SVR in detail: from the mathematical formulation of the epsilon-insensitive tube to practical implementation with scikit-learn.


Mathematical Principle

The Epsilon-Insensitive Tube Concept

Unlike linear regression which minimizes the sum of squared errors (MSE), SVR regression introduces a zone of insensitivity: any prediction within ε of the true value is considered perfect. This makes the model robust to small fluctuations and noise.

The epsilon-insensitive loss function (Vapnik, 1995) is defined as:

L_ε(y, f(x)) = max(0, |y - f(x)| - ε)

In other words:
– If the error |y – f(x)| is less than ε → loss = 0
– If the error exceeds ε → loss proportional to the excess

Optimization Formulation

The primal problem for SVR is:

min ½||w||² + C Σ(ξᵢ + ξᵢ*)
subject to:
  yᵢ - (w·φ(xᵢ) + b) ≤ ε + ξᵢ
  (w·φ(xᵢ) + b) - yᵢ ≤ ε + ξᵢ*
  ξᵢ, ξᵢ* ≥ 0

where:
– w is the weight vector in the feature space φ(x)
– C controls the tradeoff between regularization (flatness) and error penalization
– ξᵢ and ξᵢ* are slack variables for points above and below the tube
– ε defines the thickness of the insensitive tube

Dual Formulation with Kernels

As with classification SVMs, we solve the dual problem with Lagrange multipliers αᵢ and αᵢ*:

max -½ Σ(αᵢ-αᵢ*)(αⱼ-αⱼ*)K(xᵢ,xⱼ) - ε Σ(αᵢ+αᵢ*) + Σ yᵢ(αᵢ-αᵢ*)

subject to: 0 ≤ αᵢ, αᵢ ≤ C and Σ(αᵢ-αᵢ) = 0.

The prediction function is:

f(x) = Σ(αᵢ-αᵢ*)K(xᵢ,x) + b

Only points with αᵢ ≠ 0 or αᵢ ≠ 0 contribute — these are the regression support vectors*.

Available Kernels

SVR supports the same kernels as classification SVMs:
– Linear: K(x,x’) = x·x’ — for linear relationships
– Polynomial: K(x,x’) = (γ·x·x’ + r)^d — for algebraic curves
– RBF: K(x,x’) = exp(-γ||x-x’||²) — the most versatile for non-linear relationships

Intuition — How to Understand SVR?

Imagine you draw a regression line. In classical regression, every point that deviates from the line contributes to the error. With SVR regression, you create a “tube” of thickness ε around the line. Everything inside the tube is free — it doesn’t count.

It’s like telling the model: “I tolerate errors of size ε, don’t worry about them. Focus on the real deviations.”

This idea has powerful consequences:
– Noise robustness: small-amplitude noise is absorbed by the tube
– Sparsity: only points outside the tube (support vectors) influence the model
– Complexity control: ε controls the “width” of tolerance, C controls the severity of penalization

The larger ε is, the simpler the model (fewer points outside the tube, fewer support vectors). The larger C is, the more the model penalizes errors outside the tube.

Python Implementation — Complete Example

Example 1: Linear SVR vs RBF

import numpy as np
import matplotlib.pyplot as plt
from sklearn.svm import SVR, LinearSVR
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score

# Noisy sinusoidal data
np.random.seed(42)
X = np.sort(5 * np.random.rand(200, 1), axis=0)
y = np.sin(X).ravel() + 0.1 * np.random.randn(200)

# Train/test split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# Standardization
scaler_X = StandardScaler()
scaler_y = StandardScaler()
X_tr = scaler_X.fit_transform(X_train)
y_tr = scaler_y.fit_transform(y_train.reshape(-1, 1)).ravel()
X_te = scaler_X.transform(X_test)

# SVR with RBF kernel
svr_rbf = SVR(kernel='rbf', C=10, gamma='scale', epsilon=0.1)
svr_rbf.fit(X_tr, y_tr)
y_pred_rbf = scaler_y.inverse_transform(svr_rbf.predict(X_te).reshape(-1, 1)).ravel()

# Linear SVR
svr_lin = SVR(kernel='linear', C=1.0, epsilon=0.1)
svr_lin.fit(X_tr, y_tr)
y_pred_lin = scaler_y.inverse_transform(svr_lin.predict(X_te).reshape(-1, 1)).ravel()

# LinearSVR (optimized alternative)
lsvr = LinearSVR(C=1.0, epsilon=0.1, max_iter=5000)
lsvr.fit(X_tr, y_tr)
y_pred_lsvr = scaler_y.inverse_transform(lsvr.predict(X_te).reshape(-1, 1)).ravel()

# Evaluation
for name, y_pred in [('SVR RBF', y_pred_rbf), ('SVR Linear', y_pred_lin),
                      ('LinearSVR', y_pred_lsvr)]:
    r2 = r2_score(y_test, y_pred)
    rmse = np.sqrt(mean_squared_error(y_test, y_pred))
    print(f'{name:15s} -> R2={r2:.4f}, RMSE={rmse:.4f}')

# Visualization
X_plot = np.linspace(0, 5, 300).reshape(-1, 1)
X_plot_scaled = scaler_X.transform(X_plot)

plt.figure(figsize=(10, 6))
plt.scatter(X_train, y_train, c='darkblue', label='Training data', alpha=0.6, s=20)
plt.scatter(X_test, y_test, c='orange', label='Test data', alpha=0.8, s=20)

for name, pred_func, style in [
    ('SVR RBF', lambda x: scaler_y.inverse_transform(svr_rbf.predict(scaler_X.transform(x)).reshape(-1,1)).ravel(), 'r-'),
    ('SVR Linear', lambda x: scaler_y.inverse_transform(svr_lin.predict(scaler_X.transform(x)).reshape(-1,1)).ravel(), 'g--'),
]:
    plt.plot(X_plot, pred_func(X_plot), style, linewidth=2, label=name)

plt.xlabel('X')
plt.ylabel('y')
plt.title('SVR RBF vs Linear on sinusoidal data')
plt.legend()
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Example 2: Impact of Epsilon and C

# Impact of epsilon
epsilons = [0.01, 0.05, 0.1, 0.2, 0.5, 1.0]
r2_scores = []
n_support = []

for eps in epsilons:
    svr = SVR(kernel='rbf', C=10, gamma='scale', epsilon=eps)
    svr.fit(X_tr, y_tr)
    pred = scaler_y.inverse_transform(svr.predict(X_te).reshape(-1, 1)).ravel()
    r2 = r2_score(y_test, pred)
    r2_scores.append(r2)
    n_support.append(len(svr.support_vectors_))
    print(f'Epsilon={eps:.2f} -> R2={r2:.4f}, num support vectors={len(svr.support_vectors_)}')

plt.figure(figsize=(8, 5))
plt.plot(epsilons, r2_scores, 'bo-', linewidth=2)
plt.xlabel('Epsilon (insensitive tube width)', fontsize=12)
plt.ylabel('R2 Score (test)', fontsize=12)
plt.title('Impact of epsilon on SVR performance')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

# Impact of C
Cs = [0.01, 0.1, 1, 10, 100]
r2_c = []
for c in Cs:
    svr = SVR(kernel='rbf', C=c, gamma='scale', epsilon=0.1)
    svr.fit(X_tr, y_tr)
    pred = scaler_y.inverse_transform(svr.predict(X_te).reshape(-1, 1)).ravel()
    r2 = r2_score(y_test, pred)
    r2_c.append(r2)
    print(f'C={c:.2f} -> R2={r2:.4f}')

plt.figure(figsize=(8, 5))
plt.semilogx(Cs, r2_c, 'ro-', linewidth=2)
plt.xlabel('C (error penalization)', fontsize=12)
plt.ylabel('R2 Score (test)', fontsize=12)
plt.title('Impact of C on SVR performance')
plt.grid(True, alpha=0.3)
plt.tight_layout()
plt.show()

Example 3: Grid Search and Comparison

from sklearn.model_selection import GridSearchCV

param_grid = {
    'kernel': ['rbf', 'linear'],
    'C': [0.1, 1, 10, 100],
    'epsilon': [0.01, 0.1, 0.5],
    'gamma': ['scale', 'auto', 0.01, 0.1, 1]
}

grid = GridSearchCV(SVR(), param_grid, cv=5, scoring='neg_mean_squared_error', n_jobs=-1)
grid.fit(X_tr, y_tr)

print(f'Best hyperparameters: {grid.best_params_}')
print(f'Best MSE (CV): {-grid.best_score_:.4f}')

# Predictions with best model
best_svr = grid.best_estimator_
y_pred_best = scaler_y.inverse_transform(best_svr.predict(X_te).reshape(-1, 1)).ravel()
r2 = r2_score(y_test, y_pred_best)
print(f'R2 on test: {r2:.4f}')

Hyperparameters

Hyperparameter Role Typical Values Impact
C Inverse regularization 0.01, 0.1, 1, 10, 100 Small C = smoother model, tolerates errors. Large C = heavily penalizes errors outside the tube.
epsilon Insensitive tube width 0.01, 0.1 (default), 0.5 The larger ε is, the wider the tube and the fewer support vectors. Controls model parsimony.
kernel Kernel type ‘rbf’ (default), ‘linear’, ‘poly’ RBF for non-linear relationships, linear for simple high-dimensional relationships.
gamma RBF kernel scale ‘scale’ (default), ‘auto’, 0.01-10 High gamma = complex boundaries. Low gamma = smoother model.
degree Polynomial degree 2, 3 Only used with kernel=’poly’
max_iter Iteration limit -1 (unlimited), 1000 Increase if non-convergence.

Advantages and Limitations

Advantages

  • Noise robustness: the epsilon tube absorbs small fluctuations
  • Sparsity: only points outside the tube count, compact model
  • Controlled non-linearity: same kernels as SVMs (RBF, polynomial)
  • No distributional assumption: unlike linear regression which assumes Gaussian error
  • Generalization: good performance on unseen data, even in low dimensions

Limitations

  • Sensitive to standardization: mandatory, like all SVMs
  • Computationally expensive: O(n²) for training, O(n_sv) for prediction
  • Tuning difficulty: 3-4 hyperparameters to adjust simultaneously (C, ε, γ)
  • Low interpretability: difficult to explain why a prediction takes a certain value
  • Poor performance on very large datasets: beyond 100,000 points, prefer gradient boosting

Use Cases

1. Time Series Prediction

SVR with the RBF kernel excels at modeling non-linear trends in time series (prices, temperature, demand). The epsilon tube absorbs measurement noise while capturing trends.

2. Real Estate Price Estimation

The relationships between area, location, number of rooms, and price are rarely linear. SVR with RBF models these complex interactions without functional form assumptions.

3. Industrial Sensor Calibration

In industry, the relationship between a sensor’s raw reading and the actual physical value is often non-linear. SVR can be used to calibrate sensors with superior accuracy to polynomial regression.

4. Quality Prediction in Production

In the pharmaceutical or food industry, predicting batch quality from production parameters. The epsilon tube allows defining tolerance zones naturally.

Best Practices

  1. Systematically standardize features (StandardScaler) and targets if their amplitude is large.
  2. Start with epsilon=0.1 and adjust according to the scale of your target variable.
  3. Grid Search on C and epsilon: these two parameters are the most influential.
  4. Compare Linear and RBF SVR: linear may suffice in high dimensions.
  5. Monitor the number of support vectors: if it approaches n, your model is likely overfitting.

See Also


SVR vs Other Regression Methods

SVR is not the only option for non-linear relationships. Let’s compare it with the most common alternatives:

SVR vs Linear Regression: Linear regression assumes a linear relationship and minimizes the global MSE. SVR is more flexible thanks to kernels, and its epsilon tube makes it more robust to outliers.

SVR vs Random Forest: Random Forest is easier to tune (fewer sensitive hyperparameters) and naturally handles non-standardized features. But SVR offers better generalization on small datasets (< 10,000 points).

SVR vs Gradient Boosting: Gradient boosting (XGBoost, LightGBM) is often superior in pure accuracy on tabular data. SVR retains the advantage when you have little data and need theoretical robustness.

SVR vs Neural Networks: Neural networks surpass SVR on large data volumes and very complex problems. But SVR is simpler to train, with theoretical guarantees of global convergence.

In summary: SVR excels on small to medium-sized datasets (100 to 10,000 points) with non-linear relationships, when you want a robust model with theoretical guarantees.

Example 4: SVR with LinearSVR for Large Datasets

When working with more than 10,000 samples, SVR with the RBF kernel becomes slow. LinearSVR uses the liblinear algorithm, which is much faster:

from sklearn.svm import LinearSVR
from sklearn.datasets import make_regression

X, y = make_regression(n_samples=50000, n_features=20, noise=0.1, random_state=42)

lsvr = LinearSVR(C=1.0, epsilon=0.1, max_iter=2000, dual='auto')
lsvr.fit(X, y)
print(f'Coefficients: {lsvr.coef_[:5]}')  # The first 5
print(f'Intercept: {lsvr.intercept_:.4f}')
print(f'R2: {lsvr.score(X, y):.4f}')

LinearSVR only supports the linear kernel, but it’s an excellent starting point for large data volumes.

About Salah YAHIAOUI

Salah YAHIAOUI publie sur The Pingouin des tutoriels Python et des décryptages sur l’intelligence artificielle. Les cas pratiques récents précisent leurs données, leurs tests et leurs limites. À propos, méthode éditoriale et contact.

View all posts by Salah YAHIAOUI →