Hallo, viele Welten

Auf TensorFlow.org ansehen Quelle auf GitHub anzeigen Notizbuch herunterladen

Dieses Tutorial zeigt, wie ein klassisches neuronales Netzwerk lernen kann, Qubit-Kalibrierungsfehler zu korrigieren. Es führt Cirq , eine Python - Framework zum Erstellen, Bearbeiten und rufen laut Intermediate Skala Quantum (NISQ) Schaltungen und zeigt , wie Cirq Schnittstellen mit TensorFlow Quantum.

Einrichten

pip install -q tensorflow==2.4.1

Installieren Sie TensorFlow Quantum:

pip install -q tensorflow-quantum

Importieren Sie nun TensorFlow und die Modulabhängigkeiten:

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. Die Grundlagen

1.1 Cirq und parametrisierte Quantenschaltungen

TensorFlow Quantum (TFQ), lassen Sie uns Blick auf einige Bevor wir Cirq Grundlagen. Cirq ist eine Python-Bibliothek für Quantencomputing von Google. Sie verwenden es, um Schaltungen zu definieren, einschließlich statischer und parametrisierter Tore.

Cirq verwendet SymPy Symbole freie Parameter darzustellen.

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

Der folgende Code erstellt mit Ihren Parametern eine Zwei-Qubit-Schaltung:

# 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

Um zu bewerten , Schaltungen, können Sie die Verwendung cirq.Simulator Schnittstelle. Sie ersetzen freie Parameter in einer Schaltung mit bestimmten Nummern von in einem vorübergehenden cirq.ParamResolver Objekt. Der folgende Code berechnet die Rohzustandsvektorausgabe Ihrer parametrisierten Schaltung:

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

Zustandsvektoren sind außerhalb der Simulation nicht direkt zugänglich (beachten Sie die komplexen Zahlen in der obigen Ausgabe). Um physikalisch realistisch zu sein, müssen Sie eine Messung angeben, die einen Zustandsvektor in eine reelle Zahl umwandelt, die klassische Computer verstehen können. Cirq Spezifiziert Messungen unter Verwendung von Kombinationen der Pauli Operatoren $ \ hat {X} $, $ \ hat {Y} $ und $ \ hat {Z} $. Zur Veranschaulichung misst der folgende Code $\hat{Z}_0$ und $\frac{1}{2}\hat{Z}_0 + \hat{X}_1$ auf dem gerade simulierten Zustandsvektor:

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 Quantenschaltungen als Tensoren

TensorFlow Quantum (TFQ) liefert tfq.convert_to_tensor , eine Funktion , die in wandelt Cirq Tensoren Objekte. Auf diese Weise können Sie Cirq Objekte zu unserer senden Quantenschichten und Quanten ops . Die Funktion kann auf Listen oder Arrays von Cirq Circuits und Cirq Paulis aufgerufen werden:

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

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

Dieser codiert die Cirq - Objekte als tf.string Tensoren dass tfq Operationen nach Bedarf zu dekodieren.

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

1.3 Simulation des Dosierkreislaufs

TFQ bietet Methoden zum Berechnen von Erwartungswerten, Abtastwerten und Zustandsvektoren. Denn jetzt, lassen Sie uns Fokus auf Erwartungswerte.

Die höchste Ebene Schnittstelle für die Berechnung der Werte Erwartung ist die tfq.layers.Expectation Schicht, die eine ist tf.keras.Layer . In seiner einfachsten Form ist diese Schicht äquivalent eine parametrisierte Schaltung über viele zu simulieren cirq.ParamResolvers ; TFQ ermöglicht jedoch die Stapelverarbeitung nach der TensorFlow-Semantik, und Schaltungen werden mit effizientem C++-Code simuliert.

Erstellen Sie eine Batch von Werten für unsere ersetzen a und b Parameter:

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

Das Batching der Schaltungsausführung über Parameterwerte in Cirq erfordert eine Schleife:

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.56709254]
 [ 0.06688178]
 [-0.95769906]
 [-0.46601528]
 [-0.98502201]]

Die gleiche Operation wird in TFQ vereinfacht:

tfq.layers.Expectation()(circuit,
                         symbol_names=[a, b],
                         symbol_values=batch_vals,
                         operators=z0)
<tf.Tensor: shape=(5, 1), dtype=float32, numpy=
array([[ 0.56709236],
       [ 0.06688362],
       [-0.9576994 ],
       [-0.46601394],
       [-0.98502195]], dtype=float32)>

2. Hybride quantenklassische Optimierung

Nun , da Sie die Grundlagen gesehen haben, wollen wir nutzen TensorFlow Quantum ein Hybrid - Quanten klassischen neuronalen Netz zu konstruieren. Sie trainieren ein klassisches neuronales Netz, um ein einzelnes Qubit zu steuern. Die Steuerung optimiert werden , um korrekt die Qubits in der Vorbereitung 0 oder 1 Zustand einen simulierten systematischen Kalibrierungsfehler zu überwinden. Diese Abbildung zeigt die Architektur:

Auch ohne ein neuronales Netzwerk ist dies ein einfach zu lösendes Problem, aber das Thema ähnelt den echten Quantenkontrollproblemen, die Sie mit TFQ lösen könnten. Es zeigt ein End-to-End - Beispiel einer quanten klassische Berechnung der Verwendung tfq.layers.ControlledPQC (Parametrisierte Quantum Circuit) Schicht innerhalb eines tf.keras.Model .

Für die Implementierung dieses Tutorials ist diese Architektur in 3 Teile aufgeteilt:

  • Die Eingangsschaltung oder Datenpunktschaltung: Die ersten drei $ R $ Gatter.
  • Die gesteuerte Schaltung: Die anderen drei $ R $ Tore.
  • Die Steuerung: Das klassische neuronalem Netz der Parameter des Regelkreises zu setzen.

2.1 Die Regelkreisdefinition

Definieren Sie eine erlernbare Einzelbit-Rotation, wie in der Abbildung oben gezeigt. Dies wird unserem kontrollierten Kreislauf entsprechen.

# 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 Der Verantwortliche

Definieren Sie nun das Controller-Netzwerk:

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

Bei einer Reihe von Befehlen gibt die Steuerung eine Reihe von Steuersignalen für die gesteuerte Schaltung aus.

Der Controller wird zufällig initialisiert, sodass diese Ausgänge noch nicht nützlich sind.

controller(tf.constant([[0.0],[1.0]])).numpy()
array([[ 0.       ,  0.       ,  0.       ],
       [-0.4901766,  0.9337233,  1.1685666]], dtype=float32)

2.3 Controller an den Stromkreis anschließen

Verwendung tfq die Steuerung an die gesteuerte Schaltung, als ein einziger verbinden keras.Model .

Siehe die Keras Functional API - Leitfaden für mehr über diese Art von Bestimmung des Modells.

Definieren Sie zuerst die Eingaben für das Modell:

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

Wenden Sie als nächstes Operationen auf diese Eingaben an, um die Berechnung zu definieren.

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

Nun verpacken diese Berechnung als tf.keras.Model :

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

Die Netzwerkarchitektur wird durch das Diagramm des Modells unten angezeigt. Vergleichen Sie dieses Modelldiagramm mit dem Architekturdiagramm, um die Richtigkeit zu überprüfen.

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

png

Dieses Modell benötigt zwei Eingaben: Die Befehle für den Controller und die Eingangsschaltung, deren Ausgabe der Controller zu korrigieren versucht.

2.4 Der Datensatz

Das Modell versucht, für jeden Befehl den korrekten korrekten Messwert von $\hat{Z}$ auszugeben. Die Befehle und korrekten Werte sind unten definiert.

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

Dies ist nicht das gesamte Trainings-Dataset für diese Aufgabe. Jeder Datenpunkt im Datensatz benötigt außerdem eine Eingangsschaltung.

2.4 Definition des Eingangskreises

Die unten stehende Eingangsschaltung definiert die zufällige Fehlkalibrierung, die das Modell zu korrigieren lernt.

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

Es gibt zwei Kopien der Schaltung, eine für jeden Datenpunkt.

datapoint_circuits.shape
TensorShape([2])

2.5 Ausbildung

Mit den Eingängen definiert sind, können Sie den Testlauf tfq Modell.

model([datapoint_circuits, commands]).numpy()
array([[0.13463798],
       [0.01099351]], dtype=float32)

Jetzt einen Standard - Trainingsprozess ausführen , um diese Werte zu dem anpassen 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

Aus diesem Diagramm können Sie sehen, dass das neuronale Netz gelernt hat, die systematische Fehlkalibrierung zu überwinden.

2.6 Ausgänge überprüfen

Verwenden Sie nun das trainierte Modell, um die Qubit-Kalibrierungsfehler zu korrigieren. Mit 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: [-0.01647221 -1.1232504   0.67117506]
Which gives an actual expectation of: 0.9762156009674072

For a desired output (expectation) of [-1.] with noisy preparation, the controller
network found the following values for theta: [-2.6371388  1.4017701  1.8121954]
Which gives an actual expectation of: -0.9950709342956543

Der Wert der Verlustfunktion während des Trainings gibt eine ungefähre Vorstellung davon, wie gut das Modell lernt. Je niedriger der Verlust ist , je näher die Erwartungswerte in der obigen Zelle desired_values . Wenn Sie nicht wie mit den Parameterwerten betroffen sind, können Sie immer die Ausgänge überprüfen von oben mit tfq :

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

3 Lernen, Eigenzustände verschiedener Operatoren vorzubereiten

Die Wahl der $\pm\hat{Z}$-Eigenzustände zu 1 und 0 war willkürlich. Genauso gut hätte man 1 dem Eigenzustand $+ \hat{Z}$ und 0 dem Eigenzustand $-\hat{X}$ entsprechen können. Eine Möglichkeit, dies zu erreichen, besteht darin, für jeden Befehl einen anderen Messoperator anzugeben, wie in der folgenden Abbildung dargestellt:

Dies erfordert die Verwendung von tfq.layers.Expectation . Jetzt ist Ihre Eingabe auf drei Objekte angewachsen: Schaltung, Befehl und Operator. Die Ausgabe ist immer noch der Erwartungswert.

3.1 Neue Modelldefinition

Schauen wir uns das Modell an, um diese Aufgabe zu erfüllen:

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

Hier ist das Controller-Netzwerk:

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

Kombinieren der Schaltung und der Steuereinheit in eine einzige keras.Model Verwendung 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 Der Datensatz

Nun werden Sie auch die Operatoren enthalten , die Sie wünschen , für jeden Datenpunkt messen Sie liefern 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 Ausbildung

Nachdem Sie nun Ihre neuen Ein- und Ausgänge haben, können Sie erneut mit Keras trainieren.

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 347ms/step - loss: 0.8254
Epoch 2/30
1/1 [==============================] - 0s 3ms/step - loss: 0.2200
Epoch 3/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0764
Epoch 4/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0471
Epoch 5/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0272
Epoch 6/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0128
Epoch 7/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0043
Epoch 8/30
1/1 [==============================] - 0s 2ms/step - loss: 9.2659e-04
Epoch 9/30
1/1 [==============================] - 0s 2ms/step - loss: 1.8865e-04
Epoch 10/30
1/1 [==============================] - 0s 2ms/step - loss: 5.8759e-04
Epoch 11/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0023
Epoch 12/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0059
Epoch 13/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0110
Epoch 14/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0151
Epoch 15/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0158
Epoch 16/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0130
Epoch 17/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0090
Epoch 18/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0055
Epoch 19/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0029
Epoch 20/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0014
Epoch 21/30
1/1 [==============================] - 0s 2ms/step - loss: 7.8246e-04
Epoch 22/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0011
Epoch 23/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0021
Epoch 24/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0030
Epoch 25/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0029
Epoch 26/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0021
Epoch 27/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0010
Epoch 28/30
1/1 [==============================] - 0s 2ms/step - loss: 3.8834e-04
Epoch 29/30
1/1 [==============================] - 0s 2ms/step - loss: 1.0240e-04
Epoch 30/30
1/1 [==============================] - 0s 2ms/step - loss: 1.7314e-05
plt.plot(history.history['loss'])
plt.title("Learning to Control a Qubit")
plt.xlabel("Iterations")
plt.ylabel("Error in Control")
plt.show()

png

Die Verlustfunktion ist auf Null gefallen.

Der controller ist als Stand-alone - Modell zur Verfügung. Rufen Sie den Controller an und überprüfen Sie seine Reaktion auf jedes Befehlssignal. Es würde einige Arbeit richtig diese Ausgänge mit den Inhalten vergleichen random_rotations .

controller.predict(np.array([0,1]))
array([[-0.86519706,  0.12104976, -0.7411108 ],
       [-0.21088992, -1.4411982 , -2.5981088 ]], dtype=float32)