Skip to content

Commit

Permalink
[Stable] Prepare 0.2.1 release (#193)
Browse files Browse the repository at this point in the history
* Update default batch_size in quantum_kernel (#150)

The max circuits per job for most/all backend devices is 900. Setting batch_size=900 fixes an inefficient jobs being created.

Co-authored-by: Anton Dekusar <adekusar@ie.ibm.com>
Co-authored-by: Anton Dekusar <62334182+adekusar-drl@users.noreply.github.com>

* This adds the callback feature for NN Classifier and Regressor (#112) (#151)

* Added a wrapper `get_objective` method in `TrainableModel`, and `callback` argument to NN classifier

`get_objective` returns a callable which is passed as objective function to NN classifier.

It checks for a callable `callback` argument, which if not `None` can access the intermediate data during the optimization.

* style changes

* Updated callback tests for `NeuralNetworkClassifier`

* Added callback for `NeuralNetworkRegressor`

* Added tests for callback

* Refactored `get_objective`

* Added release note

* Updated `callback`  argument docstring

* Shifted `callback` parameter to parent class `TrainableModel`

* Added assertions for number of weights in `TestNeuralNetworkClassifier` `TestNeuralNetworkRegressor`

* Rephrased release note and debugged tests for callback

Co-authored-by: Anton Dekusar <62334182+adekusar-drl@users.noreply.github.com>
Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
Co-authored-by: Manoel Marques <Manoel.Marques@ibm.com>

* Added ability to automatically handle categorical labels (#62) (#143)

Added categorical label encoding

* Added documentation

* Update qiskit_machine_learning/algorithms/classifiers/neural_network_classifier.py

* Update releasenotes/notes/cateforical-output-049e682adc9d1e28.yaml

* Update releasenotes/notes/cateforical-output-049e682adc9d1e28.yaml

* Update releasenotes/notes/cateforical-output-049e682adc9d1e28.yaml

* Cleaned and organized code

* Update qiskit_machine_learning/algorithms/classifiers/neural_network_classifier.py

* Update qiskit_machine_learning/algorithms/classifiers/neural_network_classifier.py

* Update qiskit_machine_learning/algorithms/classifiers/neural_network_classifier.py

* Split categorical encoding into two functions for classifier's fit and score methods

* Set sparse=False in one-hot encoder

* removed unnecessary copy of arrays

* Renamed methods and fixed label reshape

* Added explanatory comments

Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
Co-authored-by: Manoel Marques <Manoel.Marques@ibm.com>
Co-authored-by: Anton Dekusar <adekusar@ie.ibm.com>
Co-authored-by: Anton Dekusar <62334182+adekusar-drl@users.noreply.github.com>

* Add hybrid qnn unit tests for TorchConnector (#156)

* Add hybrid qnn unit tests

* Fix spelling

* Add docstrings

* Retry

* Fix style

* Register weight name

* Update test

* Separate hybrid tests

* Fix spelling

* Fix lint

* Add comments

* Remove unused functions

* Add register weight param. reno

* Clarify procedure

* Remove unused import

Co-authored-by: Anton Dekusar <62334182+adekusar-drl@users.noreply.github.com>
Co-authored-by: Manoel Marques <Manoel.Marques@ibm.com>

* Fixed variable name in file 03_quantum_kernel.ipynb . (#179)

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

* Added getter/setter, exception handling for OpflowQNN, CircuitQNN  (#79)

* added getter and setter, raises exception if no qi

* set sampler in qi setter, added tests

* added logic to setters, fixed tests

* fix black

* calling setter with instantiation

* setters no longer accept optional qi

* undid changes to docstrings

* reset output shape within qi setter

* revised implementation

* fix spell

* fix mypy

* Update qiskit_machine_learning/neural_networks/circuit_qnn.py

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

* Update qiskit_machine_learning/neural_networks/circuit_qnn.py

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

* Update qiskit_machine_learning/neural_networks/opflow_qnn.py

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

* code review

* updated docstrings

* Update qiskit_machine_learning/neural_networks/circuit_qnn.py

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

* Update qiskit_machine_learning/neural_networks/circuit_qnn.py

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

* code review

* docstrings

Co-authored-by: Anton Dekusar <adekusar@ie.ibm.com>
Co-authored-by: Anton Dekusar <62334182+adekusar-drl@users.noreply.github.com>
Co-authored-by: Manoel Marques <Manoel.Marques@ibm.com>
Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
Co-authored-by: dlasecki <dal@zurich.ibm.com>

* Global random seed (#178)

* Made TrainableModel use global seed (issue #158)

Made TrainableModel use RandomState object seeded by algorithm_globals.random_seed to choose an initial point for the optimizer outside of warm start.

* Global random seed for QSVC constructor (issue #158)

The value of algorithm_globals.random_seed is being used as random_state parameter for SVC constructor during construction of QSVC object.

* Added Qiskit into requirements.txt (issu #158)

Added Qiskit~=0.26.0 into requirements.txt, as qiskit.utils contain algorithm_globals object.

* Added Qiskit into requirements.txt & improved code formatting (issue #158)

Added Qiskit~=0.26.0 into requirements.txt, as qiskit.utils contain algorithm_globals object.

* Removed unnecessary explicit requirement of Qiskit package (issue #158).

* Simplified random number generation using global seed (issue #158).

* random_state is now being randomly initialized only if not passed as parameter (issue #158).

* Parameter 'random_state' now initialized by 'random_seed' in kwargs (issue #158).

* Update qiskit_machine_learning/algorithms/trainable_model.py

* Reverted incorrect changes to requirements.txt (issue #158).

Co-authored-by: Anton Dekusar <62334182+adekusar-drl@users.noreply.github.com>
Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>

* Fix `RawFeatureVector` copy (#182)

* Fix num_qubits

* Remove _num_qubits

* Fix style

* Add tests for copy, bind_parameters

* Add Fix RawFeatVec reno

* Fix typing

* Fix test

* Add reset registers

* Fix num_qubits>0

* Fix mypy conflict

* Fix black

* Update if check

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

* Add if check

* Add comment

* Modify reno

* code review

* code review

* updated docstrings

* code review

Co-authored-by: Anton Dekusar <62334182+adekusar-drl@users.noreply.github.com>
Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
Co-authored-by: Anton Dekusar <adekusar@ie.ibm.com>

* Prepare 0.2.1 release

Co-authored-by: Anna Phan <9410731+attp@users.noreply.github.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: Darsh Kaushik <70013230+darshkaushik@users.noreply.github.com>
Co-authored-by: Steve Wood <40241007+woodsp-ibm@users.noreply.github.com>
Co-authored-by: Matt Wright <44040188+mattwright99@users.noreply.github.com>
Co-authored-by: ElePT <57907331+ElePT@users.noreply.github.com>
Co-authored-by: Martin Beseda <martinbeseda@seznam.cz>
Co-authored-by: Stephen Murray <stephenandrewmurray@gmail.com>
Co-authored-by: dlasecki <dal@zurich.ibm.com>
Co-authored-by: Martin Beseda <martin.beseda@vsb.cz>
  • Loading branch information
12 people committed Aug 24, 2021
1 parent 03cbe77 commit ec3cbcc
Show file tree
Hide file tree
Showing 26 changed files with 1,082 additions and 232 deletions.
1 change: 1 addition & 0 deletions .pylintdict
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ sy
tensored
th
toctree
todo
traceback
transpilation
uncompiled
Expand Down
2 changes: 1 addition & 1 deletion docs/tutorials/03_quantum_kernel.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@
"qsvc.fit(train_features, train_labels)\n",
"qsvc_score = qsvc.score(test_features, test_labels)\n",
"\n",
"print(f'QSVC classification test score: {adhoc_score}')"
"print(f'QSVC classification test score: {qsvc_score}')"
]
},
{
Expand Down
2 changes: 1 addition & 1 deletion qiskit_machine_learning/VERSION.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.2.0
0.2.1
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
# that they have been altered from the originals.
"""An implementation of quantum neural network classifier."""

from typing import Union, Optional
from typing import Union, Optional, Callable

import numpy as np
from qiskit.algorithms.optimizers import Optimizer
from sklearn.base import ClassifierMixin
from sklearn.preprocessing import OneHotEncoder, LabelEncoder

from ..objective_functions import (
BinaryObjectiveFunction,
Expand Down Expand Up @@ -43,6 +44,7 @@ def __init__(
optimizer: Optional[Optimizer] = None,
warm_start: bool = False,
initial_point: np.ndarray = None,
callback: Optional[Callable[[np.ndarray, float], None]] = None,
):
"""
Args:
Expand All @@ -65,18 +67,27 @@ def __init__(
one-hot-encoded sample (e.g. for 'CrossEntropy' loss function), and if False
as a set of individual predictions with occurrence probabilities (the index would be
the prediction and the value the corresponding frequency, e.g. for absolute/squared
loss). This option is ignored in case of a one-dimensional output.
loss). In case of a one-dimensional categorical output, this option determines how
to encode the target data (i.e. one-hot or integer encoding).
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.
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.
Raises:
QiskitMachineLearningError: unknown loss, invalid neural network
"""
super().__init__(neural_network, loss, optimizer, warm_start, initial_point)
super().__init__(neural_network, loss, optimizer, warm_start, initial_point, callback)
self._one_hot = one_hot
# encodes the target data if categorical
self._target_encoder = OneHotEncoder(sparse=False) if one_hot else LabelEncoder()

def fit(self, X: np.ndarray, y: np.ndarray): # pylint: disable=invalid-name
y = self._fit_and_encode_labels(y)

# mypy definition
function: ObjectiveFunction = None
if self._neural_network.output_shape == (1,):
Expand All @@ -91,9 +102,11 @@ def fit(self, X: np.ndarray, y: np.ndarray): # pylint: disable=invalid-name
else:
function = MultiClassObjectiveFunction(X, y, self._neural_network, self._loss)

objective = self._get_objective(function)

self._fit_result = self._optimizer.optimize(
self._neural_network.num_weights,
function.objective,
objective,
function.gradient,
initial_point=self._choose_initial_point(),
)
Expand All @@ -119,4 +132,32 @@ def predict(self, X: np.ndarray) -> np.ndarray: # pylint: disable=invalid-name
def score(
self, X: np.ndarray, y: np.ndarray, sample_weight: Optional[np.ndarray] = None
) -> float:
y = self._encode_labels(y)
return ClassifierMixin.score(self, X, y, sample_weight)

def _fit_and_encode_labels(self, y: np.ndarray) -> np.ndarray:
"""Fits label or one-hot encoder and converts categorical target data."""

if isinstance(y[0], str):
# string data is assumed to be categorical

# OneHotEncoder expects data with shape (n_samples, n_features) but
# LabelEncoder expects shape (n_samples,) so set desired shape
y = y.reshape(-1, 1) if self._one_hot else y
self._target_encoder.fit(y)
y = self._target_encoder.transform(y)

return y

def _encode_labels(self, y: np.ndarray) -> np.ndarray:
"""Converts categorical target data using label or one-hot encoding."""

if isinstance(y[0], str):
# string data is assumed to be categorical

# OneHotEncoder expects data with shape (n_samples, n_features) but
# LabelEncoder expects shape (n_samples,) so set desired shape
y = y.reshape(-1, 1) if self._one_hot else y
y = self._target_encoder.transform(y)

return y
11 changes: 10 additions & 1 deletion qiskit_machine_learning/algorithms/classifiers/qsvc.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

from sklearn.svm import SVC

from qiskit.utils.algorithm_globals import algorithm_globals

from qiskit_machine_learning.kernels.quantum_kernel import QuantumKernel


Expand Down Expand Up @@ -47,7 +49,14 @@ def __init__(self, *args, quantum_kernel: Optional[QuantumKernel] = None, **kwar

self._quantum_kernel = quantum_kernel if quantum_kernel else QuantumKernel()

super().__init__(kernel=self._quantum_kernel.evaluate, *args, **kwargs)
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:
Expand Down
9 changes: 8 additions & 1 deletion qiskit_machine_learning/algorithms/classifiers/vqc.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# that they have been altered from the originals.
"""An implementation of quantum neural network classifier."""

from typing import Union, Optional, cast
from typing import Union, Optional, Callable, cast
import numpy as np

from qiskit import QuantumCircuit
Expand Down Expand Up @@ -39,6 +39,7 @@ def __init__(
warm_start: bool = False,
quantum_instance: QuantumInstance = None,
initial_point: np.ndarray = None,
callback: Optional[Callable[[np.ndarray, float], None]] = None,
) -> None:
"""
Args:
Expand All @@ -50,6 +51,11 @@ def __init__(
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.
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.
Raises:
QiskitMachineLearningError: Needs at least one out of num_qubits, feature_map or
ansatz to be given.
Expand Down Expand Up @@ -119,6 +125,7 @@ def __init__(
optimizer=optimizer,
warm_start=warm_start,
initial_point=initial_point,
callback=callback,
)

@property
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,11 @@ def fit(self, X: np.ndarray, y: np.ndarray): # pylint: disable=invalid-name
else:
function = MultiClassObjectiveFunction(X, y, self._neural_network, self._loss)

objective = self._get_objective(function)

self._fit_result = self._optimizer.optimize(
self._neural_network.num_weights,
function.objective,
objective,
function.gradient,
initial_point=self._choose_initial_point(),
)
Expand Down
10 changes: 8 additions & 2 deletions qiskit_machine_learning/algorithms/regressors/vqr.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# that they have been altered from the originals.
"""An implementation of quantum neural network regressor."""

from typing import Union, Optional, cast
from typing import Union, Optional, Callable, cast

import numpy as np

Expand Down Expand Up @@ -39,6 +39,7 @@ def __init__(
warm_start: bool = False,
quantum_instance: QuantumInstance = None,
initial_point: np.ndarray = None,
callback: Optional[Callable[[np.ndarray, float], None]] = None,
) -> None:
r"""
Args:
Expand All @@ -54,7 +55,11 @@ def __init__(
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.
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.
Raises:
QiskitMachineLearningError: Neither num_qubits, nor feature_map, nor ansatz given.
"""
Expand All @@ -75,6 +80,7 @@ def __init__(
optimizer=optimizer,
warm_start=warm_start,
initial_point=initial_point,
callback=callback,
)

@property
Expand Down
39 changes: 36 additions & 3 deletions qiskit_machine_learning/algorithms/trainable_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
"""A base ML model with a Scikit-Learn like interface."""

from abc import abstractmethod
from typing import Union, Optional
from typing import Union, Optional, Callable

import numpy as np
from qiskit.algorithms.optimizers import Optimizer, SLSQP
from qiskit.utils import algorithm_globals

from qiskit_machine_learning import QiskitMachineLearningError
from qiskit_machine_learning.neural_networks import NeuralNetwork
Expand All @@ -28,6 +29,8 @@
)
from qiskit_machine_learning.deprecation import deprecate_values

from .objective_functions import ObjectiveFunction


class TrainableModel:
"""Base class for ML model. This class defines Scikit-Learn like interface to implement."""
Expand All @@ -42,6 +45,7 @@ def __init__(
optimizer: Optional[Optimizer] = None,
warm_start: bool = False,
initial_point: np.ndarray = None,
callback: Optional[Callable[[np.ndarray, float], None]] = None,
):
"""
Args:
Expand All @@ -62,7 +66,11 @@ def __init__(
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.
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.
Raises:
QiskitMachineLearningError: unknown loss, invalid neural network
"""
Expand Down Expand Up @@ -94,6 +102,7 @@ def __init__(
self._warm_start = warm_start
self._fit_result = None
self._initial_point = initial_point
self._callback = callback

@property
def neural_network(self):
Expand Down Expand Up @@ -194,5 +203,29 @@ def _choose_initial_point(self) -> np.ndarray:
if self._warm_start and self._fit_result is not None:
self._initial_point = self._fit_result[0]
elif self._initial_point is None:
self._initial_point = np.random.rand(self._neural_network.num_weights)
self._initial_point = algorithm_globals.random.random(self._neural_network.num_weights)
return self._initial_point

def _get_objective(
self,
function: ObjectiveFunction,
) -> Callable:
"""
Wraps the given `ObjectiveFunction` to add callback calls, if `callback` is not None, along
with evaluating the objective value. Returned objective function is passed to
`Optimizer.optimize()`.
Args:
function: The objective function whose objective is to be evaluated.
Returns:
Objective function to evaluate objective value and optionally invoke callback calls.
"""
if self._callback is None:
return function.objective

def objective(objective_weights):
objective_value = function.objective(objective_weights)
self._callback(objective_weights, objective_value)
return objective_value

return objective
30 changes: 15 additions & 15 deletions qiskit_machine_learning/circuit/library/raw_feature_vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

"""The raw feature vector circuit."""

from typing import Optional
from typing import Optional, List
import numpy as np
from qiskit.exceptions import QiskitError
from qiskit.circuit import QuantumRegister, QuantumCircuit, ParameterVector, Instruction
Expand All @@ -27,7 +27,11 @@ class RawFeatureVector(BlueprintCircuit):
placeholder instruction that can only be synthesized/defined when all parameters are bound.
In ML, this circuit can be used to load the training data into qubit amplitudes. It does not
apply an kernel transformation. (Therefore, it is a "raw" feature vector.)
apply an kernel transformation (therefore, it is a "raw" feature vector).
Since initialization is implemented via a ``QuantumCircuit.initialize()`` call, this circuit
can't be used with gradient based optimizers, one can see a warning that gradients can't be
computed.
Examples:
Expand Down Expand Up @@ -65,15 +69,14 @@ class RawFeatureVector(BlueprintCircuit):
def __init__(self, feature_dimension: Optional[int]) -> None:
"""
Args:
feature_dimension: The feature dimension and number of qubits.
feature_dimension: The feature dimension from which the number of
qubits is inferred as ``n_qubits = log2(feature_dim)``
"""
super().__init__()

self._num_qubits = None
self._ordered_parameters = ParameterVector("x")

if feature_dimension:
if feature_dimension is not None:
self.feature_dimension = feature_dimension

def _build(self):
Expand Down Expand Up @@ -103,7 +106,7 @@ def num_qubits(self) -> int:
Returns:
The number of qubits.
"""
return self._num_qubits if self._num_qubits is not None else 0
return super().num_qubits

@num_qubits.setter
def num_qubits(self, num_qubits: int) -> None:
Expand All @@ -112,11 +115,12 @@ def num_qubits(self, num_qubits: int) -> None:
Args:
The new number of qubits.
"""
if self._num_qubits != num_qubits:
if self.num_qubits != num_qubits:
# invalidate the circuit
self._invalidate()
self._num_qubits = num_qubits
self.add_register(QuantumRegister(self.num_qubits, "q"))
self.qregs: List[QuantumRegister] = []
if num_qubits is not None and num_qubits > 0:
self.qregs = [QuantumRegister(num_qubits, name="q")]

@property
def feature_dimension(self) -> int:
Expand All @@ -141,14 +145,10 @@ def feature_dimension(self, feature_dimension: int) -> None:
if int(num_qubits) != num_qubits:
raise ValueError("feature_dimension must be a power of 2!")

if self._num_qubits is None or num_qubits != self._num_qubits:
if num_qubits != self.num_qubits:
self._invalidate()
self.num_qubits = int(num_qubits)

def _invalidate(self):
super()._invalidate()
self._num_qubits = None


class ParameterizedInitialize(Instruction):
"""A normalized parameterized initialize instruction."""
Expand Down
Loading

0 comments on commit ec3cbcc

Please sign in to comment.