Data re-uploading impelementation in hybrid NN with keras layer

I want to implement data re-uploading technique in hybrid NN.
But I don’t know how to make the number of layers variable.

Here is my sample code.
Keras layers were used.

n_qubits = 2
layers = 2
data_dimension = 1 # output
param = {'num_epochs': 64}

dev = qml.device("lightning.qubit", wires=n_qubits)

@qml.qnode(dev, diff_method = "adjoint")
# data re-uploading 2 times manually.
def qnode(inputs, weights0, weights1):
    qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.templates.StronglyEntanglingLayers(weights0, wires=range(n_qubits))
    qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.templates.StronglyEntanglingLayers(weights1, wires=range(n_qubits))    
    return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]


weight_shapes = dict()
for i in range(layers):
    weight_shapes["weights"+str(i)]=(1, n_qubits,3)

qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits)
clayer1 = tf.keras.layers.Dense(n_qubits, activation='linear')
clayer2 = tf.keras.layers.Dense(data_dimension, activation="linear")
model = tf.keras.models.Sequential([clayer1,qlayer,clayer2])

In the code, I manually iterate data re-uploading layer 2 times without using for loop.
I want to make the number of data re-uploading layer variable by introducing for loop.
Here is just image.

for i in range(layers):
    qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.templates.StronglyEntanglingLayers(weights[i], wires=range(n_qubits))

How can I implement it?

Hi @Kuma-quant,

It should work to just use a for-loop inside the QNode (as per you’re suggestion), just defining the number of layers outside of it, as you’re also currently doing:

def qnode(inputs, weights0, weights1):
    for i in range(layers):
        qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
        qml.templates.StronglyEntanglingLayers(weights[i], wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]

Is this what you’re aiming to do or are you attempting to optimize over the number of layers as well? If you can share with me exactly what’s not working (e.g. a minimal non-working example) I could probably help you a bit better. :slight_smile:

Here is a sample code including dummy input data.
It returns error.

Pennylane : Version: 0.16.0
Tensorflow : Version: 2.4.1

import tensorflow as tf
import keras_metrics
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Activation
import pennylane as qml
from pennylane import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline

num_of_data = 64
X = np.random.normal((num_of_data,1))
Y = np.sin(X)

n_qubits = 1
layers = 2
data_dimension = 1 # output

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

@qml.qnode(dev, diff_method='adjoint')
def qnode(inputs, weights):
    for i in range(layers):
        qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
        qml.templates.StronglyEntanglingLayers(weights[i,:,:], wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]

weight_shapes = {"weights": (layers, n_qubits,3)}

qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits)
clayer1 = tf.keras.layers.Dense(n_qubits, activation='linear')
clayer2 = tf.keras.layers.Dense(data_dimension, activation="linear")
model = tf.keras.models.Sequential([clayer1,qlayer,clayer2])

opt = tf.keras.optimizers.Adam(learning_rate=0.01)
model.compile(opt, loss='mse')

hist = model.fit(X, Y, epochs=30, validation_split=0.1, verbose=1, shuffle='True', batch_size=32)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-18-01ae07b47770> in <module>
     23 model.compile(opt, loss='mse')
     24 
---> 25 hist = model.fit(X, Y, epochs=30, validation_split=0.1, verbose=1, shuffle='True', batch_size=32)

~~~~~

ValueError: Weights tensor must be 3-dimensional; got shape (1, 3)

weights[i,:,:] was treated as a 2d-array.

I tried to fix the array dimenstion by introducing np_split.

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

@qml.qnode(dev, diff_method='adjoint')
def qnode(inputs, weights):
    weights_each_layer = np.split(weights,layers,axis=0)
    for i in range(layers):
        qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
        qml.templates.StronglyEntanglingLayers(weights_each_layer[i], wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]

weight_shapes = {"weights": (layers, n_qubits,3)}

qlayer = qml.qnn.KerasLayer(qnode, weight_shapes, output_dim=n_qubits)
clayer1 = tf.keras.layers.Dense(n_qubits, activation='linear')
clayer2 = tf.keras.layers.Dense(data_dimension, activation="linear")
model = tf.keras.models.Sequential([clayer1,qlayer,clayer2])

opt = tf.keras.optimizers.Adam(learning_rate=0.01)
model.compile(opt, loss='mse')

hist = model.fit(X, Y, epochs=30, validation_split=0.1, verbose=1, shuffle='True', batch_size=32)

This revised code can run but I got another warning.

Epoch 1/30
WARNING:tensorflow:Gradients do not exist for variables ['sequential_9/keras_layer_9/weights:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['sequential_9/keras_layer_9/weights:0'] when minimizing the loss.
1/1 [==============================] - 0s 155ms/step - loss: 0.1568 - val_loss: 0.0025
Epoch 2/30
WARNING:tensorflow:Gradients do not exist for variables ['sequential_9/keras_layer_9/weights:0'] when minimizing the loss.
WARNING:tensorflow:Gradients do not exist for variables ['sequential_9/keras_layer_9/weights:0'] when minimizing the loss.
1/1 [==============================] - 0s 75ms/step - loss: 0.1231 - val_loss: 0.0030

The error seems critical.

How can I fix it?

I have found a temporary solution.
It worked!

I pushed layer-dimension on 3rd-dimension of weight.

@qml.qnode(dev, diff_method='adjoint')
def qnode(inputs, weights):
    for i in range(layers):
        qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
        qml.templates.StronglyEntanglingLayers(weights[:,:,3*i:3*(i+1)], wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(i)) for i in range(n_qubits)]

weight_shapes = {"weights": (1, n_qubits,3*layers)}
Epoch 1/30
1/1 [==============================] - 0s 140ms/step - loss: 0.8418 - val_loss: 0.1298
Epoch 2/30
1/1 [==============================] - 0s 65ms/step - loss: 0.1251 - val_loss: 0.1253
Epoch 3/30
1/1 [==============================] - 0s 65ms/step - loss: 0.0714 - val_loss: 0.1183

No error occurs.

To check if the model is correct, I have investigated the number of trainable parameters.

model.summary()
Model: "sequential_14"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_28 (Dense)             (1, 1)                    2         
_________________________________________________________________
keras_layer_14 (KerasLayer)  (1, 1)                    6         
_________________________________________________________________
dense_29 (Dense)             (1, 1)                    2         
=================================================================
Total params: 10
Trainable params: 10
Non-trainable params: 0

Since a StronglyEntanglingLayer requires 3 trainable parameters, the number of trainable parameters should be 6 for two StronglyEntanglingLayer.
Therefore, the above result is reasonable!

But the implementation would not be beautiful.
If there is some better idea, please teach me.

Hi @Kuma-quant, glad to see you got it working!!

With regards to your previous post, my guess is that you were getting the error

WARNING:tensorflow:Gradients do not exist for variables ['sequential_9/keras_layer_9/weights:0'] when minimizing the loss.

because, inside the QNode, you were using NumPy:

weights_each_layer = np.split(weights,layers,axis=0)

When using Keras with QNodes, you will need to ensure that all array/tensor manipulations use TensorFlow rather than NumPy. Otherwise, Keras will not be able to differentiate your QNode.

Luckily, there is often a TensorFlow equivalent for most NumPy functions. In this case, tf.split.

Dear josh-san,

Thanks for the advice.
tf.split worked well.
Great.

dev = qml.device("lightning.qubit", wires=n_qubits)

@qml.qnode(dev, diff_method='adjoint', immutable=False)
def qnode(inputs, weights):
    weights_each_layer = tf.split(weights, num_or_size_splits=layers, axis=0)
    for i in range(layers):
        qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))
        qml.templates.StronglyEntanglingLayers(weights_each_layer[i], wires=range(n_qubits))
    return qml.expval(qml.PauliZ(0))

weight_shapes = {"weights": (layers, n_qubits,3)}
Epoch 1/3
4/4 [==============================] - 1s 271ms/step - loss: 0.9884 - val_loss: 1.0154
Epoch 2/3
4/4 [==============================] - 1s 264ms/step - loss: 0.6703 - val_loss: 0.6914
Epoch 3/3
4/4 [==============================] - 1s 262ms/step - loss: 0.3764 - val_loss: 0.3741
1 Like

@Kuma-quant glad to hear it worked out :slightly_smiling_face: