Source code for psipose.estimators.regression

"""Regression estimators using quantum models."""

from sklearn.base import RegressorMixin
from sklearn.preprocessing import StandardScaler
from sklearn.utils.validation import check_array, check_is_fitted, check_X_y

from psipose.ansatze import StronglyEntanglingAnsatz
from psipose.estimators.base import QuantumEstimator
from psipose.feature_maps import AngleFeatureMap
from psipose.feature_maps import FeatureMap as FMProtocol
from psipose.measurements import PauliZExpectation
from psipose.models import VariationalModel


[docs] class VQCRegressor(QuantumEstimator, RegressorMixin): """Variational Quantum Regressor. A variational quantum model for regression tasks that uses a quantum circuit to map inputs to continuous outputs. The model is trained to minimize mean squared error (MSE) using gradient-based optimization. The implementation follows the variational quantum algorithm (VQA) paradigm where: 1. Classical features are encoded into quantum states (feature map) 2. A parameterized ansatz circuit processes the encoded states 3. Measurement (typically Pauli-Z expectation) provides continuous output 4. The target variable is scaled to match the measurement range [-1, 1] 5. Gradients computed via parameter shift rule optimize the parameters This approach extends quantum neural networks to regression tasks as described in Schuld et al. (2018, arXiv:1804.00633) and Mitarai et al. (2018, arXiv:1803.00745). Parameters ---------- n_qubits : int, default=4 Number of qubits in the quantum circuit. feature_map : FeatureMap, optional Data encoding circuit. Default: AngleFeatureMap() ansatz : Ansatz, optional Parameterized circuit. Default: StronglyEntanglingAnsatz(layers=2) measurement : Measurement, optional Measurement operator. Default: PauliZExpectation() optimizer : str or callable, default="adam" Optimizer for training (adam or sgd). learning_rate : float, default=0.01 Learning rate. n_iter : int, default=100 Number of optimization iterations. batch_size : int, optional Batch size for training. random_state : int, optional Random seed. device : str, default="default.qubit" PennyLane device. Attributes ---------- n_features_in_ : int Number of features seen during fit. weights_ : ndarray Trained circuit parameters. feature_map_ : FeatureMap Fitted feature map. ansatz_ : Ansatz Ansatz instance. scaler_ : StandardScaler Fitted scaler for target variable. loss_history_ : list[float] Training loss per iteration. qnode_ : callable Cached QNode for inference. References ---------- .. [1] M. Schuld, A. Bocharov, K. Svore, and N. Wiebe, "Circuit-centric quantum classifiers," arXiv:1804.00633, 2018. .. [2] K. Mitarai et al., "Quantum circuit learning," Phys. Rev. A 98, 032309, 2018. arXiv:1803.00745. """ def __init__( self, n_qubits: int = 4, feature_map: FMProtocol | None = None, ansatz=None, measurement=None, optimizer="adam", learning_rate=0.01, n_iter=100, batch_size=None, random_state=None, device="default.qubit", ): self.n_qubits = n_qubits self.feature_map = feature_map self.ansatz = ansatz self.measurement = measurement self.optimizer = optimizer self.learning_rate = learning_rate self.n_iter = n_iter self.batch_size = batch_size self.random_state = random_state self.device = device
[docs] def fit(self, X, y): """Fit the regressor.""" X, y = check_X_y(X, y, multi_output=False) self.n_features_in_ = X.shape[1] feature_map = self.feature_map or AngleFeatureMap(n_qubits=self.n_qubits) ansatz = self.ansatz or StronglyEntanglingAnsatz( n_qubits=self.n_qubits, layers=2 ) measurement = self.measurement or PauliZExpectation() # Scale y to [-1, 1] self.scaler_ = StandardScaler() y_scaled = self.scaler_.fit_transform(y.reshape(-1, 1)).flatten() self.model_ = VariationalModel( feature_map=feature_map, ansatz=ansatz, measurement=measurement, optimizer=self.optimizer, learning_rate=self.learning_rate, n_iter=self.n_iter, batch_size=self.batch_size, device=self.device, random_state=self.random_state, loss_fn="mse", ) self.model_.fit(X, y_scaled) self.encoder_ = feature_map self.ansatz_ = ansatz self.weights_ = self.model_.weights_ self.feature_map_ = self.model_.feature_map_ self.loss_history_ = self.model_.loss_history_ self.qnode_ = self.model_.qnode_ return self
[docs] def predict(self, X): """Predict continuous values.""" check_is_fitted(self, ["model_", "scaler_"]) X = check_array(X, ensure_2d=True) if X.shape[1] != self.n_features_in_: raise ValueError( f"X has {X.shape[1]} features, but VQCRegressor is expecting " f"{self.n_features_in_} features as input." ) predictions_scaled = self.model_.predict(X) predictions = self.scaler_.inverse_transform( predictions_scaled.reshape(-1, 1) ).flatten() return predictions
[docs] def score(self, X, y): """Return R^2 score.""" return super().score(X, y)