-
Notifications
You must be signed in to change notification settings - Fork 330
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add QuantumKernelTrainer and KernelLoss classes (#244)
* Add QuantumKernelTrainer class, KernelLoss class, and refactor QSVC to accept QKT as input. * Refactor to unconfuse mypy * Add runtime client for QKA * Remove qka program info from __init__ * Remove vqe language from qka client * Retract changes to QSVC * Address peer review comments. Remove kernels.algorithms package. Make QuantumKernel.assign_user_parameters more robust and add user tests to test parameter expression assignment. Test QKT outputs in tests. * Change SVCAlignment class to SVCLoss and update KernelLoss doc strings. * Fix equation in SVCLoss docstring * Docstring improvements * Add quantum kernel trainer runtime client and unit tests. * Fix spelling errors * Fix spelling errors * Clean up docstring for SVCLoss * Clean up docstring for SVCLoss * Fix spelling errors * Fix equation formatting in docstring * Fix equation formatting in docstring * New spelling errors * New spelling errors * Fix spelling errors. Add 'runtime' to pylint dict * Fix mypy errors * Fix mypy errors * style error * style error * spelling error in docstring * Fix circular imports due to typing. * Fix circular imports due to typing. * Make QuantumKernel class serializable by runtime. * Update quantum_kernel_trainer.py * Update quantum_kernel_trainer.py * Update quantum_kernel_trainer.py * Make KernelLoss.get_variational_callable a base method * Make SVCLoss more efficient by only calculating kernel once * Create kernels.algorithms package for QuantumKernelTrainer. Fix all CI/CD errors * spelling errors * Clean up kernel loss docstring * Create kernels.algorithms package for quantum kernel trainer. Clean up CI/CD errors. Address kernel loss feedback. * spelling errors * spelling errors * spelling errors * Create separate file for kernel loss classes. Fix spelling errors. * add runtime to pylint dict * add vqe to pylint dict * Address peer feedback * html errors * Address peer review comments * Fix mypy errors * Add a quantum kernel training tutorial * Print results of kernel fitting in tutorial * Rename QKT tutorial. Fix bad assertion in qkt test. * Make QKT tests deterministic. Add to pylintdict * Address minor comments in qkt tutorial * Fix spelling errors in QKT tutorial * Clean up docstring for qkt * Fix comment in qkt tutorial. Remove unnecessary type checks from QKT module * Clean up docstring in QKT * Fix mypy error in qkt * Fix mypy errors in qkt * Fix mypy errors in qkt Co-authored-by: Manoel Marques <[email protected]>
- Loading branch information
1 parent
8277c78
commit 9e19b62
Showing
11 changed files
with
1,200 additions
and
73 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2021. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
|
||
""" | ||
Quantum Kernel Algorithms (:mod:`qiskit_machine_learning.kernels.algorithms`) | ||
.. currentmodule:: qiskit_machine_learning.kernels.algorithms | ||
Quantum Kernels | ||
=============== | ||
.. autosummary:: | ||
:toctree: ../stubs/ | ||
:nosignatures: | ||
QuantumKernelTrainer | ||
QuantumKernelTrainerResult | ||
""" | ||
|
||
from .quantum_kernel_trainer import QuantumKernelTrainer, QuantumKernelTrainerResult | ||
|
||
__all__ = ["QuantumKernelTrainer", "QuantumKernelTrainerResult"] |
239 changes: 239 additions & 0 deletions
239
qiskit_machine_learning/kernels/algorithms/quantum_kernel_trainer.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2021. | ||
# | ||
# This code is licensed under the Apache License, Version 2.0. You may | ||
# obtain a copy of this license in the LICENSE.txt file in the root directory | ||
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. | ||
# | ||
# Any modifications or derivative works of this code must retain this | ||
# copyright notice, and modified files need to carry a notice indicating | ||
# that they have been altered from the originals. | ||
|
||
"""Quantum Kernel Trainer""" | ||
import copy | ||
from functools import partial | ||
from typing import Union, Optional, Sequence | ||
|
||
import numpy as np | ||
|
||
from qiskit.utils.algorithm_globals import algorithm_globals | ||
from qiskit.algorithms.optimizers import Optimizer, SPSA | ||
from qiskit.algorithms.variational_algorithm import VariationalResult | ||
from qiskit_machine_learning.utils.loss_functions import KernelLoss, SVCLoss | ||
|
||
from qiskit_machine_learning.kernels import QuantumKernel | ||
|
||
|
||
class QuantumKernelTrainerResult(VariationalResult): | ||
"""Quantum Kernel Trainer Result.""" | ||
|
||
def __init__(self) -> None: | ||
super().__init__() | ||
self._quantum_kernel: QuantumKernel = None | ||
|
||
@property | ||
def quantum_kernel(self) -> Optional[QuantumKernel]: | ||
"""Return the optimized quantum kernel object.""" | ||
return self._quantum_kernel | ||
|
||
@quantum_kernel.setter | ||
def quantum_kernel(self, quantum_kernel: QuantumKernel) -> None: | ||
self._quantum_kernel = quantum_kernel | ||
|
||
|
||
class QuantumKernelTrainer: | ||
""" | ||
Quantum Kernel Trainer. | ||
This class provides utility to train ``QuantumKernel`` feature map parameters. | ||
**Example** | ||
.. code-block:: | ||
# Create 2-qubit feature map | ||
qc = QuantumCircuit(2) | ||
# Vectors of input and trainable user parameters | ||
input_params = ParameterVector("x_par", 2) | ||
user_params = ParameterVector("θ_par", 2) | ||
# Create an initial rotation layer of trainable parameters | ||
for i, param in enumerate(user_params): | ||
qc.ry(param, qc.qubits[i]) | ||
# Create a rotation layer of input parameters | ||
for i, param in enumerate(input_params): | ||
qc.rz(param, qc.qubits[i]) | ||
quant_kernel = QuantumKernel( | ||
feature_map=qc, | ||
user_parameters=user_params, | ||
quantum_instance=... | ||
) | ||
loss_func = ... | ||
optimizer = ... | ||
initial_point = ... | ||
qk_trainer = QuantumKernelTrainer( | ||
quantum_kernel=quant_kernel, | ||
loss=loss_func, | ||
optimizer=optimizer, | ||
initial_point=initial_point, | ||
) | ||
qkt_results = qk_trainer.fit(X_train, y_train) | ||
optimized_kernel = qkt_results.quantum_kernel | ||
""" | ||
|
||
def __init__( | ||
self, | ||
quantum_kernel: QuantumKernel, | ||
loss: Optional[Union[str, KernelLoss]] = None, | ||
optimizer: Optional[Optimizer] = None, | ||
initial_point: Optional[Sequence[float]] = None, | ||
): | ||
""" | ||
Args: | ||
quantum_kernel: QuantumKernel to be trained | ||
loss (str or KernelLoss): Loss functions available via string: | ||
{'svc_loss': SVCLoss()}. | ||
If a string is passed as the loss function, then the | ||
underlying KernelLoss object will exhibit default | ||
behavior. | ||
optimizer: An instance of ``Optimizer`` to be used in training. Since no | ||
analytical gradient is defined for kernel loss functions, gradient-based | ||
optimizers are not recommended for training kernels. | ||
initial_point: Initial point from which the optimizer will begin. | ||
Raises: | ||
ValueError: unknown loss function | ||
""" | ||
# Class fields | ||
self._quantum_kernel = quantum_kernel | ||
self._initial_point = initial_point | ||
self._optimizer = optimizer or SPSA() | ||
|
||
# Loss setter | ||
self._set_loss(loss) | ||
|
||
@property | ||
def quantum_kernel(self) -> QuantumKernel: | ||
"""Return the quantum kernel object.""" | ||
return self._quantum_kernel | ||
|
||
@quantum_kernel.setter | ||
def quantum_kernel(self, quantum_kernel: QuantumKernel) -> None: | ||
"""Set the quantum kernel.""" | ||
self._quantum_kernel = quantum_kernel | ||
|
||
@property | ||
def loss(self) -> KernelLoss: | ||
"""Return the loss object.""" | ||
return self._loss | ||
|
||
@loss.setter | ||
def loss(self, loss: Optional[Union[str, KernelLoss]]) -> None: | ||
""" | ||
Set the loss. | ||
Args: | ||
loss: a loss function to set | ||
Raises: | ||
ValueError: Unknown loss function | ||
""" | ||
self._set_loss(loss) | ||
|
||
@property | ||
def optimizer(self) -> Optimizer: | ||
"""Return an optimizer to be used in training.""" | ||
return self._optimizer | ||
|
||
@optimizer.setter | ||
def optimizer(self, optimizer: Optimizer) -> None: | ||
"""Set the optimizer.""" | ||
self._optimizer = optimizer | ||
|
||
@property | ||
def initial_point(self) -> Optional[Sequence[float]]: | ||
"""Return initial point""" | ||
return self._initial_point | ||
|
||
@initial_point.setter | ||
def initial_point(self, initial_point: Optional[Sequence[float]]) -> None: | ||
"""Set the initial point""" | ||
self._initial_point = initial_point | ||
|
||
def fit( | ||
self, | ||
data: np.ndarray, | ||
labels: np.ndarray, | ||
) -> QuantumKernelTrainerResult: | ||
""" | ||
Train the QuantumKernel by minimizing loss over the kernel parameters. The input | ||
quantum kernel will not be altered, and an optimized quantum kernel will be returned. | ||
Args: | ||
data (numpy.ndarray): ``(N, D)`` array of training data, where ``N`` is the | ||
number of samples and ``D`` is the feature dimension | ||
labels (numpy.ndarray): ``(N, 1)`` array of target values for the training samples | ||
Returns: | ||
QuantumKernelTrainerResult: the results of kernel training | ||
Raises: | ||
ValueError: No trainable user parameters specified in quantum kernel | ||
""" | ||
# Number of parameters to tune | ||
num_params = len(self._quantum_kernel.user_parameters) | ||
if num_params == 0: | ||
msg = "Quantum kernel cannot be fit because there are no user parameters specified." | ||
raise ValueError(msg) | ||
|
||
# Bind inputs to objective function | ||
output_kernel = copy.deepcopy(self._quantum_kernel) | ||
|
||
# Randomly initialize the initial point if one was not passed | ||
if self._initial_point is None: | ||
self._initial_point = algorithm_globals.random.random(num_params) | ||
|
||
# Perform kernel optimization | ||
loss_function = partial( | ||
self._loss.evaluate, quantum_kernel=self.quantum_kernel, data=data, labels=labels | ||
) | ||
opt_results = self._optimizer.minimize( | ||
fun=loss_function, | ||
x0=self._initial_point, | ||
) | ||
|
||
# Return kernel training results | ||
result = QuantumKernelTrainerResult() | ||
result.optimizer_evals = opt_results.nfev | ||
result.optimal_value = opt_results.fun | ||
result.optimal_point = opt_results.x | ||
result.optimal_parameters = dict(zip(output_kernel.user_parameters, opt_results.x)) | ||
|
||
# Return the QuantumKernel in optimized state | ||
output_kernel.assign_user_parameters(result.optimal_parameters) | ||
result.quantum_kernel = output_kernel | ||
|
||
return result | ||
|
||
def _set_loss(self, loss: Optional[Union[str, KernelLoss]]) -> None: | ||
"""Internal setter.""" | ||
if loss is None: | ||
loss = SVCLoss() | ||
elif isinstance(loss, str): | ||
loss = self._str_to_loss(loss) | ||
|
||
self._loss = loss | ||
|
||
def _str_to_loss(self, loss_str: str) -> KernelLoss: | ||
"""Function which maps strings to default KernelLoss objects.""" | ||
if loss_str == "svc_loss": | ||
loss_obj = SVCLoss() | ||
else: | ||
raise ValueError(f"Unknown loss {loss_str}!") | ||
|
||
return loss_obj |
Oops, something went wrong.