CV QNN Local Cost function

Is there a way to reshape the qnode-returned qml.probs() of a CV Neural Network for a local cost function
in TensorFlow to a 1D array instead of the 2D array? For instance if #qumodes = 4 and cutoff dimension=2
the qnode returns a 4x2 array instead of a 1x8 array with:
return [qml.probs(wires=i) for i in range(wires)]
(wires=4).
The quantum layer was added to classical layers using
tf.keras.Sequential().

Global vs Local cost functions for a qubit network are discussed here:

Hi @art, it’s good to see you here in the forum!

I’m not sure whether this is what you’re looking for but you could try using qml.math.hstack in order to stack your results horizontally.

As an example, this is a valid qnode

@qml.qnode(dev, diff_method="adjoint")
def circuit(parameters):
    qml.StronglyEntanglingLayers(weights=parameters, wires=range(wires))
    return qml.math.hstack([qml.expval(qml.PauliZ(i)) for i in range(wires)])

Does this help? If not, please post a minimal version of your code that reproduces your issue.

Didn’t work, I got the error:
ValueError: Shapes (256, 1, 8) and (256, 1, 4, 2) are incompatible

originating from:
model.fit(X_train, y_train, batch_size = 256, epochs = 100, verbose = 1, validation_data = (X_test, y_test)

One of the traceback lines is:
…python3.8/site-packages/tensorflow/python/keras/losses.py", line 1537, in categorical_crossentropy
return K.categorical_crossentropy(y_true, y_pred, from_logits=from_logits)

8 corresponds to the size of the one-hot encoded target vector
zero padded if there are less than 8 classes.

Following code is an example:

where the qnode function return code line is:

return [qml.probs(wires=i) for i in range(wires)]

On a related post PyTorch qml.probs() used to return a 1D array but it was updated to match the TensorFlow version:

I require the TensorFlow version to be 1D.

Hi @art , can you please post your full traceback error?

Following is the full traceback:

Traceback (most recent call last):
File “local_cost_function.py”, line 524, in
history = model.fit(X_train, y_train, batch_size = 256, epochs = 100, verbose = 1, validation_data = (X_test, y_test),
File “…/site-packages/tensorflow/python/keras/engine/training.py”, line 1100, in fit
tmp_logs = self.train_function(iterator)
File “…/site-packages/tensorflow/python/keras/engine/training.py”, line 805, in train_function
return step_function(self, iterator)
File “…/site-packages/tensorflow/python/keras/engine/training.py”, line 795, in step_function
outputs = model.distribute_strategy.run(run_step, args=(data,))
File “…/site-packages/tensorflow/python/distribute/distribute_lib.py”, line 1259, in run
return self._extended.call_for_each_replica(fn, args=args, kwargs=kwargs)
File “…/site-packages/tensorflow/python/distribute/distribute_lib.py”, line 2730, in call_for_each_replica
return self._call_for_each_replica(fn, args, kwargs)
File “…/site-packages/tensorflow/python/distribute/distribute_lib.py”, line 3417, in _call_for_each_replica
return fn(*args, **kwargs)
File “…/site-packages/tensorflow/python/autograph/impl/api.py”, line 572, in wrapper
return func(*args, **kwargs)
File “…/site-packages/tensorflow/python/keras/engine/training.py”, line 788, in run_step
outputs = model.train_step(data)
File “…/site-packages/tensorflow/python/keras/engine/training.py”, line 755, in train_step
loss = self.compiled_loss(
File “…/site-packages/tensorflow/python/keras/engine/compile_utils.py”, line 203, in call
loss_value = loss_obj(y_t, y_p, sample_weight=sw)
File “…/site-packages/tensorflow/python/keras/losses.py”, line 152, in call
losses = call_fn(y_true, y_pred)
File “…/site-packages/tensorflow/python/keras/losses.py”, line 256, in call
return ag_fn(y_true, y_pred, **self._fn_kwargs)
File “…/site-packages/tensorflow/python/util/dispatch.py”, line 201, in wrapper
return target(*args, **kwargs)
File “…/site-packages/tensorflow/python/keras/losses.py”, line 1537, in categorical_crossentropy
return K.categorical_crossentropy(y_true, y_pred, from_logits=from_logits)
File “…/site-packages/tensorflow/python/util/dispatch.py”, line 201, in wrapper
return target(*args, **kwargs)
File “…/site-packages/tensorflow/python/keras/backend.py”, line 4833, in categorical_crossentropy
target.shape.assert_is_compatible_with(output.shape)
File “…/site-packages/tensorflow/python/framework/tensor_shape.py”, line 1134, in assert_is_compatible_with
raise ValueError(“Shapes %s and %s are incompatible” % (self, other))
ValueError: Shapes (256, 1, 8) and (256, 1, 4, 2) are incompatible

Hi @art,

I could run sophchoe’s example with no problem. I used pennylane==0.29 and tensorflow==2.12 . You will also need to pip install pennylane-sf.

In my case I needed to change keras.backend.set_floatx('float32') to keras.backend.set_floatx('float64') but you may not need to change this depending on your system.

Since you’re changing the return of your qnode you’ll need to change the output dimension of your quantum layer and you may need to change other parameters in the model to match that too. Is there a reason why you need to change the output of that qnode?

I hope this helps.

Is there a reason why you need to change the output of that qnode?

To implement local instead of global cost function:

def global_cost_simple(rotations):
    for i in range(wires):
          qml.RX(rotations[0][i], wires=i)
          qml.RY(rotations[1][i], wires=i)
    return qml.probs(wires=range(wires))

def local_cost_simple(rotations):
    for i in range(wires):
          qml.RX(rotations[0][i], wires=i)
          qml.RY(rotations[1][i], wires=i)
    return [qml.probs(wires=i) for i in range(wires)]

global_circuit = qml.QNode(global_cost_simple, dev, interface="autograd")

local_circuit = qml.QNode(local_cost_simple, dev, interface="autograd")

def cost_local(rotations):
    return 1 - np.sum([i for (i, _) in local_circuit(rotations)])/wires

def cost_global(rotations):
    return 1 - global_circuit(rotations)[0]

Hi @art,

Unfortunately with this change the dataset doesn’t match the problem. Your measurement in this case won’t match ‘y’ in your dataset. I don’t know of any way of flattening your output to avoid this problem so maybe you could try with a different measurement. I’ll ask someone else next week to see if we can help you here though.

Hey @art!

Apologies for the lack of follow-up. Let’s revisit this!

I’m not very comfortable grappling with the code you linked in your previous response, so let’s work with a simpler code example and see if you can apply it to what you’re trying to do. If you want to propose something different than my example below, feel free :slight_smile:

import pennylane as qml
import pennylane.numpy as np

dev = qml.device("default.qubit")
num_wires = 3

@qml.qnode(dev)
def circuit(a, b, c):
    qml.RX(a, wires=0)
    qml.RX(b, wires=1)
    qml.RX(c, wires=2)

    return [qml.expval(qml.PauliZ(i)) for i in range(num_wires)]

batch_size = 5
a = np.random.uniform(0, np.pi, size=(batch_size,))
b = np.random.uniform(0, np.pi, size=(batch_size,))
c = np.random.uniform(0, np.pi, size=(batch_size,))

If I understand correctly, you just want to flatten the output of your circuit when broadcasting is being employed and use that in a cost function. I don’t think that that’s easily done directly from a QNode return, if it’s doable at all. You’ll need to do the post-processing within the cost function like this:

def cost(a, b, c):
    return np.mean(np.array(circuit(a, b, c)).flatten())

opt = qml.GradientDescentOptimizer(0.1)
opt.step(cost, a, b, c)
[tensor([0.45550732, 2.22610709, 2.68796841, 1.79458191, 2.58409425], requires_grad=True),
 tensor([0.77061153, 1.29453608, 2.34985698, 2.50088811, 1.11313404], requires_grad=True),
 tensor([0.44164193, 0.90375335, 1.89557516, 2.31902147, 3.12914027], requires_grad=True)]

If I’m not understanding what you want to accomplish, let me know :slight_smile:. Hope we can figure this out for you!