Source code for psipose.estimators.qsvc

"""Quantum Support Vector Classifier (QSVC)."""

import warnings

from sklearn.base import BaseEstimator, ClassifierMixin
from sklearn.svm import SVC
from sklearn.utils.multiclass import unique_labels
from sklearn.utils.validation import check_is_fitted, validate_data

from psipose.encoders import BaseEncoder
from psipose.kernels import FidelityQuantumKernel


[docs] class QSVC(BaseEstimator, ClassifierMixin): """Quantum Support Vector Classifier using a fidelity quantum kernel. Combines a quantum feature map (encoder) with a classical SVM. The quantum kernel computes similarity as :math:`K(x_i, x_j) = |\\langle \\phi(x_i)|\\phi(x_j)\\rangle|^2`. This implements the quantum kernel approach from: - Havlíček et al. (2019), "Supervised learning with quantum-enhanced feature spaces" (Nature 567, 209-212). Introduces quantum kernels via the fidelity between quantum-encoded feature maps, combined with classical SVM. - Schuld & Killoran (2019), "Quantum machine learning in feature Hilbert spaces" (PRL 122, 040504). Frames quantum input encoding as a nonlinear map to Hilbert space and shows how quantum kernels can be used with classical ML methods. - Yang, Awan & Vall-Llosera (2019), "Support Vector Machines on Noisy Intermediate Scale Quantum Computers" (arXiv:1909.11988). Adapts SVM for NISQ devices using quantum kernel matrices. Mathematically, the quantum kernel is defined as: .. math:: K(x_i, x_j) = |\\langle \\phi(x_i)|\\phi(x_j)\\rangle|^2 = |\\langle 0^{\\otimes n}| U_{\\text{enc}}^\\dagger(x_j) U_{\\text{enc}}(x_i) |0^{\\otimes n}\\rangle|^2 where :math:`|\\phi(x)\\rangle = U_{\\text{enc}}(x)|0^{\\otimes n}\\rangle` is the quantum feature map implemented by the encoder. The training kernel matrix :math:`K_{\\text{train}}` is computed and passed to :class:`sklearn.svm.SVC` with ``kernel="precomputed"``. Parameters ---------- n_qubits : int, default=4 Number of qubits for the quantum feature map. encoder : BaseEncoder, optional Data encoding circuit. If None, defaults to AngleEncoder(). device : str, default="default.qubit" PennyLane device name for simulation. C : float, default=1.0 Regularization parameter for SVC. kernel : {"precomputed"}, default="precomputed" Kernel type. Must be "precomputed" (always uses quantum kernel). degree : int, default=3 Degree for polynomial kernel (ignored for precomputed). gamma : float or {"scale", "auto"}, default="scale" Kernel coefficient for RBF/poly/sigmoid (ignored for precomputed). coef0 : float, default=0.0 Independent term in poly/sigmoid kernel (ignored for precomputed). shrinking : bool, default=True Whether to use shrinking heuristic in SVC. probability : bool, default=False Whether to enable probability estimates. tol : float, default=1e-3 Tolerance for stopping criterion. cache_size : float, default=200 Kernel cache size in MB. class_weight : dict or "balanced", optional Class weights for handling imbalanced datasets. verbose : bool, default=False Enable verbose output. max_iter : int, default=-1 Maximum iterations for SVC solver (-1 = no limit). decision_function_shape : {"ovr", "ovo"}, default="ovr" Shape of decision function for multi-class. break_ties : bool, default=False Break ties in multi-class classification. random_state : int, optional Random seed for reproducibility. Attributes ---------- classes_ : ndarray Class labels. n_features_in_ : int Number of features seen during fit. n_classes_ : int Number of classes. kernel_ : FidelityQuantumKernel Fitted quantum kernel instance. K_train_ : ndarray, shape (n_samples, n_samples) Training kernel matrix. svc_ : SVC Fitted sklearn SVC instance. support_vectors_ : ndarray Support vectors (from SVC). dual_coef_ : ndarray Coefficients of support vectors in decision function (from SVC). intercept_ : ndarray Constants in decision function (from SVC). X_train_ : ndarray Training data (stored for prediction). Examples -------- >>> from psipose import QSVC >>> from sklearn.datasets import make_moons >>> X, y = make_moons(n_samples=100, noise=0.1, random_state=42) >>> clf = QSVC(n_qubits=2, random_state=42) >>> clf.fit(X, y) QSVC(n_qubits=2, random_state=42) >>> y_pred = clf.predict(X) """ def __init__( self, *, n_qubits: int = 4, encoder: BaseEncoder | None = None, device: str = "default.qubit", C: float = 1.0, kernel: str = "precomputed", degree: int = 3, gamma: float | str = "scale", coef0: float = 0.0, shrinking: bool = True, probability: bool = False, tol: float = 1e-3, cache_size: float = 200, class_weight: str | dict | None = None, verbose: bool = False, max_iter: int = -1, decision_function_shape: str = "ovr", break_ties: bool = False, random_state: int | None = None, ): self.n_qubits = n_qubits self.encoder = encoder self.device = device self.C = C self.kernel = kernel self.degree = degree self.gamma = gamma self.coef0 = coef0 self.shrinking = shrinking self.probability = probability self.tol = tol self.cache_size = cache_size self.class_weight = class_weight self.verbose = verbose self.max_iter = max_iter self.decision_function_shape = decision_function_shape self.break_ties = break_ties self.random_state = random_state
[docs] def fit(self, X, y, sample_weight=None): """Fit the quantum SVM classifier. Parameters ---------- X : array-like, shape (n_samples, n_features) Training data. y : array-like, shape (n_samples,) Target labels. sample_weight : array-like, shape (n_samples,), optional Sample weights. Returns ------- self : QSVC Fitted estimator. """ # Validate inputs X, y = validate_data(self, X, y) self.n_features_in_ = X.shape[1] self.classes_ = unique_labels(y) self.n_classes_ = len(self.classes_) # Validate kernel parameter - must be precomputed if self.kernel != "precomputed": warnings.warn( "QSVC always uses precomputed quantum kernel. " f"The kernel='{self.kernel}' parameter is ignored.", UserWarning, stacklevel=2, ) # Store training data for later predictions self.X_train_ = X.copy() # 1. Build and fit the quantum kernel self.kernel_ = FidelityQuantumKernel( encoder=self.encoder, n_qubits=self.n_qubits, device=self.device, ) self.kernel_.fit(X) # 2. Compute training kernel matrix self.K_train_ = self.kernel_.compute_matrix(X) # 3. Create and train SVC with precomputed kernel self.svc_ = SVC( C=self.C, kernel="precomputed", degree=self.degree, gamma=self.gamma, coef0=self.coef0, shrinking=self.shrinking, probability=self.probability, tol=self.tol, cache_size=self.cache_size, class_weight=self.class_weight, verbose=self.verbose, max_iter=self.max_iter, decision_function_shape=self.decision_function_shape, break_ties=self.break_ties, random_state=self.random_state, ) self.svc_.fit(self.K_train_, y, sample_weight=sample_weight) return self
def __getattr__(self, name): """Delegate attribute access to the underlying SVC for fitted attributes.""" if name == "svc_" or not hasattr(self, "svc_"): raise AttributeError( f"'{type(self).__name__}' object has no attribute '{name}'" ) return getattr(self.svc_, name)
[docs] def predict(self, X): """Predict class labels for samples. Parameters ---------- X : array-like, shape (n_samples, n_features) Input samples. Returns ------- y : ndarray, shape (n_samples,) Predicted class labels. """ check_is_fitted(self, ["svc_", "kernel_", "K_train_", "X_train_"]) X = validate_data(self, X, reset=False) # Compute kernel matrix between X and training data K_test = self.kernel_.compute_matrix(X, self.X_train_) return self.svc_.predict(K_test)
[docs] def predict_proba(self, X): """Predict class probabilities. Requires probability=True during initialization. Parameters ---------- X : array-like, shape (n_samples, n_features) Input samples. Returns ------- proba : ndarray, shape (n_samples, n_classes) Class probabilities. """ check_is_fitted(self, ["svc_", "kernel_", "X_train_"]) if not self.probability: raise AttributeError( "predict_proba is not available when probability=False. " "Set probability=True in QSVC constructor." ) X = validate_data(self, X, reset=False) K_test = self.kernel_.compute_matrix(X, self.X_train_) return self.svc_.predict_proba(K_test)
[docs] def decision_function(self, X): """Compute decision function for samples. Parameters ---------- X : array-like, shape (n_samples, n_features) Input samples. Returns ------- decision : ndarray, shape (n_samples,) or (n_samples, n_classes) Decision function values. """ check_is_fitted(self, ["svc_", "kernel_", "X_train_"]) X = validate_data(self, X, reset=False) K_test = self.kernel_.compute_matrix(X, self.X_train_) return self.svc_.decision_function(K_test)