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

Batch run #2069

Merged
merged 48 commits into from
Mar 8, 2022
Merged
Show file tree
Hide file tree
Changes from 39 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
0070315
batching ability for non-trainable inputs only following issue #2037
jackaraz Dec 25, 2021
a363dad
adaptation for batch execution
jackaraz Dec 25, 2021
c8152f7
improvements according to PR rules
jackaraz Dec 27, 2021
1dc9c77
minor update according to PR errors
jackaraz Dec 27, 2021
a6caaea
modify according to codefactor-io
jackaraz Dec 27, 2021
9331ec7
Merge branch 'master' into batch_run
antalszava Jan 7, 2022
7cac18b
Merge branch 'master' into batch_run
antalszava Jan 10, 2022
4f8af0b
reformatted code style
jackaraz Jan 26, 2022
5368af0
Merge branch 'master' into batch_run
antalszava Jan 26, 2022
694dece
adjust line lenght for linting
jackaraz Jan 27, 2022
123d039
Merge branch 'batch_run' of https://github.com/jackaraz/pennylane int…
jackaraz Jan 27, 2022
152cbde
Merge branch 'master' into batch_run
jackaraz Jan 27, 2022
57d8857
update linting
jackaraz Jan 27, 2022
14b00b4
Merge branch 'master' into batch_run
jackaraz Jan 27, 2022
66d5192
disable linting for too many arguments
jackaraz Feb 22, 2022
a0d4cd3
add testing for batch input in keras
jackaraz Feb 22, 2022
d233e19
Merge branch 'master' into batch_run
jackaraz Feb 22, 2022
b7d6152
Merge branch 'master' into batch_run
josh146 Feb 23, 2022
a3c1a31
format test_keras.py
jackaraz Feb 23, 2022
0498767
Merge branch 'batch_run' of https://github.com/jackaraz/pennylane int…
jackaraz Feb 23, 2022
afa989e
add tests for remaining functions
jackaraz Feb 23, 2022
cf0a13a
Merge branch 'master' into batch_run
jackaraz Feb 23, 2022
a58f1ae
adapt the defaults
jackaraz Feb 23, 2022
f863afc
update docstring according to @josh146 's suggestions
jackaraz Feb 24, 2022
ee30bdd
remove keras sterilazation
jackaraz Feb 24, 2022
e61d41a
Merge branch 'master' into batch_run
jackaraz Feb 25, 2022
2168ab8
Merge branch 'master' into batch_run
jackaraz Feb 28, 2022
489cd03
Merge branch 'master' into batch_run
jackaraz Feb 28, 2022
633b95e
Merge branch 'master' into batch_run
jackaraz Mar 2, 2022
cf9a53f
Merge branch 'master' into batch_run
josh146 Mar 3, 2022
9b63521
add batch_input to the docstring
jackaraz Mar 3, 2022
775fd86
docstring update for readability: pennylane/transforms/batch_input.py
jackaraz Mar 3, 2022
f239416
minor fix in documentation
jackaraz Mar 3, 2022
49ad4f8
change assertion error to valueerror
jackaraz Mar 3, 2022
f2de336
test valueerror
jackaraz Mar 3, 2022
a9d7e63
modify the definition of argnum
jackaraz Mar 3, 2022
8fd6f6a
change argnum -> batch_idx
jackaraz Mar 3, 2022
79d2d93
update changelog-dev.md
jackaraz Mar 3, 2022
6baa0a8
Merge branch 'batch_run' of https://github.com/jackaraz/pennylane int…
jackaraz Mar 3, 2022
20ceee2
Merge branch 'master' into batch_run
jackaraz Mar 4, 2022
023e60c
apply @josh146 's suggestions
jackaraz Mar 7, 2022
b7f20bc
Merge branch 'master' into batch_run
jackaraz Mar 7, 2022
71ab7c4
Merge branch 'v0.22.0-rc0' into batch_run
josh146 Mar 8, 2022
088d757
linting
josh146 Mar 8, 2022
d9adf69
tests
josh146 Mar 8, 2022
84951f8
Merge branch 'v0.22.0-rc0' into batch_run
josh146 Mar 8, 2022
92dadd6
more
josh146 Mar 8, 2022
ef20484
Merge branch 'batch_run' of github.com:jackaraz/pennylane into batch_run
josh146 Mar 8, 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
5 changes: 4 additions & 1 deletion doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,9 @@
Circuit fragments that are disconnected from the terminal measurements are now removed.
[(#2254)](https://github.com/PennyLaneAI/pennylane/pull/2254)

* A method to batch the non-trainable inputs for machine learning applications has been added.
[(#2069)](https://github.com/PennyLaneAI/pennylane/pull/2069)
jackaraz marked this conversation as resolved.
Show resolved Hide resolved

<h3>Improvements</h3>

* The `qml.gradients` module has been streamlined and special-purpose functions
Expand Down Expand Up @@ -424,7 +427,7 @@ The Operator class has undergone a major refactor with the following changes:

This release contains contributions from (in alphabetical order):

Thomas Bromley, Anthony Hayes, Josh Izaac, Christina Lee,
Jack Y. Araz, Thomas Bromley, Anthony Hayes, Josh Izaac, Christina Lee,
Maria Fernanda Morris, Romain Moyard, Zeyue Niu, Maria Schuld, Jay Soni,
Antal Száva, David Wierichs

1 change: 1 addition & 0 deletions pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
adjoint,
adjoint_metric_tensor,
batch_params,
batch_input,
batch_transform,
draw,
draw_mpl,
Expand Down
66 changes: 58 additions & 8 deletions pennylane/qnn/keras.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# Copyright 2018-2021 Xanadu Quantum Technologies Inc.

#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

#
# http://www.apache.org/licenses/LICENSE-2.0

#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
Expand All @@ -15,7 +15,9 @@
API."""
import inspect
from collections.abc import Iterable
from typing import Optional
from typing import Optional, Union, Sequence, Text

from pennylane.transforms.batch_input import batch_input

try:
import tensorflow as tf
Expand Down Expand Up @@ -52,6 +54,10 @@ class KerasLayer(Layer):
arguments of the `add_weight()
<https://www.tensorflow.org/api_docs/python/tf/keras/layers/Layer#add_weight>`__
method and values being the corresponding specification.
batch_idx (Union[Sequence[int], int]): Argument location of the non-trainable inputs for
the circuit. This allows batch execution by creating executable circuits for each
input example with the same trainable weights. Default ``None``.
See :class:`~.pennylane.transforms.batch_input` for more details.
jackaraz marked this conversation as resolved.
Show resolved Hide resolved
**kwargs: additional keyword arguments passed to the Layer_ base class

**Example**
Expand Down Expand Up @@ -196,8 +202,15 @@ def qnode(inputs, weights):
"""

def __init__(
jackaraz marked this conversation as resolved.
Show resolved Hide resolved
self, qnode, weight_shapes: dict, output_dim, weight_specs: Optional[dict] = None, **kwargs
self,
qnode,
weight_shapes: dict,
output_dim,
weight_specs: Optional[dict] = None,
batch_idx: Union[Sequence[int], int] = None,
**kwargs,
):
# pylint: disable=too-many-arguments
if not CORRECT_TF_VERSION:
raise ImportError(
"KerasLayer requires TensorFlow version 2 or above. The latest "
Expand All @@ -212,7 +225,12 @@ def __init__(
}

self._signature_validation(qnode, weight_shapes)
self.qnode = qnode

self.argnum = batch_idx
if batch_idx is None:
self.qnode = qnode
else:
self.qnode = batch_input(qnode, argnum=batch_idx)

dtype = tf.float32 if tf.keras.backend.floatx() == tf.float32 else tf.float64

Expand Down Expand Up @@ -281,7 +299,7 @@ def call(self, inputs):
Returns:
tensor: output data
"""
if len(tf.shape(inputs)) > 1:
if len(tf.shape(inputs)) > 1 and self.argnum is None:
# If the input size is not 1-dimensional, unstack the input along its first dimension,
# recursively call the forward pass on each of the yielded tensors, and then stack the
# outputs back into the correct shape
Expand All @@ -301,7 +319,10 @@ def _evaluate_qnode(self, x):
Returns:
tensor: output datapoint
"""
kwargs = {**{self.input_arg: x}, **{k: 1.0 * w for k, w in self.qnode_weights.items()}}
kwargs = {
**{self.input_arg: x},
**{k: 1.0 * w for k, w in self.qnode_weights.items()},
}
return self.qnode(**kwargs)

def compute_output_shape(self, input_shape):
Expand Down Expand Up @@ -330,3 +351,32 @@ def input_arg(self):
`Layer <https://www.tensorflow.org/api_docs/python/tf/keras/layers/Layer>`__. Set to
``"inputs"``."""
return self._input_arg

@staticmethod
def set_input_argument(input_name: Text = "inputs") -> None:
"""
Set the name of the input argument.

Args:
input_name (str): Name of the input argument
"""
KerasLayer._input_arg = input_name

def get_config(self) -> dict:
"""
Get serialized layer configuration

Returns:
dict: layer configuration
"""
config = super().get_config()

config.update(
{
"output_dim": self.output_dim,
"weight_specs": self.weight_specs,
"weight_shapes": self.weight_shapes,
"argnum": self.argnum,
}
)
return config
2 changes: 2 additions & 0 deletions pennylane/transforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@

~transforms.classical_jacobian
~batch_params
~batch_input
~draw
~draw_mpl
~transforms.get_unitary_matrix
Expand Down Expand Up @@ -158,6 +159,7 @@
from .op_transforms import op_transform
from .adjoint import adjoint
from .batch_params import batch_params
from .batch_input import batch_input
jackaraz marked this conversation as resolved.
Show resolved Hide resolved
from .classical_jacobian import classical_jacobian
from .compile import compile
from .control import ControlledOperation, ctrl
Expand Down
104 changes: 104 additions & 0 deletions pennylane/transforms/batch_input.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copyright 2018-2022 Xanadu Quantum Technologies Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Batch transformation for multiple (non-trainable) input examples following issue #2037
"""
from typing import Union, Sequence, Callable, Tuple

import pennylane as qml
from pennylane import numpy as np
from pennylane.transforms.batch_transform import batch_transform


@batch_transform
def batch_input(
tape: Union[qml.tape.JacobianTape, qml.QNode],
argnum: Union[Sequence[int], int] = 0,
) -> Tuple[Sequence[qml.tape.JacobianTape], Callable]:
"""
jackaraz marked this conversation as resolved.
Show resolved Hide resolved
Transform a QNode to support an initial batch dimension for gate inputs.
jackaraz marked this conversation as resolved.
Show resolved Hide resolved
In a classical ML application one needs to batch the non-trainable inputs of the network.
This function executes the same analogue for a quantum circuit:
separate circuit executions are created for each input, which are then executed
with the *same* trainable parameters.

The batch dimension is assumed to be the first rank of the
non trainable tensor object. For a rank 1 feature space, the shape needs to be ``(Nt, x)``
where ``x`` indicates the dimension of the features and ``Nt`` being the number of examples
within a batch.
Based on `arXiv:2202.10471 <https://arxiv.org/abs/2202.10471>__.
jackaraz marked this conversation as resolved.
Show resolved Hide resolved

Args:
tape (.tape.JacobianTape or .QNode): Input quantum circuit to batch
argnum (Sequence[int] or int): One or more index value on all gate parameters
indicating the location of the non-trainable batched inputs within the input
argument sequence of the circuit. By default first argument is assumed to be
the only batched input.

Returns:
Sequence[Sequence[.tape.JacobianTape], Callable]: list of tapes arranged
according to unbatched inputs and a callable function to batch the results.

**Example**

.. code-block:: python

dev = qml.device("default.qubit", wires = 2, shots=None)

@batch_input(argnum=0)
@qml.qnode(dev, diff_method="parameter-shift", interface="tf")
def circuit(inputs, weights):
qml.AngleEmbedding(inputs, wires = range(2), rotation="Y")
qml.RY(weights[0], wires=0)
qml.RY(weights[1], wires=1)
return qml.expval(qml.PauliZ(1))

>>> x = np.random.uniform(0,1,(10,2))
>>> x.requires_grad = False
>>> w = np.random.uniform(0,1,2)
>>> circuit(x, w)
<tf.Tensor: shape=(10,), dtype=float64, numpy=
array([0.17926078, 0.7480163 , 0.47816999, 0.50381628, 0.349178 ,
0.17511444, 0.03769436, 0.19180259, 0.75867188, 0.55335748])>
jackaraz marked this conversation as resolved.
Show resolved Hide resolved

.. seealso:: :func:`~.batch_params`
"""
parameters = tape.get_parameters(trainable_only=False)

argnum = tuple(argnum) if isinstance(argnum, (list, tuple)) else (int(argnum),)

non_trainable, trainable = [], []
jackaraz marked this conversation as resolved.
Show resolved Hide resolved
for idx, param in enumerate(parameters):
if idx in argnum:
non_trainable.append(param)
else:
trainable.append(param)

if len(np.unique([qml.math.shape(x)[0] for x in non_trainable])) != 1:
raise ValueError(
"Batch dimension for all gate arguments "
"specified by 'argnum' must be the same."
)

outputs = []
for inputs in zip(*non_trainable):
outputs += [list(inputs) + trainable]

# Construct new output tape with unstacked inputs
output_tapes = []
for params in outputs:
new_tape = tape.copy(copy_operations=True)
new_tape.set_parameters(params, trainable_only=False)
output_tapes.append(new_tape)

return output_tapes, lambda x: qml.math.squeeze(qml.math.stack(x))
18 changes: 18 additions & 0 deletions tests/qnn/test_keras.py
Original file line number Diff line number Diff line change
Expand Up @@ -615,3 +615,21 @@ def test_no_attribute():
"""Test that the qnn module raises an AttributeError if accessing an unavailable attribute"""
with pytest.raises(AttributeError, match="module 'pennylane.qnn' has no attribute 'random'"):
qml.qnn.random


def test_batch_input():
"""Test input batching in keras"""
jackaraz marked this conversation as resolved.
Show resolved Hide resolved
dev = qml.device("default.qubit.tf", wires=4)

@qml.qnode(dev, diff_method="parameter-shift")
def circuit(x, weights):
qml.AngleEmbedding(x, wires=range(4), rotation="Y")
qml.RY(weights[0], wires=0)
qml.RY(weights[1], wires=1)
return qml.probs(op=qml.PauliZ(1))

KerasLayer.set_input_argument("x")
layer = KerasLayer(circuit, weight_shapes={"weights": (2,)}, output_dim=(2,), batch_idx=0)
conf = layer.get_config()
layer.build((None, 2))
assert layer(np.random.uniform(0, 1, (10, 4))).shape == (10, 2)
71 changes: 71 additions & 0 deletions tests/transforms/test_batch_inputs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Copyright 2018-2021 Xanadu Quantum Technologies Inc.

# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at

# http://www.apache.org/licenses/LICENSE-2.0

# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""
Unit tests for the batch inputs transform.
"""
import functools
import pytest

import pennylane as qml
from pennylane import numpy as np


def test_simple_circuit():
"""Test that batching works for a simple circuit"""
dev = qml.device("default.qubit", wires=2)

@qml.batch_input(argnum=0)
jackaraz marked this conversation as resolved.
Show resolved Hide resolved
@qml.qnode(dev, diff_method="parameter-shift")
def circuit(inputs, weights):
qml.AngleEmbedding(inputs, wires=range(2), rotation="Y")
qml.RY(weights[0], wires=0)
qml.RY(weights[1], wires=1)
return qml.expval(qml.PauliZ(1))

batch_size = 5
inputs = np.random.uniform(0, np.pi, (batch_size, 2))
inputs.requires_grad = False
weights = np.random.uniform(-np.pi, np.pi, (2,))

res = circuit(inputs, weights)
assert res.shape == (batch_size,)
jackaraz marked this conversation as resolved.
Show resolved Hide resolved


def test_value_error():
"""Test if the batch_input raises relevant errors correctly"""

dev = qml.device("default.qubit", wires=2)

@qml.batch_input(argnum=0)
@qml.qnode(dev, diff_method="parameter-shift")
def circuit(input1, input2, weights):
qml.AngleEmbedding(input1, wires=range(2), rotation="Y")
qml.RY(input2[0], wires=0)
qml.RY(weights[0], wires=0)
qml.RY(weights[1], wires=1)
return qml.expval(qml.PauliZ(1))

batch_size = 5
input1 = np.random.uniform(0, np.pi, (batch_size, 2))
input1.requires_grad = False
input2 = np.random.uniform(0, np.pi, (4, 1))
input2.requires_grad = False
weights = np.random.uniform(-np.pi, np.pi, (2,))

try:
res = circuit(input1, input2, weights)
except ValueError as err:
pass
except Exception as err:
raise Exception(err)
jackaraz marked this conversation as resolved.
Show resolved Hide resolved