"""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)