TensorFlow Quantum basic tutorial explained with supplementary

Google published “TensorFlow Quantum” to play with quantum machine learning. Now I just explain the basic tutorial for the optimization of single qubit state vector.

I just follow the creative commons license on this tutorial.

Except as otherwise noted, the content of this page is licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License. For details, see the Google Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Hello, many worlds

The tutorial is here. It is little bit difficult to understand for the first and I just explain this with some supplementary.

What is TensorFlow Quantum?

It is a combination of quantum computing and machine learning combining each other. Google already provides quantum circuit simulator “cirq” unofficially.

Install

If you try to use google colaboratory, you need

try:
%tensorflow_version 2.x
except Exception:
pass

#=>TensorFlow 2.x selected.

The installation is

pip install -q tensorflow-quantum

It is very easy. And then import them on python.

#import tensorflow and its libraries
import tensorflow as tf
import tensorflow_quantum as tfq
#import quantum circuit situmator and other numerical libraries
import cirq
import sympy
import numpy as np

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit

Before start

This tutorial is look little bit difficult to understand first time. What we are going to do is “to operate a quantum state of 1qubit”.

The simulator has a function of getting “state vector” directly to see how the quantum state is , but actual quantum computer cannot see it. So, indirectly we try to get the expectation value related to the state vector.

And usually it is difficult for human to operate on a complicated quantum state, so now we need Neural Network to learn an operation on a qubit (especially on this tutorial).

Parametrize a quantum circuit

Now we use cirq to simulate a quantum circuit. The quantum circuit is parametrized by some parameter on angle in the circuit. Now we prepare 2parameter as a symbol on sympy.

a, b = sympy.symbols('a b')

And using these angle parameters we prepare a quantum circuit,

# 2qubits circuit
q0, q1 = cirq.GridQubit.rect(1, 2)

# rx gate on q0 with parameter a, and ry gate on q1 with parameter b
circuit = cirq.Circuit(
cirq.rx(a).on(q0),
cirq.ry(b).on(q1), cirq.CNOT(control=q0, target=q1))

SVGCircuit(circuit)

State Vector

Now we little bit understand the state vector. The 0 state and 1 state on quantum computing is |0> and |1>. And these can be written in vector like,

An arbitrary qubit state is,

This is also written in polar coordinates as,

The quantum state is expressed as a bloch sphere.

If you want to change the state of qubit, you can use a unitary matrix to operate on it.

one of these unitary matrix to realize an arbitrary qubit state is,

And the basic gate set usually used in near-term quantum computing (NISQ) is pauli operators,

And rotation pauli operators,

If you want to check the state vector more than 2qubits, using tensor product we can get bigger state vector.

And also quantum gate set can be operated on tensor product.

So, finally we can calculate the quantum circuit created on cirq, starting from initial state |00> as,

4*4 matrix is CX(CNOT) gate.

Check the state vector

Now we used symbol on angles. Put actual angles in these variables we can check the state vector (it is a function of simulator, we cannot check state vector on actual quantum computer).

# state vector at a=0.5, b=-0.5
resolver = cirq.ParamResolver({a: 0.5, b: -0.5})
output_state_vector = cirq.Simulator().simulate(circuit, resolver).final_state
output_state_vector

And we get,

array([ 0.9387913 +0.j        , -0.23971277+0.j        ,
0. +0.06120872j, 0. -0.23971277j], dtype=complex64)

This is state vector which has 4 elements.

Thinking about actual quantum computer

Usually we cannot get state vector directly from the quantum computer. So instead of reading the state vector, we usually try to calculate a specific value indirectly related to the state vector.

z0 = cirq.Z(q0)

qubit_map={q0: 0, q1: 1}

z0.expectation_from_wavefunction(output_state_vector, qubit_map).real

This is an expectation value of Z operator. Usually we get the quantum computation result on Z-axis of bloch sphere. By getting samples, you can easily estimate the expectation value of pauli Z operator.

The result is,

0.8775825500488281

Expectation value of pauli operator

This expectation value is calculated with a simple operation.

With matrix A, vector x and scalar λ. There is a eigenstate x and eigenvalue λ for hermite matrix A. Especially in quantum computation, instead of A, H of Hamiltonian is used. and instead of vector x, we use state vector |ψ>.

Operating <ψ| from the left side we get,

So, what we want is to get the expectation value of H as,

<ψ| is a conjugate of |ψ>

Let’s try to check a little bit on numpy. v_a is a state vector on cirq circuit.

v_a = output_state_vector

Now we prepare conjugate of a.

v_b = np.conjugate(a)
v_b

the state vector is,

array([ 0.9387913 -0.j        , -0.23971277-0.j        ,
0. -0.06120872j, 0. +0.23971277j], dtype=complex64)

And prepare Z and I matrix

Z = np.array([[1,0],[0,-1]])
I = np.eye(2)

And we can get the expectation value.

v_b@np.kron(Z,I)@v_a

and we get,

(0.8775825505048687+0j)

This is almost the same one from cirq. Let’s check another one.

z0x1 = 0.5 * z0 + cirq.X(q1)

z0x1.expectation_from_wavefunction(output_state_vector, qubit_map).real

the value is,

-0.04063427448272705

This can be expressed as linear combination of two expectation.

(v_b@np.kron(Z,I)@v_a)/2 + v_b@np.kron(I,X)@v_a

It looks good.

(-0.04063427246870743+0j)

Quantum Circuit to Tensor

Tensorflow Quantum (TFQ) is providing a function tfq.convert_to_tensor to convert quantum circuit to a tensor. This is useful to for tensorflow.

# to a order-1 tensor (vector)
circuit_tensor = tfq.convert_to_tensor([circuit])

print(circuit_tensor.shape)
print(circuit_tensor.dtype)

for example,

pauli_tensor = tfq.convert_to_tensor([z0, z0x1])
pauli_tensor.shape

it is easy.

Quantum-Classical hybrid

Let’s look at a simple example here.

https://www.tensorflow.org/quantum/tutorials/hello_many_worlds/
# Parameters that the classical NN will feed values into.
control_params = sympy.symbols('theta_1 theta_2 theta_3')

# Create the parameterized circuit.
qubit = cirq.GridQubit(0, 0)
model_circuit = cirq.Circuit(
cirq.rz(control_params[0])(qubit),
cirq.ry(control_params[1])(qubit),
cirq.rx(control_params[2])(qubit))

SVGCircuit(model_circuit)

And the classical controller, it is a normal classical NN on tensorflow

# The classical neural network layers.
controller = tf.keras.Sequential([
tf.keras.layers.Dense(10, activation='elu'),
tf.keras.layers.Dense(3)
])

Let’s connect this to the circuit

# This input is the simulated miscalibration that the model will learn to correct.
circuits_input = tf.keras.Input(shape=(),
# The circuit-tensor has dtype `tf.string`
dtype=tf.string,
name='circuits_input')

# Commands will be either `0` or `1`, specifying the state to set the qubit to.
commands_input = tf.keras.Input(shape=(1,),
dtype=tf.dtypes.float32,
name='commands_input')

The upper input is a circuit, we now remember that TFQ has function to convert circuit to a tensor, the.

The commands_input is an input for classical input. This time we are going to do a supervised learning.

dense_2 = controller(commands_input)

# TFQ layer for classically controlled circuits.
expectation_layer = tfq.layers.ControlledPQC(model_circuit,
# Observe Z
operators = cirq.Z(qubit))
expectation = expectation_layer([circuits_input, dense_2])

expectation layer is to calculate the expectation value of operator. This time we try to get expectation value of pauli Z and it will be done with <ψ|Z|ψ>.

To create a state of |ψ> we need mode_circuit, and two input of circuit input as tensor and NN input finally operate three angle parameter on thetas.

And we now have,

# The full Keras model is built from our layers.
model = tf.keras.Model(inputs=[circuits_input, commands_input],
outputs=expectation)

Let’s check the architecture,

tf.keras.utils.plot_model(model, show_shapes=True, dpi=70)

Again we have two inputs of command into classical NN and quantum circuit as tensor. Finally with these inputs controlled_pqc calculate the expectation value from the circuit.

Data set

input of classical NN is 0 or 1. And corresponding outputs are 1 or -1 as an expectation value of pauli Z (which is eigenvalue of Z).

# The command input values to the classical NN.
commands = np.array([[0], [1]], dtype=np.float32)

# The desired Z expectation value at output of quantum circuit.
expected_outputs = np.array([[1], [-1]], dtype=np.float32)

As an input quantum circuit we have,

random_rotations = np.random.uniform(0, 2 * np.pi, 3)
noisy_preparation = cirq.Circuit(
cirq.rx(random_rotations[0])(qubit),
cirq.ry(random_rotations[1])(qubit),
cirq.rz(random_rotations[2])(qubit)
)
datapoint_circuits = tfq.convert_to_tensor([
noisy_preparation
] * 2) # Make two copied of this circuit

The input is set to random and cannot change among this process. This data point is converted to the tensor and we input this tensor as circuits input.

Training

With the inputs defined you can test-run the tfq model.

model([datapoint_circuits, commands]).numpy()

we get

array([[-0.3738031 ],
[ 0.32185417]], dtype=float32)

Let’s run a training process,

optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)
loss = tf.keras.losses.MeanSquaredError()
model.compile(optimizer=optimizer, loss=loss)
history = model.fit(x=[datapoint_circuits, commands],
y=expected_outputs,
epochs=30,
verbose=0)

Adam with learning rate as 0.05, as a loss function MSE is used.

plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()

Now we see it is correctly managed.

Learning on different operators

Next we try to identify two kind of operators instead of optimizing two eigenvalue of one pauli operator.

This is an almost the same operation on just one operator, but this time we add one more input as operators input.

# Define inputs.
commands_input = tf.keras.layers.Input(shape=(1),
dtype=tf.dtypes.float32,
name='commands_input')
circuits_input = tf.keras.Input(shape=(),
# The circuit-tensor has dtype `tf.string`
dtype=tf.dtypes.string,
name='circuits_input')
operators_input = tf.keras.Input(shape=(1,),
dtype=tf.dtypes.string,
name='operators_input')

Usually we cannot get expectation value of Z on the same basis. We need to change the basis to measure. Or transform the state vector to get expectation value on different axis.

For example, if you want to get this expectation value, <ψ|X|ψ>. You need to transform X =HZH and calculate, <ψ|HZH|ψ> and it is finally <ψ’|Z|ψ’>.

To choose if you include H gate just before the measurement, you need another input.

Here is the controller network:

# Define classical NN.
controller = tf.keras.Sequential([
tf.keras.layers.Dense(10, activation='elu'),
tf.keras.layers.Dense(3)
])

Combine the circuit and the controller into a single keras.Model using tfq:

dense_2 = controller(commands_input)# Since you aren't using a PQC or ControlledPQC you must append
# your model circuit onto the datapoint circuit tensor manually.
full_circuit = tfq.layers.AddCircuit()(circuits_input, append=model_circuit)
expectation_output = tfq.layers.Expectation()(full_circuit,
symbol_names=control_params,
symbol_values=dense_2,
operators=operators_input)
# Contruct your Keras model.
two_axis_control_model = tf.keras.Model(
inputs=[circuits_input, commands_input, operators_input],
outputs=[expectation_output])

Dataset

Now you will also include the operators you wish to measure for each datapoint you supply for model_circuit:

# The operators to measure, for each command.
operator_data = tfq.convert_to_tensor([[cirq.X(qubit)], [cirq.Z(qubit)]])
# The command input values to the classical NN.
commands = np.array([[0], [1]], dtype=np.float32)
# The desired expectation value at output of quantum circuit.
expected_outputs = np.array([[1], [-1]], dtype=np.float32)

Training

optimizer = tf.keras.optimizers.Adam(learning_rate=0.05)
loss = tf.keras.losses.MeanSquaredError()

two_axis_control_model.compile(optimizer=optimizer, loss=loss)

history = two_axis_control_model.fit(
x=[datapoint_circuits, commands, operator_data],
y=expected_outputs,
epochs=30,
verbose=1)

Check

plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()

And now it’s all done.

The tutorial itself is a very basic quantum state operation. But you need some of prior knowledge on it. TensorFlow Quantum is including a lot of models so let’s enjoy learning it everyday.

--

--