|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
"""Trainable Quantum Kernel"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from abc import ABC
|
|
from typing import Mapping, Sequence
|
|
|
|
import numpy as np
|
|
from qiskit.circuit import Parameter, ParameterVector
|
|
from qiskit.circuit.parameterexpression import ParameterValueType
|
|
|
|
from .base_kernel import BaseKernel
|
|
from ..exceptions import QiskitMachineLearningError
|
|
|
|
|
|
class TrainableKernel(BaseKernel, ABC):
|
|
"""An abstract definition of the ability to train kernel via specifying training parameters."""
|
|
|
|
def __init__(
|
|
self, *, training_parameters: ParameterVector | Sequence[Parameter] | None = None, **kwargs
|
|
) -> None:
|
|
"""
|
|
Args:
|
|
training_parameters: a sequence of training parameters.
|
|
**kwargs: Additional parameters may be used by the super class.
|
|
"""
|
|
super().__init__(**kwargs)
|
|
|
|
if training_parameters is None:
|
|
training_parameters = []
|
|
|
|
self._training_parameters = training_parameters
|
|
self._num_training_parameters = len(self._training_parameters)
|
|
|
|
self._parameter_dict = {parameter: None for parameter in training_parameters}
|
|
|
|
self._feature_parameters: Sequence[Parameter] = []
|
|
|
|
def assign_training_parameters(
|
|
self,
|
|
parameter_values: Mapping[Parameter, ParameterValueType] | Sequence[ParameterValueType],
|
|
) -> None:
|
|
"""
|
|
Fix the training parameters to numerical values.
|
|
"""
|
|
if not isinstance(parameter_values, dict):
|
|
if len(parameter_values) != self._num_training_parameters:
|
|
raise ValueError(
|
|
f"The number of given parameters is wrong: {len(parameter_values)}, "
|
|
f"expected {self._num_training_parameters}."
|
|
)
|
|
self._parameter_dict.update(
|
|
{
|
|
parameter: parameter_values[i]
|
|
for i, parameter in enumerate(self._training_parameters)
|
|
}
|
|
)
|
|
else:
|
|
for key in parameter_values:
|
|
if key not in self._training_parameters:
|
|
raise ValueError(
|
|
f"Parameter {key} is not a trainable parameter of the feature map and "
|
|
f"thus cannot be bound. Make sure {key} is provided in the the trainable "
|
|
"parameters when initializing the kernel."
|
|
)
|
|
self._parameter_dict[key] = parameter_values[key]
|
|
|
|
@property
|
|
def parameter_values(self) -> np.ndarray:
|
|
"""
|
|
Returns numerical values assigned to the training parameters as a numpy array.
|
|
"""
|
|
return np.asarray([self._parameter_dict[param] for param in self._training_parameters])
|
|
|
|
@property
|
|
def training_parameters(self) -> ParameterVector | Sequence[Parameter]:
|
|
"""
|
|
Returns the vector of training parameters.
|
|
"""
|
|
return self._training_parameters
|
|
|
|
@property
|
|
def num_training_parameters(self) -> int:
|
|
"""
|
|
Returns the number of training parameters.
|
|
"""
|
|
return len(self._training_parameters)
|
|
|
|
def _parameter_array(self, x_vec: np.ndarray) -> np.ndarray:
|
|
"""
|
|
Combines the feature values and the trainable parameters into one array.
|
|
"""
|
|
self._check_trainable_parameters()
|
|
full_array = np.zeros((x_vec.shape[0], self._num_features + self._num_training_parameters))
|
|
for i, x in enumerate(x_vec):
|
|
self._parameter_dict.update(
|
|
{feature_param: x[j] for j, feature_param in enumerate(self._feature_parameters)}
|
|
)
|
|
full_array[i, :] = list(self._parameter_dict.values())
|
|
return full_array
|
|
|
|
def _check_trainable_parameters(self) -> None:
|
|
for param in self._training_parameters:
|
|
if self._parameter_dict[param] is None:
|
|
raise QiskitMachineLearningError(
|
|
f"Trainable parameter {param} has not been bound. Make sure to bind all"
|
|
"trainable parameters to numerical values using `.assign_training_parameters()`"
|
|
"before calling `.evaluate()`."
|
|
)
|
|
|