Loading...

Postulate is the best way to take and share notes for classes, research, and other learning.

More info

Tensorflow optimizers need their parameters to be Tensorflow-type objects | and other supplementary data-science-library knowledge needed for building a QGAN

Profile picture of Laura GaoLaura Gao
Apr 4, 2021Last updated Apr 4, 20215 min read

In the QGAN tutorial I was following to build my QGAN, this bit of code is shown:

np.random.seed(0) eps = 1e-2 init_gen_weights = np.array([np.pi] + [0] * 8) + \ np.random.normal(scale=eps, size=(9,)) init_disc_weights = np.random.normal(size=(9,)) gen_weights = tf.Variable(init_gen_weights) disc_weights = tf.Variable(init_disc_weights)

This portion of code is for initializing the weights. There are 2 sets of weights that we need - the generator weights and discriminator weights, each set of weights to parametize the respective portion of the quantum circuit.

However, as a complete noob to Data Science with Python, this got me confused. Why use np.array()? Why tf.Variable()? What is eps? Why not just declare the weights as a simple array, like init_gen_weights = np.random.rand(1, 9)?

We will break down every single section of that code.

1. Numpy Arrays

What does the np.array() function do? A numpy.ndarray-type object (who's output of type() is <class 'numpy.ndarray'>) displays as [2 4 36 2 9] when printed instead of [2, 4, 36, 2, 9] as a regular python list would show - the array elements do not have commas in between. In the command prompt, an outputted numpy.ndarray (when the array is outputted without using print statements, such as when you type the variable name and press enter) is displayed like so:

array([[0.7394007 ], [0.69474716], [0.49571217], [0.73310248], [0.66751727], [0.47486376], [0.19480713], [0.93749693], [0.10668695]])

The np.array() function takes in one argument called the "object", which is a native python list or array, and converts it into a numpy.ndarray-type object.

2. ([np.pi] + [0] * 8)

[np.pi] + [0] * 8 creates this vector: [3.141592653589793, 0, 0, 0, 0, 0, 0, 0, 0]. Wow. I didn't know you can add and multiply lists in python like that.

3. np.random.normal()

np.random.normal() if ran without arguments, outputs random numbers with normal distribution. 0 at the mean. Here's the distribution I got when I ran this function 80 times:



The meaning of the parameters, according to the docs:

  • 'scale' is how much up or down you compress it. In the case of our code, when it was scaled by 1e-2 or 0.01, you compress the normal distribution, almost as if you divide each value by 100.

  • size is the size of the output array.

The output of np.random.normal(size=(9,)) is a numpy.ndarray type object. The array is a row vector with dimension of 9. And then the earlier part of the line, the np.array([np.pi] + [0] * 8) has to be passed through the np.array() function to match the object type so that the 2 vectors can be added.

4. tf.Variable()

In these two lines:

gen_weights = tf.Variable(init_gen_weights) disc_weights = tf.Variable(init_disc_weights)

The numpy array is passed through the tf.Variable() function, which turns them into objects with the type <class 'tensorflow.python.ops.resource_variable_ops.ResourceVariable'>.

Why?

They needs to be turned into that tensorflow class because of this line:

opt.minimize(cost, disc_weights)

opt.minimize() will return an error if disc_weights is a numpy array. (Earlier, we declared the opt variable to be a tensorflow optimizer.)

This was my big EUREKA moment. Here is where I understood - the root cause behind all this trouble with np arrays, tf.Variable(), and the blockers behind all my hopes and dreams are caused by the simple fact that:

Tensorflow optimizers need their parameters to be Tensorflow-type objects.



This means that our earlier functions, the two cost functions as well as the probability calculating functions, take in variables gen_weights and disc_weights who are <class 'tensorflow.python.ops.resource_variable_ops.ResourceVariable'> objects. As a result, the output of the cost functions and probability calculating functions are also this tensorflow-type object.

To turn this tensorflow-type object into a readable form, we pass it through the .numpy() method.

This is why the outputs of probability and cost functions, e.g. the later line print(prob_real_true(disc_weights).numpy())) must have the .numpy() function afterward.

E.g.:

print(gen_weights[1].numpy()) displays 0.004001572083672233

print(print(gen_weights[0])) displays tf.Tensor(3.1592331770494697, shape=(), dtype=float64)

OK YAY NOW I UNDRESTAND WHY EVERY LINE OF CODE SHOWN HERE IS NECESSARY!!

A note on pyplot:

if normal_distribution is a list size 80 containing 80 numbers, and I want to histogram plot them, I use 2 lines of code to plot em:

count, bins, ignored = plt.hist(normal_distribution, 30, density=True) plt.show()

Wow. That's easy. Axis labels, scale specifications, and titles use many more lines of code but are purely decorative. First principles thinking to the moon!

Misc notes

You can turn the numpy array to a python list using variablename.tolist() (docs)

np.random.rand(9, 1) also outputs a numpy.ndarray class.

Numpy arrays can be indexed the same way as regular ol' python lists.

.numpy() is not for numpy arrays but for that tensorflow-type object.

I'm writing this post to remember what I learned. Most things that I wrote about here was found out just by trying out different things in command prompt and jupyter notebook, with the sprinkling of a bit of first principles thinking ;)


Comments (loading...)

Sign in to comment

Quantum computing

Working notes from learning QC