Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
71e1bf1
Add primitives branch to CI workflow
manoelmarques Jun 29, 2022
ae9c043
added pocs
gentinettagian Jun 29, 2022
3aae2df
init fixed
gentinettagian Jun 30, 2022
fa98e91
more init
gentinettagian Jun 30, 2022
5106333
Add qnns PoC
ElePT Jul 5, 2022
eecbdab
Establish qnn branch
ElePT Jul 8, 2022
a212196
Add fwd unit test
ElePT Jul 11, 2022
81f8082
Refactor sampler qnn
ElePT Jul 11, 2022
16f7d84
Merge branch 'Qiskit:main' into primitives-qnn
ElePT Jul 11, 2022
ada284b
Undo kernel changes
ElePT Jul 11, 2022
8b42377
Merge branch 'primitives-qnn' of https://github.com/ElePT/qiskit-mach…
ElePT Jul 11, 2022
b2d42f2
Start adding new type hint style
ElePT Jul 14, 2022
d6574d3
Move to neural_networks
ElePT Jul 14, 2022
c67ed15
Merge from main
manoelmarques Jul 14, 2022
d2342e0
Merge branch 'main'
manoelmarques Jul 15, 2022
a0c8847
Merge branch 'main'
manoelmarques Jul 21, 2022
ed50342
Merge branch 'primitives' into primitives-qnn
adekusar-drl Jul 22, 2022
3bbb1c7
Merge branch 'primitives-qnn' of https://github.com/ElePT/qiskit-mach…
ElePT Sep 13, 2022
a476678
Merge branch 'Qiskit:main' into primitives-qnn
ElePT Sep 13, 2022
af9d729
Merge branch 'primitives-qnn' of https://github.com/ElePT/qiskit-mach…
ElePT Sep 13, 2022
fc0f584
Runnable tests
ElePT Sep 14, 2022
7963cbc
Update typehints, refactor
ElePT Oct 10, 2022
8248138
Add new unittests, fix code
ElePT Oct 10, 2022
f4f1771
Add gradient unit test
ElePT Oct 10, 2022
7bf715d
Add torch tests
ElePT Oct 10, 2022
aeb05f0
Remove unwanted change circuitQNN
ElePT Oct 10, 2022
f727ea0
Remove utils
ElePT Oct 10, 2022
b36a7c7
Merge branch 'primitives-qnn' of https://github.com/ElePT/qiskit-mach…
ElePT Oct 10, 2022
0a104ae
Fix style, lint
ElePT Oct 10, 2022
f4521d9
Merge branch 'main' into primitives-qnn
ElePT Oct 10, 2022
c9abd91
Add VQC
ElePT Oct 10, 2022
1404fea
Merge branch 'primitives-qnn' of https://github.com/ElePT/qiskit-mach…
ElePT Oct 10, 2022
16b2ee3
Fix mypy
ElePT Oct 10, 2022
c36cbc2
fix mypy
ElePT Oct 10, 2022
cde4877
fix mypy
ElePT Oct 10, 2022
551c7ac
fix mypy
ElePT Oct 10, 2022
f9d11e6
Restore workflow
ElePT Oct 10, 2022
3e46137
Restore workflow
ElePT Oct 10, 2022
af88a14
Update .github/workflows/main.yml
ElePT Oct 10, 2022
3284851
Fix mypy
ElePT Oct 11, 2022
99d9afe
Merge branch 'primitives-qnn' of https://github.com/ElePT/qiskit-mach…
ElePT Oct 11, 2022
13b8c89
Fix CI (hopefully)
ElePT Oct 11, 2022
9b350ba
Update requirements
ElePT Oct 11, 2022
0252925
Add sparse support
ElePT Oct 12, 2022
5974c2e
Merge branch 'main' into primitives-qnn
ElePT Oct 12, 2022
d5e18e5
Fix style etc
ElePT Oct 12, 2022
e40c893
Add type ignore
ElePT Oct 12, 2022
16441a6
Fix style?
ElePT Oct 12, 2022
ca9e1c5
skip test if not sparse
ElePT Oct 12, 2022
8fc31d0
Add type ignore
ElePT Oct 12, 2022
82c9613
Fix mypy
manoelmarques Oct 12, 2022
b15d380
Make keyword args
ElePT Oct 13, 2022
97ea62a
Merge branch 'primitives-qnn' of https://github.com/ElePT/qiskit-mach…
ElePT Oct 13, 2022
108255f
Make keyword args
ElePT Oct 13, 2022
f5deb21
Merge branch 'main' into primitives-qnn
ElePT Oct 14, 2022
a9f0179
Apply review comments
ElePT Oct 14, 2022
b2db7b8
Fix tests, final refactor, add docstring
ElePT Oct 17, 2022
f3a7991
Add Sampler QNN
ElePT Oct 17, 2022
349fb8a
Update qiskit_machine_learning/algorithms/classifiers/vqc.py
ElePT Oct 19, 2022
04042ba
Apply reviews vqc
ElePT Oct 19, 2022
5c5e705
Apply reviews
ElePT Oct 19, 2022
d57b3c1
Merge branch 'primitives-qnn' of https://github.com/ElePT/qiskit-mach…
ElePT Oct 19, 2022
c2eaf90
Fix neko test
ElePT Oct 19, 2022
8dd2d78
Fix spell check
ElePT Oct 19, 2022
f2ee6e5
Merge branch 'main' into primitives-qnn
adekusar-drl Oct 21, 2022
715affd
Merge branch 'main' into primitives-qnn
adekusar-drl Oct 25, 2022
f601492
Update qiskit_machine_learning/neural_networks/sampler_qnn.py
ElePT Oct 27, 2022
a0d92ec
Add try-catch
ElePT Oct 28, 2022
959621a
Add deprecations
ElePT Oct 28, 2022
c5a6c11
Update tests
ElePT Oct 28, 2022
f7394c6
Fix tests
ElePT Oct 28, 2022
438a434
Filter warnings
ElePT Oct 28, 2022
7824b07
Fix filter
ElePT Oct 28, 2022
35bdc54
fix black, pylint
adekusar-drl Oct 28, 2022
af3b99f
Merge branch 'main' into primitives-qnn
adekusar-drl Oct 28, 2022
0259698
Merge branch 'main' of https://github.com/Qiskit/qiskit-machine-learn…
adekusar-drl Nov 1, 2022
12c6476
update docstring
adekusar-drl Nov 1, 2022
4f43cd7
pulled the method up, modern type hints
adekusar-drl Nov 1, 2022
b8c1042
fix spell
adekusar-drl Nov 1, 2022
79b221a
Update qiskit_machine_learning/neural_networks/sampler_qnn.py
adekusar-drl Nov 1, 2022
f5a4698
Update qiskit_machine_learning/neural_networks/sampler_qnn.py
adekusar-drl Nov 1, 2022
8124e65
code review
adekusar-drl Nov 1, 2022
024d136
Merge remote-tracking branch 'ElePT/primitives-qnn' into primitives-qnn
adekusar-drl Nov 1, 2022
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
81 changes: 55 additions & 26 deletions qiskit_machine_learning/algorithms/classifiers/vqc.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,18 @@
"""An implementation of variational quantum classifier."""

from __future__ import annotations
from typing import Callable, cast
from typing import Callable

import numpy as np

from qiskit import QuantumCircuit
from qiskit.providers import Backend
from qiskit.utils import QuantumInstance
from qiskit.algorithms.optimizers import Optimizer, OptimizerResult
from qiskit.primitives import BaseSampler

from ...neural_networks import CircuitQNN
from ...deprecation import warn_deprecated, DeprecatedType
from ...neural_networks import CircuitQNN, SamplerQNN
from ...utils import derive_num_qubits_feature_map_ansatz
from ...utils.loss_functions import Loss

Expand Down Expand Up @@ -56,32 +58,42 @@ def __init__(
quantum_instance: QuantumInstance | Backend | None = None,
initial_point: np.ndarray | None = None,
callback: Callable[[np.ndarray, float], None] | None = None,
*,
sampler: BaseSampler | None = None,
) -> None:
"""
Args:
num_qubits: The number of qubits for the underlying
:class:`~qiskit_machine_learning.neural_networks.CircuitQNN`. If ``None`` is given,
the number of qubits is derived from the feature map or ansatz. If neither of those
is given, raises an exception. The number of qubits in the feature map and ansatz
are adjusted to this number if required.
num_qubits: The number of qubits for the underlying QNN.
If ``None`` is given, the number of qubits is derived from the
feature map or ansatz. If neither of those is given, raises an exception.
The number of qubits in the feature map and ansatz are adjusted to this
number if required.
feature_map: The (parametrized) circuit to be used as a feature map for the underlying
:class:`~qiskit_machine_learning.neural_networks.CircuitQNN`. If ``None`` is given,
the ``ZZFeatureMap`` is used if the number of qubits is larger than 1. For a single
qubit classification problem the ``ZFeatureMap`` is used per default.
QNN. If ``None`` is given, the ``ZZFeatureMap`` is used if the number of qubits
is larger than 1. For a single qubit classification problem the ``ZFeatureMap``
is used by default.
ansatz: The (parametrized) circuit to be used as an ansatz for the underlying
:class:`~qiskit_machine_learning.neural_networks.CircuitQNN`. If ``None`` is given
then the ``RealAmplitudes`` circuit is used.
QNN. If ``None`` is given then the ``RealAmplitudes`` circuit is used.
loss: A target loss function to be used in training. Default value is ``cross_entropy``.
optimizer: An instance of an optimizer to be used in training. When ``None`` defaults
to SLSQP.
warm_start: Use weights from previous fit to start next fit.
quantum_instance: The quantum instance to execute circuits on.
quantum_instance: Deprecated: If a quantum instance is sent and ``sampler`` is ``None``,
the underlying QNN will be of type
:class:`~qiskit_machine_learning.neural_networks.CircuitQNN`, and the quantum
instance will be used to compute the neural network's results. If a sampler
instance is also set, it will override the `quantum_instance` parameter and
a :class:`~qiskit_machine_learning.neural_networks.SamplerQNN`
will be used instead.
initial_point: Initial point for the optimizer to start from.
callback: a reference to a user's callback function that has two parameters and
returns ``None``. The callback can access intermediate data during training.
On each iteration an optimizer invokes the callback and passes current weights
as an array and a computed value as a float of the objective function being
optimized. This allows to track how well optimization / training process is going on.
sampler: If a sampler instance is sent, the underlying QNN will be of type
:class:`~qiskit_machine_learning.neural_networks.SamplerQNN`, and the sampler
primitive will be used to compute the neural network's results.
Raises:
QiskitMachineLearningError: Needs at least one out of ``num_qubits``, ``feature_map`` or
``ansatz`` to be given. Or the number of qubits in the feature map and/or ansatz
Expand All @@ -100,16 +112,32 @@ def __init__(
self._circuit.compose(self.feature_map, inplace=True)
self._circuit.compose(self.ansatz, inplace=True)

# construct circuit QNN
neural_network = CircuitQNN(
self._circuit,
input_params=self.feature_map.parameters,
weight_params=self.ansatz.parameters,
interpret=self._get_interpret(2),
output_shape=2,
quantum_instance=quantum_instance,
input_gradients=False,
)
# needed for mypy
neural_network: SamplerQNN | CircuitQNN = None
if quantum_instance is not None and sampler is None:
warn_deprecated(
"0.5.0", DeprecatedType.ARGUMENT, old_name="quantum_instance", new_name="sampler"
)
neural_network = CircuitQNN(
self._circuit,
input_params=self.feature_map.parameters,
weight_params=self.ansatz.parameters,
interpret=self._get_interpret(2),
output_shape=2,
quantum_instance=quantum_instance,
input_gradients=False,
)
else:
# construct sampler QNN by default
neural_network = SamplerQNN(
sampler=sampler,
circuit=self._circuit,
input_params=self.feature_map.parameters,
weight_params=self.ansatz.parameters,
interpret=self._get_interpret(2),
output_shape=2,
input_gradients=False,
)

super().__init__(
neural_network=neural_network,
Expand Down Expand Up @@ -154,9 +182,10 @@ def _fit_internal(self, X: np.ndarray, y: np.ndarray) -> OptimizerResult:
"""
X, y = self._validate_input(X, y)
num_classes = self._num_classes
cast(CircuitQNN, self._neural_network).set_interpret(
self._get_interpret(num_classes), num_classes
)

# instance check required by mypy (alternative to cast)
if isinstance(self._neural_network, (CircuitQNN, SamplerQNN)):
self._neural_network.set_interpret(self._get_interpret(num_classes), num_classes)

return super()._minimize(X, y)

Expand Down
4 changes: 3 additions & 1 deletion qiskit_machine_learning/neural_networks/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
TwoLayerQNN
CircuitQNN
EstimatorQNN
SamplerQNN

Neural Network Metrics
======================
Expand All @@ -57,7 +58,6 @@

EffectiveDimension
LocalEffectiveDimension

"""

from .circuit_qnn import CircuitQNN
Expand All @@ -67,6 +67,7 @@
from .opflow_qnn import OpflowQNN
from .sampling_neural_network import SamplingNeuralNetwork
from .two_layer_qnn import TwoLayerQNN
from .sampler_qnn import SamplerQNN

__all__ = [
"NeuralNetwork",
Expand All @@ -77,4 +78,5 @@
"EffectiveDimension",
"LocalEffectiveDimension",
"EstimatorQNN",
"SamplerQNN",
]
2 changes: 1 addition & 1 deletion qiskit_machine_learning/neural_networks/circuit_qnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class SparseArray: # type: ignore


class CircuitQNN(SamplingNeuralNetwork):
"""A Sampling Neural Network based on a given quantum circuit."""
"""A sampling neural network based on a given quantum circuit."""

def __init__(
self,
Expand Down
27 changes: 2 additions & 25 deletions qiskit_machine_learning/neural_networks/estimator_qnn.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,29 +171,6 @@ def input_gradients(self, input_gradients: bool) -> None:
"""Turn on/off computation of gradients with respect to input data."""
self._input_gradients = input_gradients

def _preprocess(
self,
input_data: np.ndarray | None,
weights: np.ndarray | None,
) -> tuple[np.ndarray | None, int | None]:
"""
Pre-processing during forward pass of the network.
"""
if input_data is not None:
num_samples = input_data.shape[0]
if weights is not None:
weights = np.broadcast_to(weights, (num_samples, len(weights)))
parameters = np.concatenate((input_data, weights), axis=1)
else:
parameters = input_data
else:
if weights is not None:
num_samples = 1
parameters = np.broadcast_to(weights, (num_samples, len(weights)))
else:
return None, None
return parameters, num_samples

def _forward_postprocess(self, num_samples: int, result: EstimatorResult) -> np.ndarray:
"""Post-processing during forward pass of the network."""
if num_samples is None:
Expand All @@ -205,7 +182,7 @@ def _forward(
self, input_data: np.ndarray | None, weights: np.ndarray | None
) -> np.ndarray | None:
"""Forward pass of the neural network."""
parameter_values_, num_samples = self._preprocess(input_data, weights)
parameter_values_, num_samples = self._preprocess_forward(input_data, weights)
if num_samples is None:
job = self.estimator.run(self._circuit, self._observables)
else:
Expand Down Expand Up @@ -250,7 +227,7 @@ def _backward(
) -> tuple[np.ndarray | None, np.ndarray]:
"""Backward pass of the network."""
# prepare parameters in the required format
parameter_values_, num_samples = self._preprocess(input_data, weights)
parameter_values_, num_samples = self._preprocess_forward(input_data, weights)

if num_samples is None or (not self._input_gradients and self._num_weights == 0):
return None, None
Expand Down
63 changes: 43 additions & 20 deletions qiskit_machine_learning/neural_networks/neural_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
"""A Neural Network abstract class for all (quantum) neural networks within Qiskit's
machine learning module."""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Tuple, Union, List, Optional

import numpy as np

Expand Down Expand Up @@ -45,7 +45,7 @@ def __init__(
num_inputs: int,
num_weights: int,
sparse: bool,
output_shape: Union[int, Tuple[int, ...]],
output_shape: int | tuple[int, ...],
input_gradients: bool = False,
) -> None:
"""
Expand Down Expand Up @@ -92,7 +92,7 @@ def sparse(self) -> bool:
return self._sparse

@property
def output_shape(self) -> Tuple[int, ...]:
def output_shape(self) -> tuple[int, ...]:
"""Returns the output shape."""
return self._output_shape

Expand All @@ -117,8 +117,8 @@ def _validate_output_shape(self, output_shape):
return output_shape

def _validate_input(
self, input_data: Optional[Union[List[float], np.ndarray, float]]
) -> Tuple[Union[np.ndarray, None], Union[Tuple[int, ...], None]]:
self, input_data: float | list[float] | np.ndarray | None
) -> tuple[np.ndarray | None, tuple[int, ...] | None]:
if input_data is None:
return None, None
input_ = np.array(input_data)
Expand All @@ -144,16 +144,39 @@ def _validate_input(

return input_, shape

def _preprocess_forward(
self,
input_data: np.ndarray | None,
weights: np.ndarray | None,
) -> tuple[np.ndarray | None, int | None]:
"""
Pre-processing during forward pass of the network for the primitive-based networks.
"""
if input_data is not None:
num_samples = input_data.shape[0]
if weights is not None:
weights = np.broadcast_to(weights, (num_samples, len(weights)))
parameters = np.concatenate((input_data, weights), axis=1)
else:
parameters = input_data
else:
if weights is not None:
num_samples = 1
parameters = np.broadcast_to(weights, (num_samples, len(weights)))
else:
return None, None
return parameters, num_samples

def _validate_weights(
self, weights: Optional[Union[List[float], np.ndarray, float]]
) -> Union[np.ndarray, None]:
self, weights: float | list[float] | np.ndarray | None
) -> np.ndarray | None:
if weights is None:
return None
weights_ = np.array(weights)
return weights_.reshape(self._num_weights)

def _validate_forward_output(
self, output_data: np.ndarray, original_shape: Tuple[int, ...]
self, output_data: np.ndarray, original_shape: tuple[int, ...]
) -> np.ndarray:
if original_shape and len(original_shape) >= 2:
output_data = output_data.reshape((*original_shape[:-1], *self._output_shape))
Expand All @@ -164,8 +187,8 @@ def _validate_backward_output(
self,
input_grad: np.ndarray,
weight_grad: np.ndarray,
original_shape: Tuple[int, ...],
) -> Tuple[Union[np.ndarray, SparseArray], Union[np.ndarray, SparseArray]]:
original_shape: tuple[int, ...],
) -> tuple[np.ndarray | SparseArray, np.ndarray | SparseArray]:
if input_grad is not None and np.prod(input_grad.shape) == 0:
input_grad = None
if input_grad is not None and original_shape and len(original_shape) >= 2:
Expand All @@ -183,9 +206,9 @@ def _validate_backward_output(

def forward(
self,
input_data: Optional[Union[List[float], np.ndarray, float]],
weights: Optional[Union[List[float], np.ndarray, float]],
) -> Union[np.ndarray, SparseArray]:
input_data: float | list[float] | np.ndarray | None,
weights: float | list[float] | np.ndarray | None,
) -> np.ndarray | SparseArray:
"""Forward pass of the network.

Args:
Expand All @@ -203,15 +226,15 @@ def forward(

@abstractmethod
def _forward(
self, input_data: Optional[np.ndarray], weights: Optional[np.ndarray]
) -> Union[np.ndarray, SparseArray]:
self, input_data: np.ndarray | None, weights: np.ndarray | None
) -> np.ndarray | SparseArray:
raise NotImplementedError

def backward(
self,
input_data: Optional[Union[List[float], np.ndarray, float]],
weights: Optional[Union[List[float], np.ndarray, float]],
) -> Tuple[Optional[Union[np.ndarray, SparseArray]], Optional[Union[np.ndarray, SparseArray]],]:
input_data: float | list[float] | np.ndarray | None,
weights: float | list[float] | np.ndarray | None,
) -> tuple[np.ndarray | SparseArray | None, np.ndarray | SparseArray | None]:
"""Backward pass of the network.

Args:
Expand All @@ -236,6 +259,6 @@ def backward(

@abstractmethod
def _backward(
self, input_data: Optional[np.ndarray], weights: Optional[np.ndarray]
) -> Tuple[Optional[Union[np.ndarray, SparseArray]], Optional[Union[np.ndarray, SparseArray]],]:
self, input_data: np.ndarray | None, weights: np.ndarray | None
) -> tuple[np.ndarray | SparseArray | None, np.ndarray | SparseArray | None]:
raise NotImplementedError
Loading