Closed rdisipio closed 4 years ago
Hi @rdisipio! Thanks for logging this bug. Have you had a look at our PennyLane TensorFlow quickstart guide?
It would also be great if you could add a minimal non-working code example that leads to the above traceback (this might be something as simple as the defined QNode and how it is called).
Hi,
thanks for the quick reply. The main script is here:
https://github.com/rdisipio/qnlp/blob/master/train.py
in turns, this create a model defined in:
https://github.com/rdisipio/qnlp/blob/master/models.py
The model is mostly a TF re-implementation of the PyTorch class in the example. As I said, I'm having a hard time to figure out where the offending line is as the traceback never reference any part of my code. I assume I'm passing an object in the wrong format (if I can suggest, an assert
statement, properly placed, may help to catch such a situation).
I think I'm getting closer to where the issue arises. These lines seem to reproduce the error:
q = qml.QNode(self.variational_quantum_circuit(q_in), self.dev).to_tf()
q = tf.convert_to_tensor(q, dtype=tf.float32)
where q_in
is a numpy array like this: [ 0.08495745 -0.20793235 0.03586568 -0.016984 ]
getting there...so the solution was to convert the QNode
object to a tensor before putting it into the list but also expanding its dimensions, and then concatenate the elements:
q_out = []
for q_in in x.numpy():
q = qml.QNode(self.variational_quantum_circuit(q_in), self.dev).to_tf()
q = tf.convert_to_tensor(q_in, dtype=tf.float32)
q = tf.expand_dims(q, 0) #(1,4)
q_out.append(q)
q_out = tf.concat(q_out, axis=0)
q_out = layers.Dense(self.output_dim)(q_out)
But now I get another error:
ValueError: No gradients provided for any variable: ['dressed_quantum_circuit/kernel:0'].
In the examples, a tf.GradientTape()
context is created to define the loss and gradient. Is there a known way to make it work with the Keras fit
API? Or do I have to define a training loop manually? That would be a significant feature to let people just replace a classical layer with a quantum one without having to change large parts of the code.
As I said, I'm having a hard time to figure out where the offending line is as the traceback never reference any part of my code. I assume I'm passing an object in the wrong format (if I can suggest, an assert statement, properly placed, may help to catch such a situation).
This is something we'll definitely look into! Unfortunately, TensorFlow makes this quite hard to catch, as their custom gradient decorator wraps and replaces the QNode, leading to the very tensorflow heavy tracebacks.
In our current development version of PennyLane (v0.8-dev
) we have refactored the QNodes in such a way that it may solve the issue you are having --- could I get you to try the development branch?
It can be installed via git:
pip install git+https://github.com/XanaduAI/pennylane.git#egg=pennylane
Once installed, QNode
is now a constructor function, that now has the same signature as the decorator:
q = qml.QNode(self.variational_quantum_circuit(q_in), self.dev, interface="tf")
Hi @rdisipio - I've had a go at making a similar Keras quantum layer before and I think you're on the right track. It took me a bit of playing to get things to work, some things I can quickly spot from your code are:
dynamic=True
to the base class __init__
https://github.com/rdisipio/qnlp/blob/39a2a9e5f44441ed9de76642328b893a16ff3a3a/models.py#L67tf.float32
and tf.float64
and eventually added tf.keras.backend.set_floatx('float64')
to any scripts using the layer (although I guess this isn't ideal)Hi @trbromley ,
thanks for the suggestions. I'm passing the dynamic
flag before kwargs
. Also, forcing tensor to be tf.float64
did not change much. Yeah, it seems to require lots of fiddling to make it work, but it's worth doing so IMO as some pre-trained models are available only for TF.
Hi @josh146 ,
thanks for taking care of this. I adapted the line you pointed out to comply with the new API. But now I get:
File "/Users/Riccardo/development/qnlp/models.py", line 135, in call
q_out = self.run_quantum_circuit(x)
File "/Users/Riccardo/development/qnlp/models.py", line 114, in run_quantum_circuit
q = qml.QNode(self.variational_quantum_circuit(q_in), self.dev, interface="tf") #0.8-dev
File "/Users/Riccardo/development/qc/lib/python3.6/site-packages/pennylane/qnodes/decorator.py", line 106, in QNode
node = PARAMETER_SHIFT_QNODES[model](func, device, mutable=mutable, properties=properties)
File "/Users/Riccardo/development/qc/lib/python3.6/site-packages/pennylane/qnodes/jacobian.py", line 32, in __init__
super().__init__(func, device, mutable=mutable, properties=properties)
File "/Users/Riccardo/development/qc/lib/python3.6/site-packages/pennylane/qnodes/base.py", line 193, in __init__
_get_signature(self.func)
File "/Users/Riccardo/development/qc/lib/python3.6/site-packages/pennylane/qnodes/base.py", line 70, in _get_signature
sig = inspect.signature(func)
File "/Users/Riccardo/.pyenv/versions/3.6.9/lib/python3.6/inspect.py", line 3065, in signature
return Signature.from_callable(obj, follow_wrapped=follow_wrapped)
File "/Users/Riccardo/.pyenv/versions/3.6.9/lib/python3.6/inspect.py", line 2815, in from_callable
follow_wrapper_chains=follow_wrapped)
File "/Users/Riccardo/.pyenv/versions/3.6.9/lib/python3.6/inspect.py", line 2193, in _signature_from_callable
raise TypeError('{!r} is not a callable object'.format(obj))
TypeError: [<pennylane.ops.qubit.PauliZ object at 0x148a4d2e8>, <pennylane.ops.qubit.PauliZ object at 0x141f8e9e8>, <pennylane.ops.qubit.PauliZ object at 0x149405c18>, <pennylane.ops.qubit.PauliZ object at 0x149405630>] is not a callable object```
Ah, this is occurring because the first argument of QNode
must be the quantum circuit function itself, un-evaluated:
q = qml.QNode(self.variational_quantum_circuit, self.dev, interface="tf")
I also have a feeling this will fail as variational_quantum_circuit
is a method, not a function, and the QNode was designed to only be used with functions :thinking:
Uhm. But then, how do I pass arguments to the function?
File "/Users/Riccardo/development/qnlp/models.py", line 122, in run_quantum_circuit
q = qml.QNode(self._variational_quantum_circuit, self.dev, interface="tf") #0.8-dev
File "/Users/Riccardo/development/qc/lib/python3.6/site-packages/pennylane/qnodes/decorator.py", line 106, in QNode
node = PARAMETER_SHIFT_QNODES[model](func, device, mutable=mutable, properties=properties)
File "/Users/Riccardo/development/qc/lib/python3.6/site-packages/pennylane/qnodes/jacobian.py", line 32, in __init__
super().__init__(func, device, mutable=mutable, properties=properties)
File "/Users/Riccardo/development/qc/lib/python3.6/site-packages/pennylane/qnodes/base.py", line 193, in __init__
_get_signature(self.func)
File "/Users/Riccardo/development/qc/lib/python3.6/site-packages/pennylane/qnodes/base.py", line 73, in _get_signature
func.var_pos = False
AttributeError: 'method' object has no attribute 'var_pos'
Not sure if this helps @rdisipio, I had the following in my __init__
:
def circuit(inputs, parameters):
qml.templates.embeddings.AngleEmbedding(inputs, wires=list(range(self.units)))
qml.templates.layers.StronglyEntanglingLayers(parameters, wires=list(range(self.units)))
return [qml.expval(qml.PauliZ(i)) for i in range(self.units)]
self.dev = qml.device(device, wires=units)
self.layer = qml.QNode(circuit, self.dev, interface="tf")
You can then treat self.layer
as the function you use later on in the call
method.
Hi,
thanks for the suggestion. I'm still quite confused. In a derived class from Keras.layers.Layer, the parameters are defined in the build() function, and the inputs in the call() function. So how do I pass these parameters to circuit(inputs, parameters)
in __init__
?
Also, the error I'm still getting
ValueError: No gradients provided for any variable: ['dressed_quantum_circuit/kernel:0'].
seems to be related to the model's kernel, which I define in build()
as this:
def build(self, input_shape):
print("Building dressed_quantum_circuit")
#print(input_shape) #(None, 512)
self.kernel = self.add_weight(name='kernel',
shape=(self.q_depth, self.n_qubits),
initializer='uniform',
trainable=True)
super(dressed_quantum_circuit, self).build(input_shape) # Be sure to call this at the end
So it's probably not related to pennylane
, but still, do you have any idea why these parameters do not seem to be picked up by the gradient?
I made some progress. I can get rid of the error concerning missing gradient for kernel variables by defining custom weights rather than calling a Dense layer from call()
. However, I still get some troubles apparently due to the dynamic=True
flag. My understanding is that this is needed to manipulate information that does not result in a static graph, such as in RNNs (not sure why that's the case). Is this correct for the QNode as well?
If I set dynamic=False
, I do not know how to loop over the inputs to apply the quantum operations. The tensor object has a .numpy()
function only if dynamic=True
.
for idx, element in enumerate(w):
qml.RY(element, wires=idx)
This only works if w
is a numpy array, or I get the error:
OperatorNotAllowedInGraphError: iterating over `tf.Tensor` is not allowed in Graph execution. Use Eager execution or decorate this function with @tf.function.
On the other hand, if I set dynamic=True
, the quantum layer does not complain, but I get this other error:
E tensorflow/core/common_runtime/executor.cc:642] Executor failed to create kernel. Invalid argument: Value for attr 'T' of string is not in the list of allowed values: float, double, int32, uint8, int16, int8, complex64, int64, qint8, quint8, qint32, bfloat16, uint16, complex128, half, uint32, uint64, variant
; NodeDef: {{node Func/_303}}; Op<name=AddN; signature=inputs:N*T -> sum:T; attr=N:int,min=1; attr=T:type,allowed=[DT_FLOAT, DT_DOUBLE, DT_INT32, DT_UINT8, DT_INT16, ..., DT_COMPLEX128, DT_HALF, DT_UINT32, DT_UINT64, DT_VARIANT]; is_commutative=true; is_aggregate=true>
[[Func/_303]]
2020-01-15 15:48:31.783444: W tensorflow/core/common_runtime/base_collective_executor.cc:216] BaseCollectiveExecutor::StartAbort Invalid argument: Value for attr 'T' of string is not in the list of allowed values: float, double, int32, uint8, int16, int8, complex64, int64, qint8, quint8, qint32, bfloat16, uint16, complex128, half, uint32, uint64, variant
; NodeDef: {{node Func/_303}}; Op<name=AddN; signature=inputs:N*T -> sum:T; attr=N:int,min=1; attr=T:type,allowed=[DT_FLOAT, DT_DOUBLE, DT_INT32, DT_UINT8, DT_INT16, ..., DT_COMPLEX128, DT_HALF, DT_UINT32, DT_UINT64, DT_VARIANT]; is_commutative=true; is_aggregate=true>
[[Func/_303]]
[[gradients/StatefulPartitionedCall_grad/PartitionedCall/gradients/StatefulPartitionedCall_grad/SymbolicGradient]]
Hi @rdisipio, let me share with you an example that I got to work:
class QuantumLayers(Layer):
def __init__(
self,
units,
device="default.qubit",
n_layers=1,
rotations_initializer="random_uniform",
rotations_regularizer=None,
rotations_constraint=None,
**kwargs,
):
if "input_shape" not in kwargs and "input_dim" in kwargs:
kwargs["input_shape"] = (kwargs.pop("input_dim"),)
if "dynamic" in kwargs:
del kwargs["dynamic"]
super(QuantumLayers, self).__init__(dynamic=True, **kwargs)
self.units = units
self.device = device
self.n_layers = n_layers
self.rotations_initializer = initializers.get(rotations_initializer)
self.rotations_regularizer = regularizers.get(rotations_regularizer)
self.rotations_constraint = constraints.get(rotations_constraint)
self.input_spec = InputSpec(min_ndim=2, axes={-1: units})
self.supports_masking = False
def circuit(inputs, parameters):
qml.templates.embeddings.AngleEmbedding(inputs, wires=list(range(self.units)))
qml.templates.layers.StronglyEntanglingLayers(parameters, wires=list(range(self.units)))
return [qml.expval(qml.PauliZ(i)) for i in range(self.units)]
self.dev = qml.device(device, wires=units)
self.layer = qml.QNode(circuit, self.dev, interface="tf")
def apply_layer(self, *args):
return tf.keras.backend.cast_to_floatx(self.layer(*args))
def build(self, input_shape):
# assert len(input_shape) == 2
input_dim = input_shape[-1]
assert input_dim == self.units
self.rotations = self.add_weight(
shape=(self.n_layers, input_dim, 3),
initializer=self.rotations_initializer,
name="rotations",
regularizer=self.rotations_regularizer,
constraint=self.rotations_constraint,
)
self.built = True
def call(self, inputs):
return tf.stack([self.apply_layer(i, self.rotations) for i in inputs])
def compute_output_shape(self, input_shape):
return input_shape
def get_config(self):
config = {
"units": self.units,
"device": self.device,
"n_layers": self.n_layers,
"rotations_initializer": initializers.serialize(self.rotations_initializer),
"rotations_regularizer": regularizers.serialize(self.rotations_regularizer),
"rotations_constraint": constraints.serialize(self.rotations_constraint),
}
base_config = super(QuantumLayers, self).get_config()
return dict(list(base_config.items()) + list(config.items()))
Note that this layer applies the quantum circuit alone - there is no dressing with classical layers so you have to be careful about the input dimension.
Hi @trbromley ,
thanks for sharing the code. Finally, I can make my code work by following your example, with the main difference that I have to calculate the documents' embeddings outside the network as pass them as input to the network instead of strings. I assume that's a quirk of TensorFlow and has not much to do with PennyLane (to be checked later).
Also, I wonder if the quantum layer you are using is of the same kind of the one that appears in the transfer learning paper (arXiv:1912.08278 references arXiv:1804.00633).
Closing this for now as the code is now working :)
@rdisipio, if you can write your ML example as a single python script, feel free to make a PR here to add it to the list of demos on https://pennylane.ai/qml!
Issue description
I'm trying to create a TensorFlow-based equivalent of
tutorial_quantum_transfer_learning.py
but I get an error related to the conversion of Pennylane objects to Tensors.Expected behavior: Pass values correctly?
Actual behavior: Returns the following error: ValueError: Attempt to convert a value (<error computing repr()>) with an unsupported type (<class 'pennylane.interfaces.tf.TFQNode..qnode_str'>) to a Tensor.
Reproduces how often: (What percentage of the time does it reproduce?) I'm not really sure right now what is causing the problem. The stack trace does not point to any line of my code that can create the offence. See below.
System information: (include your operating system, Python version, Sphinx version, etc.) MacOsX 10.14 TensorFlow 2.0 PennyLane 0.7.0
Source code and tracebacks
Additional information
It would be nice if you could provide some more examples of interplay between "classical" and quantum ML using TensorFlow instead of (or beside) PyTorch.