Diese Seite wurde von der Cloud Translation API übersetzt.
Switch to English

Hallo, viele Welten

Ansicht auf TensorFlow.org Führen Sie in Google Colab aus 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.

Konfiguration

pip install -q tensorflow==2.1.0

Installieren Sie TensorFlow Quantum:

pip install -q tensorflow-quantum
ERROR: After October 2020 you may experience errors when installing or updating packages. This is because pip will change the way that it resolves dependency conflicts.

We recommend you use --use-feature=2020-resolver to test your packages with the new resolver before it becomes the default.

tensorflow-metadata 0.23.0 requires absl-py<0.9,>=0.7, but you'll have absl-py 0.9.0 which is incompatible.
google-api-core 1.22.1 requires protobuf>=3.12.0, but you'll have protobuf 3.8.0 which is incompatible.

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
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_wavefunction(output_state_vector, qubit_map).real
0.8775825500488281
z0x1 = 0.5 * z0 + cirq.X(q1)

z0x1.expectation_from_wavefunction(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 und Quantenoperationen 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 mit 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 = cirq_simulator.simulate(circuit, resolver).final_state
    cirq_results.append(
        [z0.expectation_from_wavefunction(final_state, {
            q0: 0,
            q1: 1
        }).real])

print('cirq batch results: \n {}'.format(np.array(cirq_results)))
cirq batch results: 
 [[-0.83266681]
 [ 0.99945712]
 [-0.93167216]
 [-0.0446538 ]
 [ 0.81493288]]

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.83266693],
       [ 0.9994572 ],
       [-0.9316722 ],
       [-0.04465387],
       [ 0.81493276]], 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 0 oder 1 Zustand 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 Definition der gesteuerten Schaltung

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.54516   , 0.22012806, 0.09697416]], 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 dieser Art der Modelldefinition 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.00428568],
       [-0.13666134]], dtype=float32)

Führen Sie nun einen Standardtrainingsprozess 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
    expectation = z0.expectation_from_wavefunction(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: {expectation}\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.6296288  1.1365356 -1.5962224]
Which gives an actual expectation of: 0.9621249437332153

For a desired output (expectation) of [-1.] with noisy preparation, the controller
network found the following values for theta: [1.0543389 1.18372   0.7693251]
Which gives an actual expectation of: -0.9606054425239563


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.9621245 ],
       [-0.96060544]], 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)
Train on 2 samples
Epoch 1/30
2/2 [==============================] - 1s 311ms/sample - loss: 1.0348
Epoch 2/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.6338
Epoch 3/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.4026
Epoch 4/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.3120
Epoch 5/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.2221
Epoch 6/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.1057
Epoch 7/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0341
Epoch 8/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0091
Epoch 9/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0020
Epoch 10/30
2/2 [==============================] - 0s 2ms/sample - loss: 6.2119e-04
Epoch 11/30
2/2 [==============================] - 0s 2ms/sample - loss: 0.0038
Epoch 12/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0114
Epoch 13/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0174
Epoch 14/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0158
Epoch 15/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0115
Epoch 16/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0119
Epoch 17/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0179
Epoch 18/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0245
Epoch 19/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0272
Epoch 20/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0247
Epoch 21/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0189
Epoch 22/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0125
Epoch 23/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0076
Epoch 24/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0047
Epoch 25/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0033
Epoch 26/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0025
Epoch 27/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0019
Epoch 28/30
2/2 [==============================] - 0s 1ms/sample - loss: 0.0013
Epoch 29/30
2/2 [==============================] - 0s 1ms/sample - loss: 8.3354e-04
Epoch 30/30
2/2 [==============================] - 0s 1ms/sample - loss: 5.6316e-04

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 , diese Ausgaben korrekt mit dem Inhalt von random_rotations .

controller.predict(np.array([0,1]))
array([[ 1.9317125 , -0.03036583, -0.46449944],
       [ 2.1391845 ,  1.4074974 , -0.42245713]], dtype=float32)