Source code for psipose.models.kernel
"""Quantum kernel - pure kernel model.
This module implements quantum kernel methods based on state fidelity,
which is a core technique in quantum support vector machines.
"""
import numpy as np
import pennylane as qml
from sklearn.base import BaseEstimator
from sklearn.utils.validation import check_is_fitted
from psipose.feature_maps import AngleFeatureMap
[docs]
class QuantumKernel(BaseEstimator):
"""Quantum kernel using fidelity between encoded states.
Computes the Gram matrix where each entry is the fidelity
(squared inner product) between quantum-encoded data points:
K[i, j] = |⟨φ(x_i) | φ(x_j)⟩|^2
This quantum kernel approach was introduced in the landmark paper:
- Havlíček et al. (2019), "Supervised learning with quantum-inspired
kernel" (arXiv:1904.01567). The paper demonstrates how quantum circuits
can implicitly define kernel functions that may provide quantum advantage
for classification tasks.
The kernel computation uses the overlap circuit method: by applying the
adjoint of the encoding circuit for x_j after the encoding for x_i,
the probability of measuring the all-zeros state equals the squared
fidelity between the two encoded quantum states.
Parameters
----------
feature_map : FeatureMap, optional
The quantum feature map. If None, uses AngleFeatureMap() with n_qubits.
n_qubits : int, default=4
Number of qubits. Only used if feature_map is None.
device : str, default="default.qubit"
PennyLane device.
batch_size : int, optional
Number of circuits to evaluate in parallel.
Attributes
----------
feature_map_ : FeatureMap
Fitted feature map.
n_qubits_ : int
Number of qubits used.
References
----------
.. [1] V. Havlíček et al., "Supervised learning with quantum-inspired
kernel," arXiv:1904.01567, 2019.
"""
def __init__(
self, feature_map=None, n_qubits=4, device="default.qubit", batch_size=None
):
self.feature_map = feature_map
self.n_qubits = n_qubits
self.device = device
self.batch_size = batch_size
[docs]
def fit(self, X):
"""Fit the kernel.
Parameters
----------
X : array-like, shape (n_samples, n_features)
Training data.
Returns
-------
self : QuantumKernel
"""
if self.feature_map is None:
self.feature_map_ = AngleFeatureMap(n_qubits=self.n_qubits)
else:
self.feature_map_ = self.feature_map
self.feature_map_.fit(X)
self.n_qubits_ = self.feature_map_.n_qubits_
self.encoder_ = self.feature_map_
return self
def _compute_overlap(self, x1, x2):
"""Compute fidelity between two encoded states."""
dev = qml.device(self.device, wires=self.n_qubits_)
@qml.qnode(dev, interface="autograd")
def circuit():
self.feature_map_.encode(x1, wires=range(self.n_qubits_))
qml.adjoint(self.feature_map_.encode)(x2, wires=range(self.n_qubits_))
return qml.probs(wires=range(self.n_qubits_))
probs = circuit()
return probs[0]
[docs]
def compute_matrix(self, X, Y=None):
"""Compute the kernel matrix.
Parameters
----------
X : array-like, shape (n_samples_X, n_features)
First set.
Y : array-like, shape (n_samples_Y, n_features), optional
Second set. If None, uses X.
Returns
-------
K : ndarray, shape (n_samples_X, n_samples_Y)
Kernel matrix.
"""
check_is_fitted(self, ["feature_map_", "n_qubits_"])
if Y is None:
Y = X
symmetric = True
else:
symmetric = False
n_samples_X = len(X)
n_samples_Y = len(Y)
K = np.zeros((n_samples_X, n_samples_Y))
for i in range(n_samples_X):
for j in range(n_samples_Y):
if symmetric and j < i:
K[i, j] = K[j, i]
else:
K[i, j] = self._compute_overlap(X[i], Y[j])
return K
def __call__(self, X, Y=None):
"""Make kernel callable like sklearn kernel."""
return self.compute_matrix(X, Y)