Local cost function - hybrid neural networks

Hello,

I have a few questions regarding hybrid neural networks:

  1. Local Cost Function: I am unable to make my cost function local rather than global. For instance, I have some input data with 4 features which I embed using Angle encoding. Next, I have 4-qubit variational circuit and at the end I have a classical neuron layer (output layer) to read the output. Now, when I measure all the qubits (Global cost) and pass it on to the classical output layer it works fine, however, if I measure a single qubit it gives me shapes mismatch error. I am not sure why is that the case since I have Dense Layers. Any help would be appreciated.

  2. Increasing qubit number and backend quantum device:
    If I want to increase more qubits (>8), I can not use "default.qubit" as a quantum device, right?. Since "qiskit.aer" allows to use maximum of 29 qubits, I though to use it instead. However, when I tried it for 8 qubits, I get the following error while I try and train the model.

> **InvalidArgumentError:** cannot compute Mul as input #1(zero-based) was expected to be a float tensor but is a double tensor [Op:Mul]
PS: the same model runs for "default.qubit" device perfectly well.

  1. Learning Percentage: Since it is a hybrid network, is there any way that I can get a ratio of model’s learning from classical and quantum layer(s). Like how much quantum part in particular has contributed to the learning and eventually accuracy.

Thanks in advance,

Best,
Muhammad Kashif

Hi @Muhammad_Kashif, could you please share the code you’re using? It will make it easier for us to help you.

You can use as many qubits as you want with default.qubit, the limitation is really on how powerful the machine you’re running on is. You should be able to get to about 20 qubits with a standard laptop.

As for the learning percentage I guess you could fix the parameters in the classical layers and see how much improvement you get from optimizing only the quantum layers. I’m not sure it’s exactly what you want but it might give you an idea of how much the quantum optimization is contributing to the result.

Hi @CatalinaAlbornoz,

Thanks for the response and clarifying my doubts…
My Qnode consists of the following circuit:

n_qubits = 4
dev = qml.device("default.qubit",  wires=n_qubits)
   
@qml.qnode(dev) 
 def qnode(inputs, weights):
       qml.templates.AngleEmbedding(inputs, wires=range(n_qubits), rotation = 'Y')
       qml.templates.BasicEntanglerLayers(weights, wires=range(n_qubits), rotation = qml.RY)
       return qml.expval(qml.PauliZ(wires=0)) 

  n_layers = 1
  weight_shapes = {"weights": (n_layers, n_qubits)}

the final model (to train) while adding above ciorcuit as KerasLayer is as follows:

qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits)
clayer_1 = tf.keras.layers.Dense(10, activation="softmax")

inputs = tf.keras.Input(shape=(4,))
x = qlayer(inputs)
outputs = clayer_1(x)

model_hybrid = tf.keras.Model(inputs=inputs, outputs=outputs)

The above model does not train and gives shapes mismatch error whereas if we measure all the qubits as below, it works fine.

return [qml.expval(qml.PauliZ(wires=[i])) for i in range(n_qubits)]

Any pointers why is that the case?

Thanks

Hi @Muhammad_Kashif, I’ve just run your code and I don’t get any errors. What version of PennyLane, Python, and TensorFlow are you using?

Hi @CatalinaAlbornoz, I am using the following versions:

Pennylane: 0.21.0
Python: 3.8.8
TensorFlow: 2.5.0

Thanks

Hi @Muhammad_Kashif, if you add this line to your code do you still get the error?

tf.keras.backend.set_floatx('float64')

Hi @CatalinaAlbornoz,

I did add the above line you suggested but I still get the following error:

ValueError: Input 0 of layer dense_4 is incompatible with the layer: : expected min_ndim=2, found ndim=1. Full shape received: (16,)

Overview of my model resulting in the above error:
input shape = 12 which is then passed on to 12-qubit parametrized circuit. Only the first qubit (q0) is measured and then passed on to 10-neuron classical layer.

Looking at the error above, I thought may be measuring two qubits would resolve the issue. But in the same setting when I measure two qubits (q0 and q1), I get the following error:
ValueError: Input 0 of layer dense_5 is incompatible with the layer: expected axis -1 of input shape to have value 12 but received input with shape (16, 2)

PS: 16 is the batch size i am using.

Thanks

Thanks for the additional info @Muhammad_Kashif. I’ll keep seeing if I can find a solution.

Hi @Muhammad_Kashif,

Sorry I took so long to respond. I still haven’t found the root cause of your problem.

I’m using Python 3.8.12, PennyLane 0.21.0, and tensorflow-macos 2.8.0 and I can’t reproduce it.

I would suggest upgrading your TensorFlow version.

Also, since PennyLane version 0.23.1 has already been released you may want to upgrade that one too (although that’s not what is causing the error).

I hope this solves your issue. If it doesn’t can you share the full error traceback? We may get some hints from there.

Hi @CatalinaAlbornoz,

Thanks for the response and suggestions. I tried upgrading the pennylane version and tensorflow version, and the error persists. It forces me to measure all the qubits (in hybrid neural network) even though the last classical neuron layer is a dense layer. If I try measuring one or two qubits I get the following kind of error (with full traceback).


ValueError                                Traceback (most recent call last)
<ipython-input-48-09a2ba6edca4> in <module>
      2 
      3 start = time()
----> 4 history = model_hybrid.fit(x_train, y_train, epochs=100, batch_size=16, validation_data=(x_test, y_test), callbacks = [es,rdc])
      5 print(time()-start)
      6 

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\training.py in fit(self, x, y, batch_size, epochs, verbose, callbacks, validation_split, validation_data, shuffle, class_weight, sample_weight, initial_epoch, steps_per_epoch, validation_steps, validation_batch_size, validation_freq, max_queue_size, workers, use_multiprocessing)
   1181                 _r=1):
   1182               callbacks.on_train_batch_begin(step)
-> 1183               tmp_logs = self.train_function(iterator)
   1184               if data_handler.should_sync:
   1185                 context.async_wait()

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\training.py in train_function(iterator)
    853       def train_function(iterator):
    854         """Runs a training execution with one step."""
--> 855         return step_function(self, iterator)
    856 
    857     else:

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\training.py in step_function(model, iterator)
    843 
    844       data = next(iterator)
--> 845       outputs = model.distribute_strategy.run(run_step, args=(data,))
    846       outputs = reduce_per_replica(
    847           outputs, self.distribute_strategy, reduction='first')

~\Anaconda3\lib\site-packages\tensorflow\python\distribute\distribute_lib.py in run(***failed resolving arguments***)
   1283       fn = autograph.tf_convert(
   1284           fn, autograph_ctx.control_status_ctx(), convert_by_default=False)
-> 1285       return self._extended.call_for_each_replica(fn, args=args, kwargs=kwargs)
   1286 
   1287   def reduce(self, reduce_op, value, axis):

~\Anaconda3\lib\site-packages\tensorflow\python\distribute\distribute_lib.py in call_for_each_replica(self, fn, args, kwargs)
   2831       kwargs = {}
   2832     with self._container_strategy().scope():
-> 2833       return self._call_for_each_replica(fn, args, kwargs)
   2834 
   2835   def _call_for_each_replica(self, fn, args, kwargs):

~\Anaconda3\lib\site-packages\tensorflow\python\distribute\distribute_lib.py in _call_for_each_replica(self, fn, args, kwargs)
   3606   def _call_for_each_replica(self, fn, args, kwargs):
   3607     with ReplicaContext(self._container_strategy(), replica_id_in_sync_group=0):
-> 3608       return fn(*args, **kwargs)
   3609 
   3610   def _reduce_to(self, reduce_op, value, destinations, options):

~\Anaconda3\lib\site-packages\tensorflow\python\autograph\impl\api.py in wrapper(*args, **kwargs)
    595   def wrapper(*args, **kwargs):
    596     with ag_ctx.ControlStatusCtx(status=ag_ctx.Status.UNSPECIFIED):
--> 597       return func(*args, **kwargs)
    598 
    599   if inspect.isfunction(func) or inspect.ismethod(func):

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\training.py in run_step(data)
    836 
    837       def run_step(data):
--> 838         outputs = model.train_step(data)
    839         # Ensure counter is updated only if `train_step` succeeds.
    840         with ops.control_dependencies(_minimum_control_deps(outputs)):

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\training.py in train_step(self, data)
    793     # Run forward pass.
    794     with backprop.GradientTape() as tape:
--> 795       y_pred = self(x, training=True)
    796       loss = self.compiled_loss(
    797           y, y_pred, sample_weight, regularization_losses=self.losses)

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\base_layer.py in __call__(self, *args, **kwargs)
   1028         with autocast_variable.enable_auto_cast_variables(
   1029             self._compute_dtype_object):
-> 1030           outputs = call_fn(inputs, *args, **kwargs)
   1031 
   1032         if self._activity_regularizer:

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\functional.py in call(self, inputs, training, mask)
    418         a list of tensors if there are more than one outputs.
    419     """
--> 420     return self._run_internal_graph(
    421         inputs, training=training, mask=mask)
    422 

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\functional.py in _run_internal_graph(self, inputs, training, mask)
    554 
    555         args, kwargs = node.map_arguments(tensor_dict)
--> 556         outputs = node.layer(*args, **kwargs)
    557 
    558         # Update tensor_dict.

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\base_layer.py in __call__(self, *args, **kwargs)
   1011         training=training_mode):
   1012 
-> 1013       input_spec.assert_input_compatibility(self.input_spec, inputs, self.name)
   1014       if eager:
   1015         call_fn = self.call

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\input_spec.py in assert_input_compatibility(input_spec, inputs, layer_name)
    249           value = value.value
    250         if value is not None and shape_as_list[int(axis)] not in {value, None}:
--> 251           raise ValueError(
    252               'Input ' + str(input_index) + ' of layer ' + layer_name + ' is'
    253               ' incompatible with the layer: expected axis ' + str(axis) +

ValueError: Input 0 of layer dense_2 is incompatible with the layer: expected axis -1 of input shape to have value 8 but received input with shape (16, 1) 

Hope this helps you figure out the actual issue or if pennylane does not support to measure single qubits in multi-qubit hybrid QNN architecture (as of now)?

regards,
kashif

Hi @Muhammad_Kashif, it seems that the error arises from a line different from the code you shared. Could you please share your full code including the data you’re using?

Hi @CatalinaAlbornoz,

Please find a sample full code below causing the error as I mentioned above.

Imports
import pennylane as qml
from pennylane import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn import datasets

load dataset
x_train, x_test = datasets.load_digits().data, datasets.load_digits().target

Split train and test data
x_train, x_test, y_train, y_test = train_test_split(
x_train, x_test, test_size=0.25, random_state=42)

quantum circuit

n_qubits = 6
dev = qml.device("default.qubit", wires=n_qubits)
@qml.qnode(dev)
def qnode(inputs, weights):
    qml.templates.AmplitudeEmbedding(inputs, wires=range(n_qubits), pad_with=2,  normalize = True)
    qml.templates.BasicEntanglerLayers(weights, wires=range(n_qubits), rotation = qml.RY)

    return [qml.expval(qml.PauliZ(wires=[0]))]

n_layers = 1
weight_shapes = {"weights": (n_layers, n_qubits)}

convert quantum circuit to Keraslayer
qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits)

creating and compiling neural network model

clayer = tf.keras.layers.Dense(10, activation="softmax")
model = tf.keras.models.Sequential([qlayer, clayer])

opt = tf.keras.optimizers.SGD(learning_rate=0.2)
model.compile(opt, loss="sparse_categorical_crossentropy", metrics=["accuracy"])

Start training
history = model.fit(x_train, y_train, epochs=5, batch_size=16, validation_data=(x_test, y_test))

Error with full traceback

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-61-92f97b8c4386> in <module>
----> 1 history = model.fit(x_train, y_train, epochs=5, batch_size=16, validation_data=(x_test, y_test))

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\training.py in fit(self, x, y, batch_size, epochs, verbose, callbacks, validation_split, validation_data, shuffle, class_weight, sample_weight, initial_epoch, steps_per_epoch, validation_steps, validation_batch_size, validation_freq, max_queue_size, workers, use_multiprocessing)
   1181                 _r=1):
   1182               callbacks.on_train_batch_begin(step)
-> 1183               tmp_logs = self.train_function(iterator)
   1184               if data_handler.should_sync:
   1185                 context.async_wait()

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\training.py in train_function(iterator)
    853       def train_function(iterator):
    854         """Runs a training execution with one step."""
--> 855         return step_function(self, iterator)
    856 
    857     else:

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\training.py in step_function(model, iterator)
    843 
    844       data = next(iterator)
--> 845       outputs = model.distribute_strategy.run(run_step, args=(data,))
    846       outputs = reduce_per_replica(
    847           outputs, self.distribute_strategy, reduction='first')

~\Anaconda3\lib\site-packages\tensorflow\python\distribute\distribute_lib.py in run(***failed resolving arguments***)
   1283       fn = autograph.tf_convert(
   1284           fn, autograph_ctx.control_status_ctx(), convert_by_default=False)
-> 1285       return self._extended.call_for_each_replica(fn, args=args, kwargs=kwargs)
   1286 
   1287   def reduce(self, reduce_op, value, axis):

~\Anaconda3\lib\site-packages\tensorflow\python\distribute\distribute_lib.py in call_for_each_replica(self, fn, args, kwargs)
   2831       kwargs = {}
   2832     with self._container_strategy().scope():
-> 2833       return self._call_for_each_replica(fn, args, kwargs)
   2834 
   2835   def _call_for_each_replica(self, fn, args, kwargs):

~\Anaconda3\lib\site-packages\tensorflow\python\distribute\distribute_lib.py in _call_for_each_replica(self, fn, args, kwargs)
   3606   def _call_for_each_replica(self, fn, args, kwargs):
   3607     with ReplicaContext(self._container_strategy(), replica_id_in_sync_group=0):
-> 3608       return fn(*args, **kwargs)
   3609 
   3610   def _reduce_to(self, reduce_op, value, destinations, options):

~\Anaconda3\lib\site-packages\tensorflow\python\autograph\impl\api.py in wrapper(*args, **kwargs)
    595   def wrapper(*args, **kwargs):
    596     with ag_ctx.ControlStatusCtx(status=ag_ctx.Status.UNSPECIFIED):
--> 597       return func(*args, **kwargs)
    598 
    599   if inspect.isfunction(func) or inspect.ismethod(func):

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\training.py in run_step(data)
    836 
    837       def run_step(data):
--> 838         outputs = model.train_step(data)
    839         # Ensure counter is updated only if `train_step` succeeds.
    840         with ops.control_dependencies(_minimum_control_deps(outputs)):

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\training.py in train_step(self, data)
    793     # Run forward pass.
    794     with backprop.GradientTape() as tape:
--> 795       y_pred = self(x, training=True)
    796       loss = self.compiled_loss(
    797           y, y_pred, sample_weight, regularization_losses=self.losses)

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\base_layer.py in __call__(self, *args, **kwargs)
   1028         with autocast_variable.enable_auto_cast_variables(
   1029             self._compute_dtype_object):
-> 1030           outputs = call_fn(inputs, *args, **kwargs)
   1031 
   1032         if self._activity_regularizer:

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\sequential.py in call(self, inputs, training, mask)
    378       if not self.built:
    379         self._init_graph_network(self.inputs, self.outputs)
--> 380       return super(Sequential, self).call(inputs, training=training, mask=mask)
    381 
    382     outputs = inputs  # handle the corner case where self.layers is empty

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\functional.py in call(self, inputs, training, mask)
    418         a list of tensors if there are more than one outputs.
    419     """
--> 420     return self._run_internal_graph(
    421         inputs, training=training, mask=mask)
    422 

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\functional.py in _run_internal_graph(self, inputs, training, mask)
    554 
    555         args, kwargs = node.map_arguments(tensor_dict)
--> 556         outputs = node.layer(*args, **kwargs)
    557 
    558         # Update tensor_dict.

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\base_layer.py in __call__(self, *args, **kwargs)
   1011         training=training_mode):
   1012 
-> 1013       input_spec.assert_input_compatibility(self.input_spec, inputs, self.name)
   1014       if eager:
   1015         call_fn = self.call

~\Anaconda3\lib\site-packages\tensorflow\python\keras\engine\input_spec.py in assert_input_compatibility(input_spec, inputs, layer_name)
    249           value = value.value
    250         if value is not None and shape_as_list[int(axis)] not in {value, None}:
--> 251           raise ValueError(
    252               'Input ' + str(input_index) + ' of layer ' + layer_name + ' is'
    253               ' incompatible with the layer: expected axis ' + str(axis) +

ValueError: Input 0 of layer dense_13 is incompatible with the layer: expected axis -1 of input shape to have value 6 but received input with shape (16, 1)

Hope it makes the issue clear. Thanks for the help.

Regards,
M. Kashif
​
​

Hi @Muhammad_Kashif,

The problem is that when you define the layer

qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits)

you’re saying that the output dimension is equal to the number of qubits (6 in this case). However in the return statement for your circuit you’re returning a single value.

There are two ways how you can remove this error. On one hand you can change the return statement to be of size 6 using something such as:

return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)]

On the other hand you could keep the return statement as you have it and change the output dimension to be 1.

Please let me know if this is clear and if this solves your problem!

Hi @CatalinaAlbornoz,

The suggested solution of setting the output dimension ogf Qlayer to 1 works fine.

Thanks for the great help.

Best,
M. Kashif

1 Like