Réserve cette date! Google I / O revient du 18 au 20 mai S'inscrire maintenant
Cette page a été traduite par l'API Cloud Translation.
Switch to English

Bonjour, de nombreux mondes

Voir sur TensorFlow.org Exécuter dans Google Colab Afficher la source sur GitHub Télécharger le cahier

Ce tutoriel montre comment un réseau neuronal classique peut apprendre à corriger les erreurs d'étalonnage des qubits. Il présente Cirq , un framework Python pour créer, éditer et invoquer des circuits Noisy Intermediate Scale Quantum (NISQ), et montre comment Cirq s'interface avec TensorFlow Quantum.

Installer

pip install -q tensorflow==2.3.1

Installez TensorFlow Quantum:

pip install -q tensorflow-quantum

Importez maintenant TensorFlow et les dépendances du module:

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

1. Les bases

1.1 Cirq et circuits quantiques paramétrés

Avant d'explorer TensorFlow Quantum (TFQ), examinons quelques notions de base de Cirq . Cirq est une bibliothèque Python pour l'informatique quantique de Google. Vous l'utilisez pour définir des circuits, y compris des portes statiques et paramétrées.

Cirq utilise des symboles SymPy pour représenter des paramètres libres.

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

Le code suivant crée un circuit à deux qubits à l'aide de vos paramètres:

# 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

Pour évaluer les circuits, vous pouvez utiliser l'interface cirq.Simulator . Vous remplacez les paramètres libres dans un circuit par des nombres spécifiques en passant un objet cirq.ParamResolver . Le code suivant calcule la sortie de vecteur d'état brut de votre circuit paramétré:

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

Les vecteurs d'état ne sont pas directement accessibles en dehors de la simulation (notez les nombres complexes dans la sortie ci-dessus). Pour être physiquement réaliste, vous devez spécifier une mesure, qui convertit un vecteur d'état en un nombre réel que les ordinateurs classiques peuvent comprendre. Cirq spécifie des mesures en utilisant des combinaisons des opérateurs Pauli $ \ hat {X} $, $ \ hat {Y} $ et $ \ hat {Z} $. À titre d'illustration, le code suivant mesure $ \ hat {Z} _0 $ et $ \ frac {1} {2} \ hat {Z} _0 + \ hat {X} _1 $ sur le vecteur d'état que vous venez de simuler:

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 Circuits quantiques comme tenseurs

TensorFlow Quantum (TFQ) fournit tfq.convert_to_tensor , une fonction qui convertit les objets Cirq en tenseurs. Cela vous permet d'envoyer des objets Cirq vers nos couchesquantiques et nosopérations quantiques . La fonction peut être appelée sur des listes ou des tableaux de Circuits Cirq et 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'>

Cela encode les objets Cirq en tant tf.string tenseurs tfq que les opérations tfq décodent selon les besoins.

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

1.3 Simulation du circuit de traitement par lots

TFQ fournit des méthodes pour calculer les valeurs d'attente, les échantillons et les vecteurs d'état. Pour l'instant, concentrons-nous sur les valeurs d'attente .

L'interface de plus haut niveau pour le calcul des valeurs d'attente est la couche tfq.layers.Expectation , qui est une tf.keras.Layer . Dans sa forme la plus simple, cette couche équivaut à simuler un circuit paramétré sur plusieurs cirq.ParamResolvers ; cependant, TFQ permet le traitement par lots suivant la sémantique de TensorFlow, et les circuits sont simulés à l'aide d'un code C ++ efficace.

Créer un lot de valeurs à substituer à notre a et b paramètres:

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

L'exécution de circuits par lots sur des valeurs de paramètres dans Cirq nécessite une boucle:

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.97633868]
 [ 0.68726736]
 [-0.67144978]
 [-0.92119527]
 [ 0.41925651]]

La même opération est simplifiée dans 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.9763384 ],
       [ 0.6872687 ],
       [-0.67144877],
       [-0.9211948 ],
       [ 0.41925824]], dtype=float32)>

2. Optimisation classique quantique hybride

Maintenant que vous avez vu les bases, utilisons TensorFlow Quantum pour construire un réseau neuronal hybride classique quantique . Vous allez entraîner un réseau neuronal classique pour contrôler un seul qubit. Le contrôle sera optimisé pour préparer correctement le qubit à l'état 0 ou 1 , en surmontant une erreur d'étalonnage systématique simulée. Cette figure montre l'architecture:

Même sans réseau de neurones, c'est un problème simple à résoudre, mais le thème est similaire aux vrais problèmes de contrôle quantique que vous pourriez résoudre en utilisant TFQ. Il montre un exemple de bout en bout d'un calcul quantique classique utilisant la tfq.layers.ControlledPQC (Parametrized Quantum Circuit) à l'intérieur d'un tf.keras.Model .

Pour la mise en œuvre de ce tutoriel, cette architecture est divisée en 3 parties:

  • Le circuit d'entrée ou circuit de point de données : les trois premières portes $ R $.
  • Le circuit contrôlé : les trois autres portes $ R $.
  • Le contrôleur : Le réseau neuronal classique définissant les paramètres du circuit contrôlé.

2.1 La définition du circuit contrôlé

Définissez une rotation de bit unique apprenable, comme indiqué dans la figure ci-dessus. Cela correspondra à notre circuit contrôlé.

# 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 Le contrôleur

Maintenant, définissez le réseau du contrôleur:

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

Étant donné un lot de commandes, le contrôleur émet un lot de signaux de commande pour le circuit contrôlé.

Le contrôleur est initialisé de manière aléatoire, donc ces sorties ne sont pas encore utiles.

controller(tf.constant([[0.0],[1.0]])).numpy()
array([[0.        , 0.        , 0.        ],
       [0.08031327, 0.03002123, 0.0143298 ]], dtype=float32)

2.3 Connecter le contrôleur au circuit

Utilisez tfq pour connecter le contrôleur au circuit contrôlé, comme un seul keras.Model .

Consultez le guide de l'API fonctionnelle Keras pour en savoir plus sur ce style de définition de modèle.

Définissez d'abord les entrées du modèle:

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

Appliquez ensuite des opérations à ces entrées pour définir le calcul.

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

tf.keras.Model maintenant ce calcul sous la forme d'un tf.keras.Model :

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

L'architecture du réseau est indiquée par le tracé du modèle ci-dessous. Comparez ce tracé de modèle au diagramme d'architecture pour vérifier l'exactitude.

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

png

Ce modèle prend deux entrées: les commandes du contrôleur et le circuit d'entrée dont le contrôleur tente de corriger la sortie.

2.4 L'ensemble de données

Le modèle tente de sortir la valeur de mesure correcte correcte de $ \ hat {Z} $ pour chaque commande. Les commandes et les valeurs correctes sont définies ci-dessous.

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

Il ne s'agit pas du jeu de données d'entraînement complet pour cette tâche. Chaque point de données de l'ensemble de données a également besoin d'un circuit d'entrée.

2.4 Définition du circuit d'entrée

Le circuit d'entrée ci-dessous définit l'erreur de calibrage aléatoire que le modèle apprendra à corriger.

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

Il existe deux copies du circuit, une pour chaque point de données.

datapoint_circuits.shape
TensorShape([2])

2.5 Formation

Avec les entrées définies, vous pouvez tester et exécuter le modèle tfq .

model([datapoint_circuits, commands]).numpy()
array([[-0.14540803],
       [-0.16774747]], dtype=float32)

Exécutez maintenant un processus d'entraînement standard pour ajuster ces valeurs vers les 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

À partir de ce graphique, vous pouvez voir que le réseau de neurones a appris à surmonter l'erreur de calibrage systématique.

2.6 Vérifier les sorties

Utilisez maintenant le modèle entraîné pour corriger les erreurs d'étalonnage des qubits. Avec 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: [-1.8619336 -1.523817  -3.2972674]
Which gives an actual expectation of: 0.9911799430847168

For a desired output (expectation) of [-1.] with noisy preparation, the controller
network found the following values for theta: [1.0268601  1.7161715  0.44205332]
Which gives an actual expectation of: -0.9540284872055054

La valeur de la fonction de perte pendant la formation donne une idée approximative de la qualité d'apprentissage du modèle. Plus la perte est faible, plus les valeurs d'espérance dans la cellule ci-dessus sont desired_values . Si vous n'êtes pas aussi concerné par les valeurs des paramètres, vous pouvez toujours vérifier les sorties ci-dessus en utilisant tfq :

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

3 Apprendre à préparer les états propres de différents opérateurs

Le choix des états propres $ \ pm \ hat {Z} $ correspondant à 1 et 0 était arbitraire. Vous auriez pu tout aussi bien vouloir que 1 corresponde à l'état propre $ + \ hat {Z} $ et 0 à l'état propre $ - \ hat {X} $. Une façon d'y parvenir consiste à spécifier un opérateur de mesure différent pour chaque commande, comme indiqué dans la figure ci-dessous:

Cela nécessite l'utilisation de tfq.layers.Expectation . Maintenant, votre entrée a augmenté pour inclure trois objets: circuit, commande et opérateur. La sortie est toujours la valeur attendue.

3.1 Nouvelle définition de modèle

Jetons un coup d'œil au modèle pour accomplir cette tâche:

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

Voici le réseau du contrôleur:

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

Combinez le circuit et le contrôleur en un seul keras.Model utilisant 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 L'ensemble de données

Vous allez maintenant inclure également les opérateurs que vous souhaitez mesurer pour chaque point de données que vous fournissez pour 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 Formation

Maintenant que vous avez vos nouvelles entrées et sorties, vous pouvez à nouveau vous entraîner à l'aide de 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 1ms/step - loss: 1.7510
Epoch 2/30
1/1 [==============================] - 0s 759us/step - loss: 0.8703
Epoch 3/30
1/1 [==============================] - 0s 1ms/step - loss: 0.3938
Epoch 4/30
1/1 [==============================] - 0s 749us/step - loss: 0.1695
Epoch 5/30
1/1 [==============================] - 0s 722us/step - loss: 0.0820
Epoch 6/30
1/1 [==============================] - 0s 685us/step - loss: 0.0609
Epoch 7/30
1/1 [==============================] - 0s 676us/step - loss: 0.0704
Epoch 8/30
1/1 [==============================] - 0s 734us/step - loss: 0.0826
Epoch 9/30
1/1 [==============================] - 0s 732us/step - loss: 0.0723
Epoch 10/30
1/1 [==============================] - 0s 907us/step - loss: 0.0431
Epoch 11/30
1/1 [==============================] - 0s 731us/step - loss: 0.0178
Epoch 12/30
1/1 [==============================] - 0s 859us/step - loss: 0.0064
Epoch 13/30
1/1 [==============================] - 0s 763us/step - loss: 0.0035
Epoch 14/30
1/1 [==============================] - 0s 684us/step - loss: 0.0031
Epoch 15/30
1/1 [==============================] - 0s 710us/step - loss: 0.0030
Epoch 16/30
1/1 [==============================] - 0s 671us/step - loss: 0.0038
Epoch 17/30
1/1 [==============================] - 0s 716us/step - loss: 0.0062
Epoch 18/30
1/1 [==============================] - 0s 707us/step - loss: 0.0095
Epoch 19/30
1/1 [==============================] - 0s 700us/step - loss: 0.0120
Epoch 20/30
1/1 [==============================] - 0s 703us/step - loss: 0.0117
Epoch 21/30
1/1 [==============================] - 0s 697us/step - loss: 0.0089
Epoch 22/30
1/1 [==============================] - 0s 717us/step - loss: 0.0053
Epoch 23/30
1/1 [==============================] - 0s 739us/step - loss: 0.0025
Epoch 24/30
1/1 [==============================] - 0s 725us/step - loss: 9.8351e-04
Epoch 25/30
1/1 [==============================] - 0s 709us/step - loss: 3.7235e-04
Epoch 26/30
1/1 [==============================] - 0s 660us/step - loss: 3.4534e-04
Epoch 27/30
1/1 [==============================] - 0s 744us/step - loss: 6.7898e-04
Epoch 28/30
1/1 [==============================] - 0s 740us/step - loss: 0.0013
Epoch 29/30
1/1 [==============================] - 0s 770us/step - loss: 0.0020
Epoch 30/30
1/1 [==============================] - 0s 756us/step - loss: 0.0028
plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()

png

La fonction de perte est tombée à zéro.

Le controller est disponible en tant que modèle autonome. Appelez le contrôleur et vérifiez sa réponse à chaque signal de commande. Il faudrait un certain travail pour comparer correctement ces sorties au contenu de random_rotations .

controller.predict(np.array([0,1]))
array([[ 1.741399  , -0.22516271,  0.7868666 ],
       [-0.31710118,  1.6385193 ,  1.8622308 ]], dtype=float32)