Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QuantumKernelTrainer and KernelLoss classes #244

Merged
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
b6399c7
Add QuantumKernelTrainer class, KernelLoss class, and refactor QSVC t…
caleb-johnson Oct 18, 2021
a4803b2
Refactor to unconfuse mypy
caleb-johnson Oct 18, 2021
7344b90
Add runtime client for QKA
caleb-johnson Oct 18, 2021
7a990cb
Remove qka program info from __init__
caleb-johnson Oct 18, 2021
f86f844
Remove vqe language from qka client
caleb-johnson Oct 19, 2021
4b58eb2
Merge branch 'main' into qka-runtime
manoelmarques Oct 29, 2021
33b0d39
Merge branch 'main' into quantum-kernel-trainer
manoelmarques Oct 29, 2021
a7ae58f
Merge branch 'main' of github.com:calebj15/qiskit-machine-learning in…
caleb-johnson Nov 10, 2021
2b524f2
Retract changes to QSVC
caleb-johnson Nov 10, 2021
2bee96e
Address peer review comments. Remove kernels.algorithms package. Make…
caleb-johnson Nov 10, 2021
72b4905
Change SVCAlignment class to SVCLoss and update KernelLoss doc strings.
caleb-johnson Nov 10, 2021
f6202c8
Fix equation in SVCLoss docstring
caleb-johnson Nov 10, 2021
bad47a9
Docstring improvements
caleb-johnson Nov 10, 2021
5949970
Add quantum kernel trainer runtime client and unit tests.
caleb-johnson Nov 10, 2021
6833223
Fix spelling errors
caleb-johnson Nov 10, 2021
47f0d3e
Fix spelling errors
caleb-johnson Nov 10, 2021
1126bbd
Clean up docstring for SVCLoss
caleb-johnson Nov 10, 2021
859ad4f
Clean up docstring for SVCLoss
caleb-johnson Nov 10, 2021
d2323d8
Fix spelling errors
caleb-johnson Nov 10, 2021
45e2aa4
Fix equation formatting in docstring
caleb-johnson Nov 11, 2021
d40345e
Fix equation formatting in docstring
caleb-johnson Nov 11, 2021
6a3e042
New spelling errors
caleb-johnson Nov 11, 2021
2973ca4
New spelling errors
caleb-johnson Nov 11, 2021
67dbc22
Fix spelling errors. Add 'runtime' to pylint dict
caleb-johnson Nov 11, 2021
afa7232
Fix mypy errors
caleb-johnson Nov 11, 2021
eb95125
Fix mypy errors
caleb-johnson Nov 11, 2021
7640bfb
style error
caleb-johnson Nov 11, 2021
f0506dc
style error
caleb-johnson Nov 11, 2021
7989965
spelling error in docstring
caleb-johnson Nov 11, 2021
1e468ee
Fix circular imports due to typing.
caleb-johnson Nov 11, 2021
c7b191e
Fix circular imports due to typing.
caleb-johnson Nov 11, 2021
fe34bfb
Make QuantumKernel class serializable by runtime.
caleb-johnson Nov 16, 2021
47267f1
Update quantum_kernel_trainer.py
caleb-johnson Nov 17, 2021
30d4ec8
Update quantum_kernel_trainer.py
caleb-johnson Nov 17, 2021
6921254
Update quantum_kernel_trainer.py
caleb-johnson Nov 17, 2021
7e51267
Make KernelLoss.get_variational_callable a base method
caleb-johnson Nov 17, 2021
bd417d1
Make SVCLoss more efficient by only calculating kernel once
caleb-johnson Nov 19, 2021
33972a9
Create kernels.algorithms package for QuantumKernelTrainer. Fix all C…
caleb-johnson Nov 22, 2021
c946418
spelling errors
caleb-johnson Nov 22, 2021
83b2330
Clean up kernel loss docstring
caleb-johnson Nov 22, 2021
5af811e
Create kernels.algorithms package for quantum kernel trainer. Clean u…
caleb-johnson Nov 22, 2021
b57d1a4
spelling errors
caleb-johnson Nov 22, 2021
84c38f3
spelling errors
caleb-johnson Nov 22, 2021
676816b
spelling errors
caleb-johnson Nov 22, 2021
a4e4203
Create separate file for kernel loss classes. Fix spelling errors.
caleb-johnson Nov 22, 2021
2ec50d5
add runtime to pylint dict
caleb-johnson Nov 22, 2021
30395f2
add vqe to pylint dict
caleb-johnson Nov 22, 2021
c9ec0e3
Merge branch 'main' into quantum-kernel-trainer
caleb-johnson Nov 22, 2021
eb90863
Merge branch 'main' into qka-runtime
manoelmarques Nov 26, 2021
6214143
Merge branch 'main' into quantum-kernel-trainer
manoelmarques Nov 26, 2021
1f49459
Address peer feedback
caleb-johnson Dec 1, 2021
66f04b2
Merge branch 'qka-runtime' of github.com:calebj15/qiskit-machine-lear…
caleb-johnson Dec 1, 2021
52c2e61
Address peer review comments
caleb-johnson Dec 1, 2021
3849e60
Merge branch 'main' into quantum-kernel-trainer
manoelmarques Dec 2, 2021
a24a563
Remove qkt runtime code from qkt branch
caleb-johnson Dec 6, 2021
199e3be
html errors
caleb-johnson Dec 6, 2021
7131b7c
Merge branch 'main' into quantum-kernel-trainer
manoelmarques Dec 7, 2021
08de1b8
Merge branch 'main' into quantum-kernel-trainer
manoelmarques Dec 7, 2021
44e0c0e
Merge branch 'main' of github.com:calebj15/qiskit-machine-learning in…
caleb-johnson Dec 8, 2021
67c4125
Merge branch 'quantum-kernel-trainer' of github.com:calebj15/qiskit-m…
caleb-johnson Dec 8, 2021
309dd52
Address peer review comments
caleb-johnson Dec 8, 2021
0874a93
Fix mypy errors
caleb-johnson Dec 8, 2021
dd49cd3
Add a quantum kernel training tutorial
caleb-johnson Dec 12, 2021
28589ae
Print results of kernel fitting in tutorial
caleb-johnson Dec 12, 2021
09c75bb
Modify QKT tutorial. Clean up QKT interface.
caleb-johnson Dec 13, 2021
4f18589
Rename QKT tutorial. Fix bad assertion in qkt test.
caleb-johnson Dec 13, 2021
000f7b4
Make QKT tests deterministic. Add to pylintdict
caleb-johnson Dec 13, 2021
fc99b56
Address minor comments in qkt tutorial
caleb-johnson Dec 13, 2021
d6bd020
Fix spelling errors in QKT tutorial
caleb-johnson Dec 13, 2021
d5c8bff
Clean up docstring for qkt
caleb-johnson Dec 13, 2021
4ed685a
Fix comment in qkt tutorial. Remove unnecessary type checks from QKT …
caleb-johnson Dec 14, 2021
5505b61
Clean up docstring in QKT
caleb-johnson Dec 14, 2021
a6625d4
Fix mypy error in qkt
caleb-johnson Dec 14, 2021
c7d03c1
Fix mypy errors in qkt
caleb-johnson Dec 14, 2021
93f8cbb
Fix mypy errors in qkt
caleb-johnson Dec 14, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ ovo
ovr
param
parametrized
parametrization
params
pauli
pre
Expand Down Expand Up @@ -182,10 +183,12 @@ utils
variational
vec
vqc
vqe
vqr
vx
vy
vz
wrt
zoufal
zsh
θ
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`)
caleb-johnson marked this conversation as resolved.
Show resolved Hide resolved

.. currentmodule:: qiskit_machine_learning.kernels.algorithms
adekusar-drl marked this conversation as resolved.
Show resolved Hide resolved

Quantum Kernels
===============

.. autosummary::
:toctree: ../stubs/
:nosignatures:

QuantumKernelTrainer
QuantumKernelTrainerResult

"""

from .quantum_kernel_trainer import QuantumKernelTrainer, QuantumKernelTrainerResult

__all__ = ["QuantumKernelTrainer", "QuantumKernelTrainerResult"]
231 changes: 231 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,231 @@
# 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, Callable

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_kernel(X_train, y_train)
optimized_kernel = qkt_results.quantum_kernel
"""

def __init__(
self,
quantum_kernel: QuantumKernel,
loss: Union[str, Callable[[Sequence[float]], float]] = "svc_loss",
optimizer: Optional[Optimizer] = None,
initial_point: Optional[Sequence[float]] = None,
):
"""
Args:
quantum_kernel: QuantumKernel to be trained
loss (Callable[[Sequence[float]], float] or str):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please update loss type hints to be the same across the code? I guess it should be Union[str, KernelLoss] now.

str: 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.
Callable[[Sequence[float]], float]: A callable loss function which takes
a vector of parameter values as input and returns a loss value (float)
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 if optimizer else SPSA()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can be simplified:

Suggested change
self._optimizer = optimizer if optimizer else SPSA()
self._optimizer = optimizer or SPSA()


# Set up loss function
if isinstance(loss, str):
loss = loss.lower()
if loss == "svc_loss":
self._loss = self._str_to_loss(loss)
else:
raise ValueError(f"Unknown loss {loss}!")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess a string to loss conversion is done in the _str_to_loss, so these checks are unnecessary as you mentioned below.

elif isinstance(loss, KernelLoss):
self._loss = loss
else:
raise ValueError(f"Unknown loss {loss}!")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So if I pass a callable Callable[[Sequence[float]], float], I get an exception.
Could you also add a test with a custom loss to check this scenario?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I believe we decided against this usage, so I just need to update type hint. Thanks


@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: KernelLoss) -> None:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Setter type should be the same as in the constructor.

"""Set the loss."""
self._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_kernel(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd suggest to rename to plain fit, make it more scikit-learn compatible. See here: https://scikit-learn.org/stable/developers/develop.html, QKT is an estimator, I think, in scikit-learn terms.

self,
data: np.ndarray,
labels: np.ndarray,
) -> QuantumKernelTrainerResult:
adekusar-drl marked this conversation as resolved.
Show resolved Hide resolved
"""
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 _str_to_loss(self, loss_str: str) -> KernelLoss:
if loss_str == "svc_loss":
loss_obj = SVCLoss()
else:
raise ValueError(f"Unknown loss {loss_str}!")
caleb-johnson marked this conversation as resolved.
Show resolved Hide resolved

return loss_obj
Loading