Hybrid quantum optics neural network

When I tried to hybrid quantum optics neural network with classical one, the Error raised, it says that: QuantumFunctionError: Continuous and discrete operations are not allowed in the same quantum circuit.
But I don’t know why, could you please help me modify this incorrect code? My previous codes are as follows:

import tensorflow as tf
import pennylane as qml

import matplotlib.pyplot as plt
import numpy as np
from sklearn.datasets import make_moons

# Set random seeds
np.random.seed(42)
tf.random.set_seed(42)

X, y = make_moons(n_samples=200, noise=0.1)
y_hot = tf.keras.utils.to_categorical(y, num_classes=2)  # one-hot encoded labels

c = ["#1f77b4" if y_ == 0 else "#ff7f0e" for y_ in y]  # colours for each class
plt.axis("off")
plt.scatter(X[:, 0], X[:, 1], c=c)
plt.show()

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

layer_1 = tf.keras.layers.Dense(2, activation="relu")
layer_2 = tf.keras.layers.Dense(2, activation="softmax")

model = tf.keras.Sequential([layer_1, layer_2])
model.compile(loss="mae")

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

def layer(theta, phi, wires):
    M = len(wires)
    phi_nonlinear = np.pi / 2

    qml.templates.Interferometer(
        theta, phi, np.zeros(M), wires=wires, mesh="rectangular", beamsplitter="pennylane"
    )

    for i in wires:
        qml.Kerr(phi_nonlinear, wires=i)

@qml.qnode(dev)
def quantum_neural_net(inputs, weights):
    wires = list(range(len(inputs)))

    # Encode input x into a sequence of quantum fock states
    qml.templates.AngleEmbedding(inputs, wires=range(n_qubits))

    # "layer" subcircuits
    for i, v in enumerate(weights):
        layer(v[: len(v) // 2], v[len(v) // 2 :], wires)

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

M = 2
num_variables_per_layer = M * (M - 1)
weight_shapes = {"weights": (n_layers, num_variables_per_layer)}
qlayer = qml.qnn.KerasLayer(quantum_neural_net, weight_shapes, output_dim=n_qubits)

clayer_1 = tf.keras.layers.Dense(2)
clayer_2 = tf.keras.layers.Dense(2, activation="softmax")
model = tf.keras.models.Sequential([clayer_1, qlayer, clayer_2])

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

fitting = model.fit(X, y_hot, epochs=6, batch_size=5, validation_split=0.25, verbose=2)

Hi @Hamitonian,

The error is occuring because the continuous variable (CV) operations like qml.Kerr are not supported on the device because it is a qubit device, rather than a qumode device. To perform such gates, you’ll have to use a different type of device that supports CV operations (such as strawberryfields.fock).

Note that on those devices, however, it will then no longer be possible to use a template like AngleEmbedding, since this is an operation on qubits. So I think you’ll need two devices and a bit of re-writing to get the code working with both types of operations.

There’s an example in this demo that might be helpful here. It shows a hybrid network that combines the output of one QNode that runs on a qubit device, and another that runs on a CV device.

Hope that helps, please let us know if you have any further questions!

Thanks for your generous help!
I’ve changed my code but suffered another problem, it says that Beamsplitter: Real scalar parameter expected, got <class ‘numpy.ndarray’>., but I have no idea how to solve this problem, here is my code:

import tensorflow as tf
import pennylane as qml
from pennylane import numpy as np
tf.keras.backend.set_floatx('float64')
from sklearn.datasets import make_moons

layer_1 = tf.keras.layers.Dense(2)
layer_2 = tf.keras.layers.Dense(2, activation="softmax")

model = tf.keras.Sequential([layer_1, layer_2])
model.compile(loss="mae")

# Set random seeds
np.random.seed(42)
tf.random.set_seed(42)

X, y = make_moons(n_samples=200, noise=0.1)
y_hot = tf.keras.utils.to_categorical(y, num_classes=2)  # one-hot encoded labels


n_qubits = 2
dev = qml.device("strawberryfields.fock", wires=2, cutoff_dim=4)

@qml.qnode(dev)
def qnode(inputs, weights):
    qml.Displacement(inputs[0], 0, wires=0)
    qml.Displacement(inputs[1], 0, wires=1)
    qml.Beamsplitter(weights[0], weights[1], wires=[0, 1])
    qml.Rotation(weights[2], 0, wires=0)
    qml.Rotation(weights[3], 0, wires=1)
    return [qml.expval(qml.NumberOperator(wires=i)) for i in range(n_qubits)]

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

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

clayer_1 = tf.keras.layers.Dense(2)
clayer_2 = tf.keras.layers.Dense(2, activation="softmax")
model = tf.keras.models.Sequential([clayer_1, qlayer, clayer_2])
opt = tf.keras.optimizers.SGD(learning_rate=0.2)
model.compile(opt, loss="mae", metrics=["accuracy"])
fitting = model.fit(X, y_hot, epochs=6, batch_size=5, validation_split=0.25, verbose=2)

Another question is that how to restrict the weights in my Qnode to [-2pi, 2pi], in consideration of phaseshift and beamsplitter.

I follow the example given by https://pennylane.ai/qml/demos/tutorial_qnn_module_tf.html

Hi @Hamiltonian,

The error with the beamsplitter is because of the shape of the weights vector being used - it has shape (6, 4, 1), so calling weights[0] will give a vector of shape (4, 1) rather than a single value (which is what’s expected). You’ll have to modify the shape of weights, or adjust the QNode to extract the desired entries.

Additional constraints on the weights can be added when initializing the KerasLayer through the weight_specs argument (see the “Usage Details” section of the docs for an example). I think the example there with tf.keras.initializers.RandomUniform is the one you’ll want, as it allows specification of bounds on the values.