Skip to main content

Support Vector Machines

πŸ“š What You'll Learn

By the end of this lesson, you will be able to:

⏱️ Estimated Time: 45–60 minutes

🎯 Project: Train an SVM with an RBF kernel on a non-linearly separable dataset, tune C and gamma, and visualize the decision boundary.

Maximum Margin Classification! 🎯

Support Vector Machines find the optimal hyperplane that maximizes the margin between classes. With the kernel trick, SVMs can efficiently handle non-linear decision boundaries in high-dimensional spaces. Master this powerful algorithm that excels at both linear and non-linear classification tasks.

SVM Conceptual Framework

graph TD A[Support Vector Machines] --> B[Linear SVM] A --> C[Non-linear SVM] B --> D[Hard Margin] B --> E[Soft Margin] C --> F[Kernel Trick] F --> G[RBF Kernel] F --> H[Polynomial Kernel] F --> I[Sigmoid Kernel] E --> K[C Parameter] G --> M[Gamma Parameter] style A fill:#f9f,stroke:#333,stroke-width:2px style F fill:#bbf,stroke:#333,stroke-width:2px style K fill:#9f9,stroke:#333,stroke-width:2px

Understanding Support Vector Machines

Core Concepts and Linear SVM

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.svm import SVC, SVR, LinearSVC
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
from sklearn.datasets import make_classification, make_circles, make_moons
import warnings
warnings.filterwarnings('ignore')

# Generate different types of data
np.random.seed(42)

# Linear separable data
X_linear, y_linear = make_classification(
    n_samples=200, n_features=2, n_redundant=0,
    n_informative=2, n_clusters_per_class=1,
    flip_y=0.1, random_state=42
)

# Non-linear data
X_circles, y_circles = make_circles(n_samples=200, noise=0.1, 
                                   factor=0.5, random_state=42)
X_moons, y_moons = make_moons(n_samples=200, noise=0.15, random_state=42)

class SVMAnalyzer:
    """Comprehensive SVM Analysis Tool"""
    
    def __init__(self):
        self.models = {}
        self.results = {}
        
    def visualize_linear_svm(self, X, y):
        """Visualize linear SVM with different C values"""
        
        # Scale features
        scaler = StandardScaler()
        X_scaled = scaler.fit_transform(X)
        
        # C values to test
        C_values = [0.01, 0.1, 1, 10, 100]
        
        fig, axes = plt.subplots(2, 3, figsize=(15, 10))
        axes = axes.flatten()
        
        for idx, C in enumerate(C_values):
            # Train SVM
            svm = SVC(kernel='linear', C=C)
            svm.fit(X_scaled, y)
            
            # Get support vectors
            support_vectors = scaler.inverse_transform(svm.support_vectors_)
            
            # Create mesh for decision boundary
            h = 0.02
            x_min, x_max = X[:, 0].min() - 1, X[:, 0].max() + 1
            y_min, y_max = X[:, 1].min() - 1, X[:, 1].max() + 1
            xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                               np.arange(y_min, y_max, h))
            
            # Predict on mesh
            Z = svm.predict(scaler.transform(np.c_[xx.ravel(), yy.ravel()]))
            Z = Z.reshape(xx.shape)
            
            # Plot
            axes[idx].contourf(xx, yy, Z, alpha=0.4, cmap=plt.cm.RdBu)
            axes[idx].scatter(X[:, 0], X[:, 1], c=y, cmap=plt.cm.RdBu, 
                            edgecolor='black', s=30)
            axes[idx].scatter(support_vectors[:, 0], support_vectors[:, 1], 
                            s=100, facecolors='none', edgecolors='green', 
                            linewidths=2, label=f'SVs: {len(support_vectors)}')
            
            axes[idx].set_title(f'C = {C}')
            axes[idx].set_xlabel('Feature 1')
            axes[idx].set_ylabel('Feature 2')
            axes[idx].legend()
        
        # Remove extra subplot
        fig.delaxes(axes[5])
        
        plt.suptitle('Linear SVM with Different Regularization (C)', fontsize=14)
        plt.tight_layout()
        plt.show()
    
    def kernel_comparison(self, X, y):
        """Compare different kernel functions"""
        
        kernels = ['linear', 'poly', 'rbf', 'sigmoid']
        
        # Scale data
        scaler = StandardScaler()
        X_scaled = scaler.fit_transform(X)
        
        # Split data
        X_train, X_test, y_train, y_test = train_test_split(
            X_scaled, y, test_size=0.3, random_state=42
        )
        
        results = {}
        
        fig, axes = plt.subplots(2, 2, figsize=(12, 10))
        axes = axes.flatten()
        
        for idx, kernel in enumerate(kernels):
            # Train SVM
            svm = SVC(kernel=kernel, C=1.0)
            svm.fit(X_train, y_train)
            
            # Store model and results
            self.models[kernel] = svm
            y_pred = svm.predict(X_test)
            results[kernel] = {
                'accuracy': accuracy_score(y_test, y_pred),
                'n_support': len(svm.support_vectors_)
            }
            
            # Visualization
            h = 0.02
            x_min, x_max = X_scaled[:, 0].min() - 0.5, X_scaled[:, 0].max() + 0.5
            y_min, y_max = X_scaled[:, 1].min() - 0.5, X_scaled[:, 1].max() + 0.5
            xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                               np.arange(y_min, y_max, h))
            
            Z = svm.predict(np.c_[xx.ravel(), yy.ravel()])
            Z = Z.reshape(xx.shape)
            
            axes[idx].contourf(xx, yy, Z, alpha=0.4, cmap=plt.cm.coolwarm)
            axes[idx].scatter(X_scaled[:, 0], X_scaled[:, 1], c=y, 
                            cmap=plt.cm.coolwarm, edgecolor='black', s=30)
            axes[idx].scatter(svm.support_vectors_[:, 0], 
                            svm.support_vectors_[:, 1],
                            s=100, facecolors='none', edgecolors='green', 
                            linewidths=2)
            
            axes[idx].set_title(f'{kernel.upper()} Kernel\n'
                               f'Acc: {results[kernel]["accuracy"]:.3f}, '
                               f'SVs: {results[kernel]["n_support"]}')
            axes[idx].set_xlabel('Feature 1')
            axes[idx].set_ylabel('Feature 2')
        
        plt.suptitle('SVM Kernel Comparison', fontsize=14)
        plt.tight_layout()
        plt.show()
        
        return results

# Initialize analyzer
analyzer = SVMAnalyzer()

print("="*60)
print("SUPPORT VECTOR MACHINES FUNDAMENTALS")
print("="*60)

# Visualize linear SVM
print("\nVisualizing Linear SVM with different C values...")
analyzer.visualize_linear_svm(X_linear, y_linear)

# Kernel comparison
print("\nComparing different kernels...")
kernel_results = analyzer.kernel_comparison(X_circles, y_circles)

print("\nKernel Performance Summary:")
for kernel, metrics in kernel_results.items():
    print(f"{kernel.upper():8} - Accuracy: {metrics['accuracy']:.3f}, "
          f"Support Vectors: {metrics['n_support']}")

Hyperparameter Optimization

Grid Search and Cross-Validation

class SVMTuner:
    """Hyperparameter tuning for SVM"""
    
    def __init__(self):
        self.best_model = None
        self.cv_results = None
        
    def grid_search_optimization(self, X, y):
        """Perform grid search for optimal parameters"""
        
        # Scale data
        scaler = StandardScaler()
        X_scaled = scaler.fit_transform(X)
        
        # Split data
        X_train, X_test, y_train, y_test = train_test_split(
            X_scaled, y, test_size=0.2, random_state=42
        )
        
        # Define parameter grid
        param_grid = [
            # Linear kernel
            {'kernel': ['linear'], 'C': [0.01, 0.1, 1, 10, 100]},
            
            # RBF kernel
            {'kernel': ['rbf'], 
             'C': [0.1, 1, 10, 100],
             'gamma': ['scale', 'auto', 0.001, 0.01, 0.1, 1]},
            
            # Polynomial kernel
            {'kernel': ['poly'],
             'C': [0.1, 1, 10],
             'degree': [2, 3, 4],
             'gamma': ['scale', 'auto']}
        ]
        
        # Grid search
        print("Performing Grid Search...")
        grid_search = GridSearchCV(
            SVC(random_state=42),
            param_grid,
            cv=5,
            scoring='accuracy',
            n_jobs=-1,
            verbose=1
        )
        
        grid_search.fit(X_train, y_train)
        
        # Store results
        self.best_model = grid_search.best_estimator_
        self.cv_results = pd.DataFrame(grid_search.cv_results_)
        
        # Test performance
        y_pred = self.best_model.predict(X_test)
        test_accuracy = accuracy_score(y_test, y_pred)
        
        print(f"\nBest Parameters: {grid_search.best_params_}")
        print(f"Best CV Score: {grid_search.best_score_:.4f}")
        print(f"Test Accuracy: {test_accuracy:.4f}")
        
        return grid_search.best_params_, test_accuracy
    
    def c_gamma_heatmap(self, X, y):
        """Analyze interaction between C and gamma parameters"""
        
        # Scale data
        scaler = StandardScaler()
        X_scaled = scaler.fit_transform(X)
        X_train, X_test, y_train, y_test = train_test_split(
            X_scaled, y, test_size=0.3, random_state=42
        )
        
        # Parameter ranges
        C_range = np.logspace(-2, 3, 6)
        gamma_range = np.logspace(-3, 2, 6)
        
        # Create grid
        scores = np.zeros((len(C_range), len(gamma_range)))
        
        for i, C in enumerate(C_range):
            for j, gamma in enumerate(gamma_range):
                svm = SVC(kernel='rbf', C=C, gamma=gamma)
                svm.fit(X_train, y_train)
                scores[i, j] = svm.score(X_test, y_test)
        
        # Visualization
        plt.figure(figsize=(10, 8))
        plt.imshow(scores, cmap='YlOrRd', aspect='auto')
        plt.colorbar(label='Test Accuracy')
        plt.xticks(range(len(gamma_range)), [f'{g:.1e}' for g in gamma_range])
        plt.yticks(range(len(C_range)), [f'{c:.1e}' for c in C_range])
        plt.xlabel('Gamma')
        plt.ylabel('C')
        plt.title('C-Gamma Parameter Interaction Heatmap')
        
        # Add text annotations
        for i in range(len(C_range)):
            for j in range(len(gamma_range)):
                plt.text(j, i, f'{scores[i, j]:.2f}',
                        ha='center', va='center', color='black')
        
        plt.tight_layout()
        plt.show()
        
        return scores

# Hyperparameter tuning
tuner = SVMTuner()

print("\n" + "="*60)
print("HYPERPARAMETER OPTIMIZATION")
print("="*60)

# Grid search
best_params, test_acc = tuner.grid_search_optimization(X_circles, y_circles)

# C-Gamma interaction
print("\nAnalyzing C-Gamma interaction...")
scores_grid = tuner.c_gamma_heatmap(X_circles, y_circles)

Support Vector Regression (SVR)

Regression with SVM

from sklearn.svm import SVR

# Generate regression data
np.random.seed(42)
X_reg = np.sort(5 * np.random.rand(200))
y_reg = np.sin(X_reg) + 0.2 * np.random.randn(200)

def svr_demonstration():
    """Demonstrate Support Vector Regression"""
    
    # Reshape data
    X_reg_reshaped = X_reg.reshape(-1, 1)
    
    # Different epsilon values
    epsilon_values = [0.01, 0.1, 0.5]
    
    fig, axes = plt.subplots(1, 3, figsize=(15, 5))
    
    for idx, epsilon in enumerate(epsilon_values):
        # Train SVR
        svr = SVR(kernel='rbf', C=100, epsilon=epsilon)
        svr.fit(X_reg_reshaped, y_reg)
        y_pred = svr.predict(X_reg_reshaped)
        
        # Plot
        axes[idx].scatter(X_reg, y_reg, alpha=0.5, label='Data')
        axes[idx].plot(X_reg, y_pred, 'r-', label='SVR fit', linewidth=2)
        axes[idx].fill_between(X_reg, 
                              y_pred - epsilon, 
                              y_pred + epsilon,
                              alpha=0.2, color='red', 
                              label=f'Ξ΅-tube (Ξ΅={epsilon})')
        
        # Mark support vectors
        axes[idx].scatter(X_reg[svr.support_], y_reg[svr.support_],
                        s=100, facecolors='none', edgecolors='green',
                        linewidths=2, label='Support Vectors')
        
        axes[idx].set_xlabel('X')
        axes[idx].set_ylabel('y')
        axes[idx].set_title(f'Ξ΅ = {epsilon}, SVs = {len(svr.support_)}')
        axes[idx].legend()
        axes[idx].grid(True, alpha=0.3)
    
    plt.suptitle('SVR Epsilon-Insensitive Tube', fontsize=14)
    plt.tight_layout()
    plt.show()

print("\n" + "="*60)
print("SUPPORT VECTOR REGRESSION")
print("="*60)
svr_demonstration()

Practical Applications

One-Class SVM for Anomaly Detection

from sklearn.svm import OneClassSVM

def one_class_svm_demo(X_normal):
    """One-Class SVM for anomaly detection"""
    
    # Train One-Class SVM
    nu = 0.1  # Expected fraction of outliers
    ocsvm = OneClassSVM(kernel='rbf', nu=nu, gamma='auto')
    ocsvm.fit(X_normal)
    
    # Generate anomalous data
    np.random.seed(42)
    X_anomalies = np.random.uniform(low=-4, high=4, size=(20, 2))
    
    # Combine data
    X_combined = np.vstack([X_normal, X_anomalies])
    y_true = np.array([1]*len(X_normal) + [-1]*len(X_anomalies))
    
    # Predict
    y_pred = ocsvm.predict(X_combined)
    
    # Visualization
    fig, axes = plt.subplots(1, 2, figsize=(12, 5))
    
    # Decision boundary
    h = 0.02
    x_min, x_max = X_combined[:, 0].min() - 1, X_combined[:, 0].max() + 1
    y_min, y_max = X_combined[:, 1].min() - 1, X_combined[:, 1].max() + 1
    xx, yy = np.meshgrid(np.arange(x_min, x_max, h),
                       np.arange(y_min, y_max, h))
    
    Z = ocsvm.predict(np.c_[xx.ravel(), yy.ravel()])
    Z = Z.reshape(xx.shape)
    
    axes[0].contourf(xx, yy, Z, levels=[-1, 0, 1], 
                    colors=['red', 'white'], alpha=0.3)
    axes[0].contour(xx, yy, Z, levels=[0], colors='black', linewidths=2)
    
    # Plot points
    axes[0].scatter(X_normal[:, 0], X_normal[:, 1], 
                   c='blue', label='Normal', s=30)
    axes[0].scatter(X_anomalies[:, 0], X_anomalies[:, 1], 
                   c='red', marker='^', label='Anomalies', s=50)
    
    axes[0].set_xlabel('Feature 1')
    axes[0].set_ylabel('Feature 2')
    axes[0].set_title('One-Class SVM Anomaly Detection')
    axes[0].legend()
    axes[0].grid(True, alpha=0.3)
    
    # Decision scores
    decision_scores = ocsvm.decision_function(X_combined)
    
    axes[1].hist(decision_scores[y_true == 1], bins=20, 
                alpha=0.5, label='Normal', color='blue')
    axes[1].hist(decision_scores[y_true == -1], bins=20, 
                alpha=0.5, label='Anomaly', color='red')
    axes[1].axvline(x=0, color='black', linestyle='--', 
                   label='Decision Boundary')
    axes[1].set_xlabel('Decision Score')
    axes[1].set_ylabel('Frequency')
    axes[1].set_title('Decision Score Distribution')
    axes[1].legend()
    axes[1].grid(True, alpha=0.3)
    
    plt.suptitle('One-Class SVM for Anomaly Detection', fontsize=14)
    plt.tight_layout()
    plt.show()
    
    # Print metrics
    from sklearn.metrics import classification_report
    print("\nOne-Class SVM Performance:")
    print(classification_report(y_true, y_pred, 
                               target_names=['Anomaly', 'Normal']))

print("\n" + "="*60)
print("ONE-CLASS SVM FOR ANOMALY DETECTION")
print("="*60)

# Use circles data as normal
normal_data = X_circles[y_circles == 0]
one_class_svm_demo(normal_data)

Best Practices and Tips

print("\n" + "="*60)
print("SVM BEST PRACTICES")
print("="*60)

best_practices = """
KEY GUIDELINES:

1. ALWAYS SCALE FEATURES
   β€’ Use StandardScaler or MinMaxScaler
   β€’ SVM is very sensitive to feature scales
   β€’ Performance will be poor without scaling

2. KERNEL SELECTION:
   β€’ Start with RBF (good default)
   β€’ Use linear for high-dimensional sparse data
   β€’ Use polynomial for known polynomial relationships
   β€’ Custom kernels for domain-specific problems

3. HYPERPARAMETER TUNING:
   β€’ C: Start with [0.001, 0.01, 0.1, 1, 10, 100, 1000]
   β€’ gamma (RBF): Try ['scale', 'auto', 0.001, 0.01, 0.1, 1]
   β€’ Use grid search with cross-validation

4. COMPUTATIONAL EFFICIENCY:
   β€’ Use LinearSVC for linear kernel (much faster)
   β€’ Consider SGDClassifier for large datasets
   β€’ Set cache_size for large datasets
   β€’ Use probability=False unless needed

5. WHEN TO USE SVM:
   βœ“ High-dimensional data (text, images)
   βœ“ Clear margin of separation exists
   βœ“ Non-linear problems (with kernels)
   βœ“ Robust classification needed
   βœ— Very large datasets (>50,000 samples)
   βœ— Need probability estimates
   βœ— Need interpretability

6. COMMON PITFALLS:
   β€’ Not scaling features
   β€’ Using default parameters without tuning
   β€’ Using SVM for very large datasets
   β€’ Ignoring class imbalance
"""

print(best_practices)

# Summary comparison
comparison_data = {
    'Algorithm': ['SVM', 'Random Forest', 'Neural Network', 'Logistic Reg', 'KNN'],
    'Speed': ['Slow', 'Medium', 'Slow', 'Fast', 'Fast'],
    'Accuracy': ['High', 'High', 'Very High', 'Medium', 'Medium'],
    'Interpretability': ['Low', 'Medium', 'Very Low', 'High', 'Low'],
    'Non-linearity': ['Yes', 'Yes', 'Yes', 'No', 'Yes'],
    'Scaling Required': ['Yes', 'No', 'Yes', 'Beneficial', 'Yes'],
    'High Dimensions': ['Excellent', 'Good', 'Good', 'Good', 'Poor']
}

comparison_df = pd.DataFrame(comparison_data)
print("\n" + "="*60)
print("ALGORITHM COMPARISON")
print("="*60)
print(comparison_df.to_string(index=False))

Practice Exercises

Exercise 1: Custom Kernel Implementation

Implement and test custom kernel functions:

  1. Create a string kernel for text data
  2. Design a composite kernel combining RBF and polynomial
  3. Compare performance with standard kernels
  4. Analyze computational complexity

Exercise 2: Multi-class Classification

Implement multi-class SVM strategies:

  1. Compare One-vs-One and One-vs-Rest approaches
  2. Implement probability calibration
  3. Handle imbalanced multi-class data
  4. Optimize for different metrics (F1, AUC)

Exercise 3: Large-scale SVM

Handle large datasets efficiently:

  1. Implement online SVM with SGD
  2. Use kernel approximations (NystrΓΆm method)
  3. Compare LinearSVC vs SVC with linear kernel
  4. Benchmark performance trade-offs

Key Takeaways

Further Resources

πŸ““ Learning Journal

Keep a learning journal β€” digital or physical. After this lesson, take a few minutes to write down:

  • Key concepts you learned
  • Techniques that clicked for you
  • Questions or confusion points to revisit
  • Ideas you want to try
  • Your progress and feelings about learning this

✍️ This lesson's prompt: The kernel trick lets an SVM separate data in a higher-dimensional space without ever computing those coordinates directly. Where else have you seen a clever change of perspective make a hard problem suddenly easy to solve?

πŸ“ Lesson Summary

πŸŽ“ Key Takeaways

  • An SVM finds the hyperplane with the maximum margin between classes, defined by the support vectors nearest the boundary.
  • The C parameter trades off a wider margin against fewer training errors (soft-margin classification).
  • The kernel trick (linear, polynomial, RBF) lets SVMs draw non-linear boundaries efficiently.
  • SVMs are strong in high-dimensional spaces but need feature scaling and careful tuning of C and gamma.

πŸŽ‰ What You've Accomplished

You can now train and tune SVMs for both linear and non-linear problems, explain the roles of the margin, support vectors, and kernels, and choose sensible hyperparameters with grid search.

❓ Common Questions at This Stage

What do C and gamma actually control?

C controls regularization: small C allows a wider margin with more misclassifications, large C fits the training data more tightly. For the RBF kernel, gamma sets how far a single point's influence reaches β€” high gamma means very local, wiggly boundaries.

Which kernel should I start with?

Try a linear kernel first, especially with many features or lots of data (use LinearSVC for speed). If the boundary is clearly non-linear, the RBF kernel is a strong, flexible default.

Do I need to scale my features for SVM?

Yes. SVMs are distance-based, so features on larger scales dominate the objective. Standardize or normalize your features before training, and apply the same scaling to test data.

πŸ”­ Looking Ahead

SVMs draw powerful geometric decision boundaries. Next you'll look at Naive Bayes, a probabilistic classifier that reaches decisions through a completely different, probability-based lens.

βœ… Before the Next Lesson

🌟 Encouragement for the Journey

SVMs blend elegant geometry with real predictive power β€” and you now understand both the intuition and the knobs that make them work. That's a meaningful step up in your ML skills. Keep going!