|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
""" Kernel Loss utilities """
|
|
|
|
from abc import ABC, abstractmethod
|
|
from typing import Sequence
|
|
|
|
import numpy as np
|
|
from sklearn.svm import SVC
|
|
|
|
|
|
from ...kernels import TrainableKernel
|
|
|
|
|
|
class KernelLoss(ABC):
|
|
"""
|
|
Abstract base class for computing the loss of a kernel function.
|
|
Unlike many loss functions, which only take into account the labels and predictions
|
|
of a model, kernel loss functions may be a function of internal model parameters or
|
|
quantities that are generated during training.
|
|
"""
|
|
|
|
def __call__(
|
|
self,
|
|
parameter_values: Sequence[float],
|
|
quantum_kernel: TrainableKernel,
|
|
data: np.ndarray,
|
|
labels: np.ndarray,
|
|
) -> float:
|
|
"""
|
|
This method calls the ``evaluate`` method. This is a convenient method to compute loss.
|
|
"""
|
|
return self.evaluate(parameter_values, quantum_kernel, data, labels)
|
|
|
|
@abstractmethod
|
|
def evaluate(
|
|
self,
|
|
parameter_values: Sequence[float],
|
|
quantum_kernel: TrainableKernel,
|
|
data: np.ndarray,
|
|
labels: np.ndarray,
|
|
) -> float:
|
|
"""
|
|
An abstract method for evaluating the loss of a kernel function on a labeled dataset.
|
|
|
|
Args:
|
|
parameter_values: An array of values to assign to the user params
|
|
quantum_kernel: A trainable quantum kernel object to evaluate
|
|
data: An ``(N, M)`` matrix containing the data
|
|
``N = # samples, M = dimension of data``
|
|
labels: A length-N array containing the truth labels
|
|
|
|
Returns:
|
|
A loss value
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
|
|
class SVCLoss(KernelLoss):
|
|
r"""
|
|
This class provides a kernel loss function for classification tasks by fitting an ``SVC`` model
|
|
from scikit-learn. Given training samples, :math:`x_{i}`, with binary labels, :math:`y_{i}`,
|
|
and a kernel, :math:`K_{θ}`, parameterized by values, :math:`θ`, the loss is defined as:
|
|
|
|
.. math::
|
|
|
|
SVCLoss = \sum_{i} a_i - 0.5 \sum_{i,j} a_i a_j y_{i} y_{j} K_θ(x_i, x_j)
|
|
|
|
where :math:`a_i` are the optimal Lagrange multipliers found by solving the standard SVM
|
|
quadratic program. Note that the hyper-parameter ``C`` for the soft-margin penalty can be
|
|
specified through the keyword args.
|
|
|
|
Minimizing this loss over the parameters, :math:`θ`, of the kernel is equivalent to maximizing a
|
|
weighted kernel alignment, which in turn yields the smallest upper bound to the SVM
|
|
generalization error for a given parameterization.
|
|
|
|
See https://arxiv.org/abs/2105.03406 for further details.
|
|
"""
|
|
|
|
def __init__(self, **kwargs):
|
|
"""
|
|
Args:
|
|
**kwargs: Arbitrary keyword arguments to pass to SVC constructor within
|
|
SVCLoss evaluation.
|
|
"""
|
|
self.kwargs = kwargs
|
|
|
|
def evaluate(
|
|
self,
|
|
parameter_values: Sequence[float],
|
|
quantum_kernel: TrainableKernel,
|
|
data: np.ndarray,
|
|
labels: np.ndarray,
|
|
) -> float:
|
|
|
|
quantum_kernel.assign_training_parameters(parameter_values)
|
|
|
|
|
|
kmatrix = quantum_kernel.evaluate(np.array(data))
|
|
|
|
|
|
svc = SVC(kernel="precomputed", **self.kwargs)
|
|
svc.fit(kmatrix, labels)
|
|
|
|
|
|
dual_coefs = svc.dual_coef_[0]
|
|
|
|
|
|
support_vecs = svc.support_
|
|
|
|
|
|
kmatrix = kmatrix[support_vecs, :][:, support_vecs]
|
|
|
|
|
|
loss = np.sum(np.abs(dual_coefs)) - (0.5 * (dual_coefs.T @ kmatrix @ dual_coefs))
|
|
|
|
return loss
|
|
|