Source code for DLL.MachineLearning.SupervisedLearning.SupportVectorMachines._SVR

import torch
import cvxopt
import numpy as np

from ..Kernels import RBF, _Base
from ....Exceptions import NotFittedError


[docs] class SVR: """ The support vector machine regressor with a quadratic programming solver. Args: kernel (:ref:`kernel_section_label`, optional): The non-linearity function for fitting the model. Defaults to RBF. C (float or int, optional): A regularization parameter. Defaults to 1. Must be positive real number. epsilon (float or int, optional): The width of the tube of no penalty in epsilon-SVR. Must be a positive real number. Attributes: n_features (int): The number of features. Available after fitting. alpha (torch.Tensor of shape (n_samples,)): The optimized dual coefficients. Available after fitting. alpha_star (torch.Tensor of shape (n_samples,)): The optimized dual coefficients. Available after fitting. """ def __init__(self, kernel=RBF(), C=1, epsilon=0.1): if not isinstance(kernel, _Base): raise ValueError("kernel must be from DLL.MachineLearning.SupervisedLearning.Kernels") if not isinstance(C, float | int) or C <= 0: raise ValueError("C must be must be positive real number.") if not isinstance(epsilon, float | int) or epsilon <= 0: raise ValueError("epsilon must be must be positive real number.") self.kernel = kernel self.C = C self.epsilon = epsilon def _kernel_matrix(self, X1, X2): return self.kernel(X1, X2).to(X1.dtype)
[docs] def fit(self, X, y): """ Fits the SVR model to the input data by finding the hypertube that contains the data with minimum loss. Args: X (torch.Tensor of shape (n_samples, n_features)): The input data, where each row is a sample and each column is a feature. y (torch.Tensor of shape (n_samples,)): The target values corresponding to each sample. Returns: None Raises: TypeError: If the input matrix or the target matrix is not a PyTorch tensor. ValueError: If the input matrix or the target matrix is not the correct shape. """ if not isinstance(X, torch.Tensor) or not isinstance(y, torch.Tensor): raise TypeError("The input matrix and the target matrix must be a PyTorch tensor.") if X.ndim != 2: raise ValueError("The input matrix must be a 2 dimensional tensor.") if y.ndim != 1 or y.shape[0] != X.shape[0]: raise ValueError("The targets must be 1 dimensional with the same number of samples as the input data") self.y = y.reshape((-1, 1)).to(X.dtype) self.X = X self.n_features = X.shape[1] n = X.shape[0] K = self._kernel_matrix(X, X).numpy() P = cvxopt.matrix(np.block([[K, -K], [-K, K]]).tolist()) # [[K, -K], [-K, K]] q = cvxopt.matrix(np.hstack([self.epsilon - y, self.epsilon + y]).tolist()) A = cvxopt.matrix(np.vstack([np.ones((n, 1)), -np.ones((n, 1))]).tolist()) b = cvxopt.matrix(0.0) G = cvxopt.matrix(np.hstack([-np.eye(2 * n), np.eye(2 * n)]).tolist()) h = cvxopt.matrix(np.hstack([np.zeros((2 * n,)), np.ones((2 * n,)) * self.C]).tolist()) cvxopt.solvers.options['show_progress'] = False sol = cvxopt.solvers.qp(P, q, G, h, A, b) alpha = torch.tensor(np.array(sol["x"]), dtype=torch.float64).squeeze(dim=1) self.alpha = alpha[:n] self.alpha_star = alpha[n:]
[docs] def predict(self, X): """ Applies the fitted SVR model to the input data, predicting the correct values. Args: X (torch.Tensor of shape (n_samples, n_features)): The input data to be regressed. Returns: target values (torch.Tensor of shape (n_samples,)): The predicted values corresponding to each sample. Raises: NotFittedError: If the SVR model has not been fitted before predicting. TypeError: If the input matrix is not a PyTorch tensor. ValueError: If the input matrix is not the correct shape. """ if not hasattr(self, "n_features"): raise NotFittedError("SVR.fit() must be called before predicting.") if not isinstance(X, torch.Tensor): raise TypeError("The input matrix must be a PyTorch tensor.") if X.ndim != 2 or X.shape[1] != self.n_features: raise ValueError("The input matrix must be a 2 dimensional tensor with the same number of features as the fitted tensor.") # Use every datapoint as a support vector, since otherwise the results seem to be bad. # This makes no sense to me, but yields the best results. bias = (self.y - torch.sum((self.alpha - self.alpha_star) * self._kernel_matrix(self.X, self.X), dim=1)).mean() prediction = (self.alpha - self.alpha_star) @ self._kernel_matrix(self.X, X).to(dtype=self.alpha.dtype) + bias return prediction