How to control randomness when training?

Hi. What seeds do I have to set to obtain a reproducible result when training a hybrid circuit with pennylane and tensorflow?

If I want to make the results completely different, what do I need to instantiate again to make sure it follows a different seed?

I am getting very similar results between different runs.
What I am doing is using a function to reset these seeds:

def ResetSeed(seed=None):
    np.random.seed(seed)     # Seed for NumPy random number generator
    tf.random.set_seed(seed) # Seed for TensorFlow random number generator
    keras.utils.set_random_seed(seed)

However, the results are very very similar every time.
The code is quite long so I will give you the gist of it here and maybe you can help me find out if this behaviour is expected.

I define a device and a circuit and JIT compile the circuit with circuit = tf.function(circuit, jit_compile=True). Then, create a layer from it with QuantumLayer = qml.qnn.KerasLayer(circuit, params_shape, output_dim=n_qubits, trainable=True).

In a loop I train the hybrid model 10 times with different seeds. Inside the loop, I reset the seed, define the hybrid model with Tensorflow, compile it and train it with model.fit(...).

Should I redefine the device, the quantum circuit and the QuantumLayer everytime I reset the seed? Or does resetting the seed afterwards already change their behaviour?

Thanks,
João

Hi @joaofbravo ,

Are you able to share a minimal example that shows the behaviour you’re describing? Maybe you can create some random arrays and see if they change? You can also try testing this within your program.

It’s much easier to find where the issue is occurring when there’s code to copy-paste and test :smiley:

On the other hand, have you tried moving the three lines of code you shared outside of the ResetSeed function? I’m wondering if this is the issue.

In any case please feel free to share a small (but self-contained) example of code.

Hi @CatalinaAlbornoz.

It’s a bit hard to present all this in a self contained example. Essentially, I am training hybrid and classical models and comparing them and the hybrid models are having much much lower variance, which is strange. I am just wondering how Pennylane deals with randomness as I cannot find it in the docs.

What seeds do I need to set to ensure a Pennylane circuit always gets the same results?

Another subquestion would be: when I reset a seed, do I need to redefine a qml.device and/or a qml.qnn.KerasLayer so that they behave differently?
In other words, is their behaviour random in some way? Does it depend the seed state when they are defined?

Hi @joaofbravo,

Randomness in this case is very complicated because there’s local seeds that can be applied to many functions and the result will vary if you don’t set a seed every time.

For example, you can indeed set a seed when you create your device but it may or not affect your result depending on specifics of your algorithm. For example in the code below you can remove the seed set when creating the device and the results won’t change.

Assuming you’re using default.qubit you can find the details here in the docs.

For qml.qnn.KerasLayer however it is important to set a seed. The weight_specs argument allows you to use a Keras initializer where you can set a seed if you want to have more control over randomness in initializing the parameters.

Here’s a modified example based on our demo on Turning quantum nodes into Keras Layers.

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

# Data
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()

# QNode
n_qubits = 2
dev = qml.device("default.qubit", seed=42, wires=n_qubits)

@qml.qnode(dev)
def circuit(inputs, weights):
    qml.AngleEmbedding(inputs, wires=range(n_qubits))
    qml.BasicEntanglerLayers(weights, wires=range(n_qubits))
    return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_qubits)]

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

initializer = tf.random_uniform_initializer(minval=-0.05, maxval=0.05, seed=42)

qlayer = qml.qnn.KerasLayer(circuit, weight_shapes, output_dim=n_qubits,weight_specs = {"weights": {"initializer": initializer}})

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)

I hope this answers your questions!

1 Like

Great, that was what I was looking for. Thanks

1 Like