Skip to content

Commit

Permalink
Add QuantumKernelTrainer and KernelLoss classes (#244)
Browse files Browse the repository at this point in the history
* 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
caleb-johnson and manoelmarques authored Dec 14, 2021
1 parent 8277c78 commit 9e19b62
Show file tree
Hide file tree
Showing 11 changed files with 1,200 additions and 73 deletions.
4 changes: 4 additions & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ instantiations
isometry
iten
iterable
iteratively
jupyter
kernelized
kwarg
Expand Down Expand Up @@ -116,6 +117,7 @@ pre
precomputed
precomputes
precomputing
preprint
priori
ps
py
Expand Down Expand Up @@ -190,10 +192,12 @@ utils
variational
vec
vqc
vqe
vqr
vx
vy
vz
wrt
zoufal
zsh
θ
457 changes: 457 additions & 0 deletions docs/tutorials/08_quantum_kernel_trainer.ipynb

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions qiskit_machine_learning/kernels/algorithms/__init__.py
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 qiskit_machine_learning/kernels/algorithms/quantum_kernel_trainer.py
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
Loading

0 comments on commit 9e19b62

Please sign in to comment.