Skip to content

Commit

Permalink
Add QuantumKernel class using terra primitives (#437)
Browse files Browse the repository at this point in the history
* Add primitives branch to CI workflow

* added pocs

* init fixed

* more init

* Add qnns PoC

* lint fixed

* Migrate qnns to new branch

* some unittests

* more unittest

* adapted new design without unittests

* removed compute

* removed compute

* Change inputs, assign parameters method name

* docstrings

* working on dict version

* support dictionarys

* Update qiskit_machine_learning/primitives/kernels/base_kernel.py

Co-authored-by: dlasecki <dal@zurich.ibm.com>

* Update qiskit_machine_learning/primitives/kernels/base_kernel.py

Co-authored-by: dlasecki <dal@zurich.ibm.com>

* Small fixes

* Remove changes from trailing commit

* Update qiskit_machine_learning/utils/utils.py

Co-authored-by: dlasecki <dal@zurich.ibm.com>

* raise error if not all parameters bound

* typo

* unit tests

* training unit tests

* feature params order fixex

* lint

* Create trainable mixin

* Update qiskit_machine_learning/primitives/kernels/base_kernel.py

Co-authored-by: dlasecki <dal@zurich.ibm.com>

* Update qiskit_machine_learning/primitives/kernels/base_kernel.py

Co-authored-by: dlasecki <dal@zurich.ibm.com>

* Update qiskit_machine_learning/primitives/kernels/pseudo_kernel.py

Co-authored-by: dlasecki <dal@zurich.ibm.com>

* Fix fidelity imports (new location)

* Remove pseudo kernel

* evaluate duplicates

* merge

* black

* fix some issues

* update to the recent interfaces

* update tests

* fix fidelity parameterization

* skip some tests

* fix pylint, mypy

* fix copyright

* more tweaks

* rename methods

* refactor

* clean up

* some docstrings

* more tests

* streamline base class

* fix spell

* trainable qk tweaks

* fix trainability

* revert workflows

* revert the notebook

* renamings, refactorings, code review

* code review

* remove commented code

* bump terra requirements

* code review

* code review

* fix pylint

* enforce psd test

* fix docstring

* add reno

* fix documentation reference

* update qsvc, more tests

* update qsvr, even more tests

* fix qsvr tests

* fix lint

* Change Terra requirements to accept rc

* update pegasos

* fix test

* Update qiskit_machine_learning/algorithms/classifiers/pegasos_qsvc.py

Co-authored-by: Gian Gentinetta <31244916+gentinettagian@users.noreply.github.com>

* fix complex eigenvalues

* pass parameters as keywords

* fix trainability, code review

* mixin -> abstract class, keywords

* fix docs

* update docs

* code review

* fix tests, pylint

* update reno

* update reno

* fix reno

* Update qiskit_machine_learning/algorithms/classifiers/qsvc.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>

* Update qiskit_machine_learning/algorithms/regressors/qsvr.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>

* Update qiskit_machine_learning/kernels/quantum_kernel.py

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>

* Update releasenotes/notes/add-fidelity-quantum-kernel-d40278abb49e19b5.yaml

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>

* code review

* fix tests

* terra 0.22

* more tests

Co-authored-by: Manoel Marques <manoel.marques@ibm.com>
Co-authored-by: Gian Gentinetta <gian.gentinetta@gmx.ch>
Co-authored-by: dlasecki <dal@zurich.ibm.com>
Co-authored-by: Anton Dekusar <adekusar@ie.ibm.com>
Co-authored-by: Anton Dekusar <62334182+adekusar-drl@users.noreply.github.com>
Co-authored-by: Gian Gentinetta <31244916+gentinettagian@users.noreply.github.com>
Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
  • Loading branch information
8 people authored Oct 14, 2022
1 parent 3702b74 commit 66e0476
Show file tree
Hide file tree
Showing 22 changed files with 2,125 additions and 78 deletions.
2 changes: 2 additions & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ ansatz
ansatzes
args
asmatrix
async
autograd
autosummary
backend
Expand Down Expand Up @@ -62,6 +63,7 @@ et
eval
expressibility
farrokh
fidelities
formatter
func
gambetta
Expand Down
55 changes: 30 additions & 25 deletions qiskit_machine_learning/algorithms/classifiers/pegasos_qsvc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,20 @@
# that they have been altered from the originals.

"""Pegasos Quantum Support Vector Classifier."""
from __future__ import annotations

import logging
from datetime import datetime
from typing import Optional, Dict
from typing import Dict

import numpy as np
from qiskit.utils import algorithm_globals
from sklearn.base import ClassifierMixin

from ...algorithms.serializable_model import SerializableModelMixin
from ...exceptions import QiskitMachineLearningError
from ...kernels.quantum_kernel import QuantumKernel
from ...kernels import BaseKernel, FidelityQuantumKernel


logger = logging.getLogger(__name__)

Expand All @@ -38,7 +40,7 @@ class PegasosQSVC(ClassifierMixin, SerializableModelMixin):
.. code-block:: python
quantum_kernel = QuantumKernel()
quantum_kernel = FidelityQuantumKernel()
pegasos_qsvc = PegasosQSVC(quantum_kernel=quantum_kernel)
pegasos_qsvc.fit(sample_train, label_train)
Expand All @@ -56,15 +58,15 @@ class PegasosQSVC(ClassifierMixin, SerializableModelMixin):
# pylint: disable=invalid-name
def __init__(
self,
quantum_kernel: Optional[QuantumKernel] = None,
quantum_kernel: BaseKernel | None = None,
C: float = 1.0,
num_steps: int = 1000,
precomputed: bool = False,
seed: Optional[int] = None,
seed: int | None = None,
) -> None:
"""
Args:
quantum_kernel: QuantumKernel to be used for classification. Has to be ``None`` when
quantum_kernel: a quantum kernel to be used for classification. Has to be ``None`` when
a precomputed kernel is used.
C: Positive regularization parameter. The strength of the regularization is inversely
proportional to C. Smaller ``C`` induce smaller weights which generally helps
Expand All @@ -85,17 +87,16 @@ def __init__(
- if ``quantum_kernel`` is passed and ``precomputed`` is set to ``True``. To use
a precomputed kernel, ``quantum_kernel`` has to be of the ``None`` type.
TypeError:
- if ``quantum_instance`` neither instance of ``QuantumKernel`` nor ``None``.
- if ``quantum_kernel`` neither instance of
:class:`~qiskit_machine_learning.kernels.BaseKernel` nor ``None``.
"""

if precomputed:
if quantum_kernel is not None:
raise ValueError("'quantum_kernel' has to be None to use a precomputed kernel")
else:
if quantum_kernel is None:
quantum_kernel = QuantumKernel()
elif not isinstance(quantum_kernel, QuantumKernel):
raise TypeError("'quantum_kernel' has to be of type None or QuantumKernel")
quantum_kernel = FidelityQuantumKernel()

self._quantum_kernel = quantum_kernel
self._precomputed = precomputed
Expand All @@ -109,13 +110,13 @@ def __init__(
raise ValueError(f"C has to be a positive number, found {C}.")

# these are the parameters being fit and are needed for prediction
self._alphas: Optional[Dict[int, int]] = None
self._x_train: Optional[np.ndarray] = None
self._n_samples: Optional[int] = None
self._y_train: Optional[np.ndarray] = None
self._label_map: Optional[Dict[int, int]] = None
self._label_pos: Optional[int] = None
self._label_neg: Optional[int] = None
self._alphas: Dict[int, int] | None = None
self._x_train: np.ndarray | None = None
self._n_samples: int | None = None
self._y_train: np.ndarray | None = None
self._label_map: Dict[int, int] | None = None
self._label_pos: int | None = None
self._label_neg: int | None = None

# added to all kernel values to include an implicit bias to the hyperplane
self._kernel_offset = 1
Expand All @@ -125,12 +126,13 @@ def __init__(

# pylint: disable=invalid-name
def fit(
self, X: np.ndarray, y: np.ndarray, sample_weight: Optional[np.ndarray] = None
self, X: np.ndarray, y: np.ndarray, sample_weight: np.ndarray | None = None
) -> "PegasosQSVC":
"""Fit the model according to the given training data.
Args:
X: Train features. For a callable kernel (an instance of ``QuantumKernel``) the shape
X: Train features. For a callable kernel (an instance of
:class:`~qiskit_machine_learning.kernels.BaseKernel`) the shape
should be ``(n_samples, n_features)``, for a precomputed kernel the shape should be
``(n_samples, n_samples)``.
y: shape (n_samples), train labels . Must not contain more than two unique labels.
Expand Down Expand Up @@ -206,7 +208,8 @@ def predict(self, X: np.ndarray) -> np.ndarray:
Perform classification on samples in X.
Args:
X: Features. For a callable kernel (an instance of ``QuantumKernel``) the shape
X: Features. For a callable kernel (an instance of
:class:`~qiskit_machine_learning.kernels.BaseKernel`) the shape
should be ``(m_samples, n_features)``, for a precomputed kernel the shape should be
``(m_samples, n_samples)``. Where ``m`` denotes the set to be predicted and ``n`` the
size of the training set. In that case, the kernel values in X have to be calculated
Expand Down Expand Up @@ -234,7 +237,8 @@ def decision_function(self, X: np.ndarray) -> np.ndarray:
Evaluate the decision function for the samples in X.
Args:
X: Features. For a callable kernel (an instance of ``QuantumKernel``) the shape
X: Features. For a callable kernel (an instance of
:class:`~qiskit_machine_learning.kernels.BaseKernel`) the shape
should be ``(m_samples, n_features)``, for a precomputed kernel the shape should be
``(m_samples, n_samples)``. Where ``m`` denotes the set to be predicted and ``n`` the
size of the training set. In that case, the kernel values in X have to be calculated
Expand Down Expand Up @@ -302,12 +306,12 @@ def _compute_weighted_kernel_sum(self, index: int, X: np.ndarray, training: bool
return value

@property
def quantum_kernel(self) -> QuantumKernel:
def quantum_kernel(self) -> BaseKernel:
"""Returns quantum kernel"""
return self._quantum_kernel

@quantum_kernel.setter
def quantum_kernel(self, quantum_kernel: QuantumKernel):
def quantum_kernel(self, quantum_kernel: BaseKernel):
"""
Sets quantum kernel. If previously a precomputed kernel was set, it is reset to ``False``.
"""
Expand Down Expand Up @@ -340,14 +344,15 @@ def precomputed(self) -> bool:
@precomputed.setter
def precomputed(self, precomputed: bool):
"""Sets the pre-computed kernel flag. If ``True`` is passed then the previous kernel is
cleared. If ``False`` is passed then a new instance of ``QuantumKernel`` is created."""
cleared. If ``False`` is passed then a new instance of
:class:`~qiskit_machine_learning.kernels.FidelityQuantumKernel` is created."""
self._precomputed = precomputed
if precomputed:
# remove the kernel, a precomputed will
self._quantum_kernel = None
else:
# re-create a new default quantum kernel
self._quantum_kernel = QuantumKernel()
self._quantum_kernel = FidelityQuantumKernel()

# reset training status
self._reset_state()
Expand Down
12 changes: 6 additions & 6 deletions qiskit_machine_learning/algorithms/classifiers/qsvc.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

from qiskit_machine_learning.algorithms.serializable_model import SerializableModelMixin
from qiskit_machine_learning.exceptions import QiskitMachineLearningWarning
from qiskit_machine_learning.kernels.quantum_kernel import QuantumKernel
from qiskit_machine_learning.kernels import BaseKernel, FidelityQuantumKernel


class QSVC(SVC, SerializableModelMixin):
Expand All @@ -41,10 +41,10 @@ class QSVC(SVC, SerializableModelMixin):
qsvc.predict(sample_test)
"""

def __init__(self, *args, quantum_kernel: Optional[QuantumKernel] = None, **kwargs):
def __init__(self, *args, quantum_kernel: Optional[BaseKernel] = None, **kwargs):
"""
Args:
quantum_kernel: QuantumKernel to be used for classification.
quantum_kernel: Quantum kernel to be used for classification.
*args: Variable length argument list to pass to SVC constructor.
**kwargs: Arbitrary keyword arguments to pass to SVC constructor.
"""
Expand All @@ -65,20 +65,20 @@ def __init__(self, *args, quantum_kernel: Optional[QuantumKernel] = None, **kwar
# if we don't delete, then this value clashes with our quantum kernel
del kwargs["kernel"]

self._quantum_kernel = quantum_kernel if quantum_kernel else QuantumKernel()
self._quantum_kernel = quantum_kernel if quantum_kernel else FidelityQuantumKernel()

if "random_state" not in kwargs:
kwargs["random_state"] = algorithm_globals.random_seed

super().__init__(kernel=self._quantum_kernel.evaluate, *args, **kwargs)

@property
def quantum_kernel(self) -> QuantumKernel:
def quantum_kernel(self) -> BaseKernel:
"""Returns quantum kernel"""
return self._quantum_kernel

@quantum_kernel.setter
def quantum_kernel(self, quantum_kernel: QuantumKernel):
def quantum_kernel(self, quantum_kernel: BaseKernel):
"""Sets quantum kernel"""
self._quantum_kernel = quantum_kernel
self.kernel = self._quantum_kernel.evaluate
Expand Down
16 changes: 8 additions & 8 deletions qiskit_machine_learning/algorithms/regressors/qsvr.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

from sklearn.svm import SVR

from ..serializable_model import SerializableModelMixin
from ...exceptions import QiskitMachineLearningWarning
from ...kernels.quantum_kernel import QuantumKernel
from qiskit_machine_learning.algorithms.serializable_model import SerializableModelMixin
from qiskit_machine_learning.exceptions import QiskitMachineLearningWarning
from qiskit_machine_learning.kernels import BaseKernel, FidelityQuantumKernel


class QSVR(SVR, SerializableModelMixin):
Expand All @@ -40,10 +40,10 @@ class QSVR(SVR, SerializableModelMixin):
qsvr.predict(sample_test)
"""

def __init__(self, *args, quantum_kernel: Optional[QuantumKernel] = None, **kwargs):
def __init__(self, *args, quantum_kernel: Optional[BaseKernel] = None, **kwargs):
"""
Args:
quantum_kernel: QuantumKernel to be used for regression.
quantum_kernel: Quantum kernel to be used for regression.
*args: Variable length argument list to pass to SVR constructor.
**kwargs: Arbitrary keyword arguments to pass to SVR constructor.
"""
Expand All @@ -64,17 +64,17 @@ def __init__(self, *args, quantum_kernel: Optional[QuantumKernel] = None, **kwar
# if we don't delete, then this value clashes with our quantum kernel
del kwargs["kernel"]

self._quantum_kernel = quantum_kernel if quantum_kernel else QuantumKernel()
self._quantum_kernel = quantum_kernel if quantum_kernel else FidelityQuantumKernel()

super().__init__(kernel=self._quantum_kernel.evaluate, *args, **kwargs)

@property
def quantum_kernel(self) -> QuantumKernel:
def quantum_kernel(self) -> BaseKernel:
"""Returns quantum kernel"""
return self._quantum_kernel

@quantum_kernel.setter
def quantum_kernel(self, quantum_kernel: QuantumKernel):
def quantum_kernel(self, quantum_kernel: BaseKernel):
"""Sets quantum kernel"""
self._quantum_kernel = quantum_kernel
self.kernel = self._quantum_kernel.evaluate
Expand Down
18 changes: 16 additions & 2 deletions qiskit_machine_learning/kernels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
:nosignatures:
QuantumKernel
BaseKernel
FidelityQuantumKernel
TrainableKernel
TrainableFidelityQuantumKernel
Submodules
==========
Expand All @@ -34,5 +38,15 @@
"""

from .quantum_kernel import QuantumKernel

__all__ = ["QuantumKernel"]
from .base_kernel import BaseKernel
from .fidelity_quantum_kernel import FidelityQuantumKernel
from .trainable_kernel import TrainableKernel
from .trainable_fidelity_quantum_kernel import TrainableFidelityQuantumKernel

__all__ = [
"QuantumKernel",
"BaseKernel",
"FidelityQuantumKernel",
"TrainableKernel",
"TrainableFidelityQuantumKernel",
]
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
# that they have been altered from the originals.

"""Quantum Kernel Trainer"""

import copy
from functools import partial
from typing import Union, Optional, Sequence
Expand All @@ -22,23 +23,23 @@
from qiskit.algorithms.variational_algorithm import VariationalResult
from qiskit_machine_learning.utils.loss_functions import KernelLoss, SVCLoss

from qiskit_machine_learning.kernels import QuantumKernel
from qiskit_machine_learning.kernels import TrainableKernel


class QuantumKernelTrainerResult(VariationalResult):
"""Quantum Kernel Trainer Result."""

def __init__(self) -> None:
super().__init__()
self._quantum_kernel: QuantumKernel = None
self._quantum_kernel: TrainableKernel = None

@property
def quantum_kernel(self) -> Optional[QuantumKernel]:
def quantum_kernel(self) -> Optional[TrainableKernel]:
"""Return the optimized quantum kernel object."""
return self._quantum_kernel

@quantum_kernel.setter
def quantum_kernel(self, quantum_kernel: QuantumKernel) -> None:
def quantum_kernel(self, quantum_kernel: TrainableKernel) -> None:
self._quantum_kernel = quantum_kernel


Expand Down Expand Up @@ -66,10 +67,9 @@ class QuantumKernelTrainer:
for i, param in enumerate(input_params):
qc.rz(param, qc.qubits[i])
quant_kernel = QuantumKernel(
quant_kernel = TrainableFidelityQuantumKernel(
feature_map=qc,
training_parameters=training_params,
quantum_instance=...
)
loss_func = ...
Expand All @@ -88,7 +88,7 @@ class QuantumKernelTrainer:

def __init__(
self,
quantum_kernel: QuantumKernel,
quantum_kernel: TrainableKernel,
loss: Optional[Union[str, KernelLoss]] = None,
optimizer: Optional[Optimizer] = None,
initial_point: Optional[Sequence[float]] = None,
Expand Down Expand Up @@ -118,12 +118,12 @@ def __init__(
self._set_loss(loss)

@property
def quantum_kernel(self) -> QuantumKernel:
def quantum_kernel(self) -> TrainableKernel:
"""Return the quantum kernel object."""
return self._quantum_kernel

@quantum_kernel.setter
def quantum_kernel(self, quantum_kernel: QuantumKernel) -> None:
def quantum_kernel(self, quantum_kernel: TrainableKernel) -> None:
"""Set the quantum kernel."""
self._quantum_kernel = quantum_kernel

Expand Down
Loading

0 comments on commit 66e0476

Please sign in to comment.