RSVP für Ihr lokales TensorFlow Everywhere-Event noch heute!
Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Hallo, viele Welten

Ansicht auf TensorFlow.org In Google Colab ausführen Quelle auf GitHub anzeigen Notizbuch herunterladen

Dieses Tutorial zeigt, wie ein klassisches neuronales Netzwerk lernen kann, Qubit-Kalibrierungsfehler zu korrigieren. Es stellt Cirq vor , ein Python-Framework zum Erstellen, Bearbeiten und Aufrufen von NISQ-Schaltkreisen (Noisy Intermediate Scale Quantum) und zeigt, wie Cirq mit TensorFlow Quantum zusammenarbeitet.

Installieren

pip install -q tensorflow==2.3.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

Bevor wir uns mit TensorFlow Quantum (TFQ) befassen, wollen wir uns einige Cirq- Grundlagen ansehen. Cirq ist eine Python-Bibliothek für Quantencomputer von Google. Sie verwenden es, um Schaltkreise zu definieren, einschließlich statischer und parametrisierter Gatter.

Cirq verwendet SymPy- Symbole, um 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

Zur Auswertung von Schaltkreisen können Sie die cirq.Simulator Schnittstelle verwenden. Sie ersetzen freie Parameter in einer Schaltung durch bestimmte Zahlen, indem Sie ein cirq.ParamResolver Objekt übergeben. Der folgende Code berechnet die Ausgabe des Rohzustandsvektors 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 soeben 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) bietet tfq.convert_to_tensor , eine Funktion, die Cirq-Objekte in Tensoren konvertiert. Auf diese Weise können Sie Cirq-Objekte an unsere Quantenschichten undQuantenoperationen senden. Die Funktion kann für 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'>

Dies codiert die Cirq-Objekte als tf.string Tensoren, die tfq Operationen nach Bedarf decodieren.

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

1.3 Batching-Schaltungssimulation

TFQ bietet Methoden zum Berechnen von Erwartungswerten, Abtastwerten und Zustandsvektoren. Konzentrieren wir uns zunächst auf die Erwartungswerte .

Die Schnittstelle auf höchster Ebene zum Berechnen von Erwartungswerten ist die tfq.layers.Expectation , eine tf.keras.Layer . In ihrer einfachsten Form entspricht diese Schicht der Simulation einer parametrisierten Schaltung über viele cirq.ParamResolvers ; TFQ ermöglicht jedoch das Stapeln nach der TensorFlow-Semantik, und Schaltkreise werden unter Verwendung von effizientem C ++ - Code simuliert.

Erstellen Sie eine Reihe von Werten, um unsere Parameter a und b zu ersetzen:

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

Die Ausführung der Stapelschaltung ü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.97633868]
 [ 0.68726736]
 [-0.67144978]
 [-0.92119527]
 [ 0.41925651]]

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.9763384 ],
       [ 0.6872687 ],
       [-0.67144877],
       [-0.9211948 ],
       [ 0.41925824]], dtype=float32)>

2. Hybride quantenklassische Optimierung

Nachdem Sie die Grundlagen kennengelernt haben, verwenden wir TensorFlow Quantum, um ein hybrides quantenklassisches neuronales Netz aufzubauen . Sie trainieren ein klassisches neuronales Netz, um ein einzelnes Qubit zu steuern. Die Steuerung wird optimiert, um das Qubit im Zustand 0 oder 1 korrekt vorzubereiten und einen simulierten systematischen Kalibrierungsfehler zu überwinden. Diese Abbildung zeigt die Architektur:

Selbst ohne ein neuronales Netzwerk ist dies ein unkompliziertes Problem, aber das Thema ähnelt den tatsächlichen Quantensteuerungsproblemen, die Sie möglicherweise mit TFQ lösen. Es zeigt ein End-to-End-Beispiel einer quantenklassischen Berechnung unter Verwendung der tfq.layers.ControlledPQC (Parametrized Quantum Circuit) innerhalb eines tf.keras.Model .

Für die Implementierung dieses Tutorials ist diese Architektur in drei Teile unterteilt:

  • Die Eingangsschaltung oder Datenpunktschaltung : Die ersten drei $ R $ -Gatter.
  • Die gesteuerte Schaltung : Die anderen drei $ R $ -Tore.
  • Die Steuerung : Das klassische neuronale Netzwerk, das die Parameter der gesteuerten Schaltung einstellt.

2.1 Die Regelkreisdefinition

Definieren Sie eine lernbare Einzelbitrotation, wie in der obigen Abbildung gezeigt. Dies entspricht unserer gesteuerten Schaltung.

# 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 Die Steuerung

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.08031327, 0.03002123, 0.0143298 ]], dtype=float32)

2.3 Schließen Sie den Controller an den Stromkreis an

Verwenden Sie tfq , um den Controller als einzelnes keras.Model an den gesteuerten Stromkreis keras.Model .

Weitere Informationen zu diesem Modelldefinitionsstil finden Sie im Keras Functional API-Handbuch .

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

tf.keras.Model diese Berechnung nun 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 die Darstellung des folgenden Modells 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 die Steuerung und die Eingangsschaltung, deren Ausgabe die Steuerung zu korrigieren versucht.

2.4 Der Datensatz

Das Modell versucht, für jeden Befehl den richtigen 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 der gesamte Trainingsdatensatz für diese Aufgabe. Jeder Datenpunkt im Datensatz benötigt auch eine Eingangsschaltung.

2.4 Definition der Eingangsschaltung

Die folgende 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 Schulung

Mit den definierten Eingaben können Sie das tfq Modell tfq .

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

Führen Sie nun einen Standardschulungsprozess durch, um diese Werte an die expected_outputs Ergebnisse anzupassen.

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 dieser Darstellung können Sie ersehen, dass das neuronale Netzwerk gelernt hat, die systematische Fehlkalibrierung zu überwinden.

2.6 Überprüfen Sie die Ausgänge

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: [-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


Der Wert der Verlustfunktion während des Trainings gibt eine ungefähre Vorstellung davon, wie gut das Modell lernt. Je geringer der Verlust ist, desired_values näher liegen die Erwartungswerte in der obigen Zelle an den desired_values . Wenn Sie sich nicht so sehr mit den Parameterwerten befassen, können Sie die Ausgaben von oben immer mit tfq :

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

3 Lernen, Eigenzustände verschiedener Operatoren vorzubereiten

Die Wahl der $ \ pm \ hat {Z} $ -Eigenzustände entsprechend 1 und 0 war willkürlich. Sie hätten genauso gut wollen können, dass 1 dem Eigenzustand $ + \ hat {Z} $ und 0 dem Eigenzustand $ - \ hat {X} $ entspricht. 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 Sie die Schaltung und den Controller zu einem einzigen keras.Model mit 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

Jetzt geben Sie auch die Operatoren an, die Sie für jeden Datenpunkt messen möchten, den Sie für 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 Schulung

Nachdem Sie 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 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

Die Verlustfunktion ist auf Null gefallen.

Der controller ist als eigenständiges Modell erhältlich. Rufen Sie den Controller an und überprüfen Sie die Reaktion auf jedes Befehlssignal. Es würde einige Arbeit random_rotations , um diese Ausgaben korrekt mit dem Inhalt von random_rotations .

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