Ayuda a proteger la Gran Barrera de Coral con TensorFlow en Kaggle Únete Challenge

Hola muchos mundos

Ver en TensorFlow.org Ejecutar en Google Colab Ver fuente en GitHub Descargar cuaderno

Este tutorial muestra cómo una red neuronal clásica puede aprender a corregir errores de calibración de qubit. Se introduce Cirq , un marco de Python para crear, editar, e invocar ruidoso Intermedio escala cuántica circuitos (NISQ), y demuestra cómo las interfaces Cirq con TensorFlow Quantum.

Configuración

pip install tensorflow==2.4.1

Instale TensorFlow Quantum:

pip install tensorflow-quantum
# Update package resources to account for version changes.
import importlib, pkg_resources
importlib.reload(pkg_resources)
<module 'pkg_resources' from '/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/pkg_resources/__init__.py'>

Ahora importe TensorFlow y las dependencias del módulo:

import tensorflow as tf
import tensorflow_quantum as tfq

import cirq
import sympy
import numpy as np

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit
2021-10-12 11:16:10.499542: E tensorflow/stream_executor/cuda/cuda_driver.cc:328] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected

1. Los fundamentos

1.1 Cirq y circuitos cuánticos parametrizados

Antes de explorar TensorFlow Quantum (CLT), vamos a ver algunos Cirq básico. Cirq es una biblioteca de Python para computación cuántica de Google. Se usa para definir circuitos, incluidas puertas estáticas y parametrizadas.

Cirq utiliza sympy símbolos para representar parámetros libres.

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

El siguiente código crea un circuito de dos qubit usando sus parámetros:

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

# Create a circuit on these qubits using the parameters you created above.
circuit = cirq.Circuit(
    cirq.rx(a).on(q0),
    cirq.ry(b).on(q1), cirq.CNOT(control=q0, target=q1))

SVGCircuit(circuit)
findfont: Font family ['Arial'] not found. Falling back to DejaVu Sans.

svg

Para evaluar circuitos, se puede utilizar el cirq.Simulator interfaz. Reemplaza parámetros libres en un circuito con números específicos pasando un cirq.ParamResolver objeto. El siguiente código calcula la salida del vector de estado sin procesar de su circuito parametrizado:

# Calculate a state vector with a=0.5 and b=-0.5.
resolver = cirq.ParamResolver({a: 0.5, b: -0.5})
output_state_vector = cirq.Simulator().simulate(circuit, resolver).final_state_vector
output_state_vector
array([ 0.9387913 +0.j        , -0.23971277+0.j        ,

        0.        +0.06120872j,  0.        -0.23971277j], dtype=complex64)

Los vectores de estado no son directamente accesibles fuera de la simulación (observe los números complejos en la salida anterior). Para ser físicamente realista, debe especificar una medida que convierta un vector de estado en un número real que las computadoras clásicas puedan entender. Cirq especifica mediciones usando combinaciones de los operadores de Pauli \(\hat{X}\), \(\hat{Y}\), y \(\hat{Z}\). Como ilustración, las siguientes medidas de código \(\hat{Z}_0\) y \(\frac{1}{2}\hat{Z}_0 + \hat{X}_1\) en el vector de estado que acaba de simular:

z0 = cirq.Z(q0)

qubit_map={q0: 0, q1: 1}

z0.expectation_from_state_vector(output_state_vector, qubit_map).real
0.8775825500488281
z0x1 = 0.5 * z0 + cirq.X(q1)

z0x1.expectation_from_state_vector(output_state_vector, qubit_map).real
-0.04063427448272705

1.2 Circuitos cuánticos como tensores

TensorFlow Quantum (TFQ) proporciona tfq.convert_to_tensor , una función que convierte Cirq objetos en tensores. Esto le permite enviar objetos Cirq a nuestras capas cuántica y operaciones cuánticas . La función se puede llamar en listas o matrices de Cirq Circuits y Cirq Paulis:

# Rank 1 tensor containing 1 circuit.
circuit_tensor = tfq.convert_to_tensor([circuit])

print(circuit_tensor.shape)
print(circuit_tensor.dtype)
(1,)
<dtype: 'string'>

Este codifica la Cirq objetos como tf.string tensores que tfq operaciones decodificar según sea necesario.

# Rank 1 tensor containing 2 Pauli operators.
pauli_tensor = tfq.convert_to_tensor([z0, z0x1])
pauli_tensor.shape
TensorShape([2])

1.3 Simulación de circuito por lotes

TFQ proporciona métodos para calcular valores esperados, muestras y vectores de estado. Por ahora, vamos a centrarnos en valores esperados.

La interfaz de más alto nivel para el cálculo de valores esperados es la tfq.layers.Expectation capa, que es una tf.keras.Layer . En su forma más simple, esta capa es equivalente a la simulación de un circuito parametrizada durante muchos cirq.ParamResolvers ; sin embargo, TFQ permite el procesamiento por lotes siguiendo la semántica de TensorFlow y los circuitos se simulan mediante un código C ++ eficiente.

Crear un lote de valores para sustituir nuestra a y b parámetros:

batch_vals = np.array(np.random.uniform(0, 2 * np.pi, (5, 2)), dtype=np.float32)

La ejecución del circuito por lotes sobre los valores de los parámetros en Cirq requiere un bucle:

cirq_results = []
cirq_simulator = cirq.Simulator()

for vals in batch_vals:
    resolver = cirq.ParamResolver({a: vals[0], b: vals[1]})
    final_state_vector = cirq_simulator.simulate(circuit, resolver).final_state_vector
    cirq_results.append(
        [z0.expectation_from_state_vector(final_state_vector, {
            q0: 0,
            q1: 1
        }).real])

print('cirq batch results: \n {}'.format(np.array(cirq_results)))
cirq batch results: 
 [[-0.86000198]
 [-0.83201134]
 [-0.93121868]
 [ 0.28362957]
 [ 0.72345072]]

La misma operación se simplifica en TFQ:

tfq.layers.Expectation()(circuit,
                         symbol_names=[a, b],
                         symbol_values=batch_vals,
                         operators=z0)
<tf.Tensor: shape=(5, 1), dtype=float32, numpy=
array([[-0.8600013 ],
       [-0.83201194],
       [-0.931218  ],
       [ 0.28362915],
       [ 0.7234506 ]], dtype=float32)>

2. Optimización híbrida cuántica clásica

Ahora que usted ha visto los conceptos básicos, vamos a usar TensorFlow Quantum para la construcción de una red neuronal cuántico-clásica híbrido. Entrenarás una red neuronal clásica para controlar un solo qubit. El control será optimizado para preparar correctamente el qubit en el 0 o 1 estado, la superación de un error de calibración sistemática simulado. Esta figura muestra la arquitectura:

Incluso sin una red neuronal, este es un problema sencillo de resolver, pero el tema es similar a los problemas reales de control cuántico que podría resolver usando TFQ. Se muestra un ejemplo de extremo a extremo de una computación cuántica-clásica usando el tfq.layers.ControlledPQC capa interior (parametrizada Quantum Circuit) de un tf.keras.Model .

Para la implementación de este tutorial, esta arquitectura se divide en 3 partes:

  • El circuito de entrada o circuito punto de datos: Los tres primeros \(R\) puertas.
  • El circuito controlado: Los otros tres \(R\) puertas.
  • El controlador: La clásica configuración de los parámetros del circuito controlado neural-red.

2.1 La definición del circuito controlado

Defina una rotación de un solo bit que se pueda aprender, como se indica en la figura anterior. Esto corresponderá a nuestro circuito controlado.

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

svg

2.2 El controlador

Ahora defina la red del controlador:

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

Dado un lote de comandos, el controlador emite un lote de señales de control para el circuito controlado.

El controlador se inicializa aleatoriamente, por lo que estas salidas aún no son útiles.

controller(tf.constant([[0.0],[1.0]])).numpy()
array([[ 0.        ,  0.        ,  0.        ],
       [ 0.37945798, -0.31607187,  0.23631476]], dtype=float32)

2.3 Conecte el controlador al circuito

Uso tfq para conectar el controlador al circuito controlado, como un solo keras.Model .

Consulte la guía API funcional Keras para más información sobre este tipo de modelo de definición.

Primero defina las entradas al modelo:

# 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')

A continuación, aplique operaciones a esas entradas para definir el cálculo.

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])

Ahora empaquetar este cálculo como tf.keras.Model :

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

La arquitectura de la red está indicada por el gráfico del modelo a continuación. Compare este diagrama de modelo con el diagrama de arquitectura para verificar que sea correcto.

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

png

Este modelo toma dos entradas: los comandos para el controlador y el circuito de entrada cuya salida el controlador está intentando corregir.

2.4 El conjunto de datos

Los intentos del modelo a la salida el valor de medición correcto correcto de \(\hat{Z}\) para cada comando. Los comandos y los valores correctos se definen a continuación.

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

Este no es el conjunto de datos de entrenamiento completo para esta tarea. Cada punto de datos del conjunto de datos también necesita un circuito de entrada.

2.4 Definición del circuito de entrada

El circuito de entrada a continuación define la descalibración aleatoria que el modelo aprenderá a corregir.

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

Hay dos copias del circuito, una para cada punto de datos.

datapoint_circuits.shape
TensorShape([2])

2.5 Entrenamiento

Con las entradas definidas puede probar a ejecutar el tfq modelo.

model([datapoint_circuits, commands]).numpy()
array([[-0.74668354],
       [-0.8791507 ]], dtype=float32)

Ahora ejecute un proceso de formación estándar para ajustar estos valores hacia los expected_outputs .

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)
plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()

png

En este gráfico se puede ver que la red neuronal ha aprendido a superar el error de calibración sistemático.

2.6 Verificar salidas

Ahora use el modelo entrenado para corregir los errores de calibración de qubit. Con Cirq:

def check_error(command_values, desired_values):
  """Based on the value in `command_value` see how well you could prepare
  the full circuit to have `desired_value` when taking expectation w.r.t. Z."""
  params_to_prepare_output = controller(command_values).numpy()
  full_circuit = noisy_preparation + model_circuit

  # Test how well you can prepare a state to get expectation the expectation
  # value in `desired_values`
  for index in [0, 1]:
    state = cirq_simulator.simulate(
        full_circuit,
        {s:v for (s,v) in zip(control_params, params_to_prepare_output[index])}
    ).final_state_vector
    expt = cirq.Z(qubit).expectation_from_state_vector(state, {qubit: 0}).real
    print(f'For a desired output (expectation) of {desired_values[index]} with'
          f' noisy preparation, the controller\nnetwork found the following '
          f'values for theta: {params_to_prepare_output[index]}\nWhich gives an'
          f' actual expectation of: {expt}\n')


check_error(commands, expected_outputs)
For a desired output (expectation) of [1.] with noisy preparation, the controller
network found the following values for theta: [-2.3688276   3.655123    0.24446163]
Which gives an actual expectation of: 0.9826769232749939

For a desired output (expectation) of [-1.] with noisy preparation, the controller
network found the following values for theta: [2.3142653  0.05689991 0.72213745]
Which gives an actual expectation of: -0.988701581954956

El valor de la función de pérdida durante el entrenamiento proporciona una idea aproximada de qué tan bien está aprendiendo el modelo. Cuanto menor es la pérdida, la más estrechos los valores esperados en la célula anterior es a desired_values . Si no están tan preocupados con los valores de los parámetros, siempre se puede comprobar las salidas desde arriba con tfq :

model([datapoint_circuits, commands])
<tf.Tensor: shape=(2, 1), dtype=float32, numpy=
array([[ 0.9826765 ],
       [-0.98870134]], dtype=float32)>

3 Aprender a preparar estados propios de diferentes operadores

La elección de los \(\pm \hat{Z}\) estados propios que corresponden a 1 y 0 fue arbitraria. Usted podría tener la misma facilidad querido 1 para corresponder a la \(+ \hat{Z}\) estado propio y 0 para corresponder a la \(-\hat{X}\) estado propio. Una forma de lograr esto es especificando un operador de medición diferente para cada comando, como se indica en la siguiente figura:

Esto requiere el uso de tfq.layers.Expectation . Ahora su entrada ha crecido para incluir tres objetos: circuito, comando y operador. La salida sigue siendo el valor esperado.

3.1 Definición de nuevo modelo

Echemos un vistazo al modelo para realizar esta tarea:

# 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')

Aquí está la red del controlador:

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

Combinar el circuito y el controlador en un único keras.Model usando 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])

3.2 El conjunto de datos

Ahora también puede incluir los operadores que desea a medida para cada punto de datos que usted suministra para 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)

3.3 Entrenamiento

Ahora que tiene sus nuevas entradas y salidas, puede entrenar una vez más usando keras.

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)
Epoch 1/30
1/1 [==============================] - 0s 336ms/step - loss: 1.3425
Epoch 2/30
1/1 [==============================] - 0s 3ms/step - loss: 1.0469
Epoch 3/30
1/1 [==============================] - 0s 3ms/step - loss: 0.7611
Epoch 4/30
1/1 [==============================] - 0s 3ms/step - loss: 0.4976
Epoch 5/30
1/1 [==============================] - 0s 2ms/step - loss: 0.3565
Epoch 6/30
1/1 [==============================] - 0s 2ms/step - loss: 0.2611
Epoch 7/30
1/1 [==============================] - 0s 2ms/step - loss: 0.1574
Epoch 8/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0850
Epoch 9/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0568
Epoch 10/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0536
Epoch 11/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0608
Epoch 12/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0648
Epoch 13/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0581
Epoch 14/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0473
Epoch 15/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0396
Epoch 16/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0348
Epoch 17/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0310
Epoch 18/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0278
Epoch 19/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0258
Epoch 20/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0249
Epoch 21/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0230
Epoch 22/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0189
Epoch 23/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0132
Epoch 24/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0078
Epoch 25/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0040
Epoch 26/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0019
Epoch 27/30
1/1 [==============================] - 0s 2ms/step - loss: 8.9138e-04
Epoch 28/30
1/1 [==============================] - 0s 2ms/step - loss: 5.2909e-04
Epoch 29/30
1/1 [==============================] - 0s 3ms/step - loss: 4.9192e-04
Epoch 30/30
1/1 [==============================] - 0s 2ms/step - loss: 6.7420e-04
plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()

png

La función de pérdida se ha reducido a cero.

El controller está disponible como un modelo independiente. Llame al controlador y verifique su respuesta a cada señal de comando. Haría falta algo de trabajo para comparar correctamente estas salidas al contenido de random_rotations .

controller.predict(np.array([0,1]))
array([[-2.846111  , -0.89885163, -1.3263428 ],
       [-1.2928145 , -0.22680755, -0.6467615 ]], dtype=float32)