Ta strona została przetłumaczona przez Cloud Translation API.
Switch to English

Witam, wiele światów

Zobacz na TensorFlow.org Uruchom w Google Colab Wyświetl źródło na GitHub Pobierz notatnik

Ten samouczek pokazuje, jak klasyczna sieć neuronowa może nauczyć się poprawiać błędy kalibracji kubitów. Przedstawia Cirq , platformę Python do tworzenia, edytowania i wywoływania obwodów kwantowych o dużej skali pośredniej (NISQ) i demonstruje, jak Cirq łączy się z TensorFlow Quantum.

Ustawiać

pip install -q tensorflow==2.1.0

Zainstaluj 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.

Teraz zaimportuj TensorFlow i zależności modułów:

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. Podstawy

1.1 Obwody kwantowe i sparametryzowane obwody kwantowe

Przed zapoznaniem się z TensorFlow Quantum (TFQ) przyjrzyjmy się niektórym podstawom Cirq . Cirq to biblioteka Pythona firmy Google do obliczeń kwantowych. Używasz go do definiowania obwodów, w tym bramek statycznych i sparametryzowanych.

Cirq używa symboli SymPy do reprezentowania dowolnych parametrów.

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

Poniższy kod tworzy obwód z dwoma kubitami przy użyciu parametrów:

# 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

Aby ocenić obwody, możesz użyć interfejsu cirq.Simulator . cirq.ParamResolver parametry w obwodzie zastępujesz określonymi liczbami, przekazując obiekt cirq.ParamResolver . Poniższy kod oblicza wyjściowy wektor stanu surowego sparametryzowanego obwodu:

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

Wektory stanu nie są bezpośrednio dostępne poza symulacją (zwróć uwagę na liczby zespolone w powyższym wyniku). Aby być realistycznym fizycznie, musisz określić pomiar, który przekształca wektor stanu na liczbę rzeczywistą, którą mogą zrozumieć klasyczne komputery. Cirq określa pomiary za pomocą kombinacji operatorów Pauliego $ \ hat {X} $, $ \ hat {Y} $ i $ \ hat {Z} $. Przykładowo, poniższy kod mierzy $ \ hat {Z} _0 $ i $ \ frac {1} {2} \ hat {Z} _0 + \ hat {X} _1 $ na zasymulowanym wektorze stanu:

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 Obwody kwantowe jako tensory

TensorFlow Quantum (TFQ) zapewnia tfq.convert_to_tensor , funkcję, która przekształca obiekty Cirq w tensory. Pozwala to na wysyłanie obiektów Cirq do naszych warstw kwantowych i operacji kwantowych . Funkcję można wywołać z list lub tablic Cirq Circuits i 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'>

Ten koduje Cirq obiektów jak tf.string tensory że tfq operacje Decode, ile potrzeba.

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

1.3 Symulacja obwodu dozowania

TFQ zapewnia metody obliczania wartości oczekiwanych, próbek i wektorów stanu. Na razie skupmy się na wartościach oczekiwanych .

Interfejs najwyższego poziomu do obliczania wartości oczekiwanych to warstwa tfq.layers.Expectation , która jest tf.keras.Layer . W najprostszej postaci ta warstwa jest równoważna symulacji sparametryzowanego obwodu w wielu cirq.ParamResolvers ; jednak TFQ pozwala na grupowanie zgodnie z semantyką TensorFlow, a obwody są symulowane przy użyciu wydajnego kodu C ++.

Utwórz zbiór wartości, które zastąpią nasze parametry a i b :

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

Dozowanie obwodu ponad wartościami parametrów w Cirq wymaga pętli:

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

Ta sama operacja jest uproszczona w 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.83266693],
       [ 0.9994572 ],
       [-0.9316722 ],
       [-0.04465387],
       [ 0.81493276]], dtype=float32)>

2. Hybrydowa optymalizacja kwantowo-klasyczna

Teraz, gdy znasz już podstawy, użyjmy TensorFlow Quantum do skonstruowania hybrydowej kwantowo-klasycznej sieci neuronowej . Nauczysz klasyczną sieć neuronową do sterowania pojedynczym kubitem. Sterowanie zostanie zoptymalizowane, aby poprawnie przygotować kubit w stanie 0 lub 1 , przezwyciężając symulowany systematyczny błąd kalibracji. Ten rysunek przedstawia architekturę:

Nawet bez sieci neuronowej jest to prosty problem do rozwiązania, ale temat jest podobny do rzeczywistych problemów kontroli kwantowej, które można rozwiązać za pomocą TFQ. Przedstawia kompleksowy przykład kwantowo-klasycznego obliczenia przy użyciu tfq.layers.ControlledPQC (parametryczny obwód kwantowy) wewnątrz tf.keras.Model .

W celu wykonania tego samouczka ta architektura została podzielona na 3 części:

  • Obwód wejściowy lub obwód punktu danych : pierwsze trzy bramki $ R $.
  • Kontrolowany obwód : pozostałe trzy bramki $ R $.
  • Sterownik : Klasyczna sieć neuronowa ustawiająca parametry sterowanego obwodu.

2.1 Definicja obwodu sterowanego

Zdefiniuj możliwy do nauczenia obrót pojedynczego bitu, jak pokazano na powyższym rysunku. Będzie to odpowiadać naszemu kontrolowanemu obwodowi.

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

Teraz zdefiniuj sieć kontrolerów:

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

Po wydaniu serii poleceń sterownik wyprowadza pakiet sygnałów sterujących dla sterowanego obwodu.

Sterownik jest inicjalizowany losowo, więc te wyjścia nie są jeszcze przydatne.

controller(tf.constant([[0.0],[1.0]])).numpy()
array([[0.        , 0.        , 0.        ],
       [0.54516   , 0.22012806, 0.09697416]], dtype=float32)

2.3 Podłączyć sterownik do obwodu

Za pomocą tfq podłącz kontroler do kontrolowanego obwodu, jako pojedynczy keras.Model .

Więcej informacji na temat tego stylu definiowania modelu można znaleźć w przewodniku po funkcjonalnym interfejsie API Keras .

Najpierw zdefiniuj dane wejściowe do modelu:

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

Następnie zastosuj operacje do tych danych wejściowych, aby zdefiniować obliczenia.

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

Teraz tf.keras.Model to obliczenie jako tf.keras.Model :

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

Architekturę sieci wskazuje wykres poniżej. Porównaj ten wykres modelowy z diagramem architektury, aby zweryfikować poprawność.

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

png

Ten model ma dwa wejścia: polecenia dla sterownika i obwód wejściowy, którego wyjście sterownik próbuje poprawić.

2.4 Zbiór danych

Model próbuje wyprowadzić poprawną wartość pomiaru $ \ hat {Z} $ dla każdego polecenia. Polecenia i prawidłowe wartości są zdefiniowane poniżej.

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

To nie jest cały zestaw danych szkoleniowych dla tego zadania. Każdy punkt danych w zbiorze danych również wymaga obwodu wejściowego.

2.4 Definicja obwodu wejściowego

Poniższy obwód wejściowy definiuje przypadkową błędną kalibrację, którą model nauczy się korygować.

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

Istnieją dwie kopie obwodu, po jednej dla każdego punktu danych.

datapoint_circuits.shape
TensorShape([2])

2.5 Szkolenie

Po zdefiniowaniu danych wejściowych można przetestować model tfq .

model([datapoint_circuits, commands]).numpy()
array([[ 0.00428568],
       [-0.13666134]], dtype=float32)

Teraz uruchom standardowy proces szkoleniowy, aby dostosować te wartości do 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

Z tego wykresu widać, że sieć neuronowa nauczyła się przezwyciężać systematyczne błędy kalibracji.

2.6 Weryfikacja wyjść

Teraz użyj wyszkolonego modelu, aby poprawić błędy kalibracji kubitu. Z 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


Wartość funkcji straty podczas treningu daje ogólne pojęcie o tym, jak dobrze model się uczy. Im niższa strata, tym wartości oczekiwane w powyższej komórce są desired_values . Jeśli nie przejmujesz się wartościami parametrów, zawsze możesz sprawdzić dane wyjściowe z góry za pomocą tfq :

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

3 Nauka przygotowywania stanów własnych różnych operatorów

Wybór stanów własnych $ \ pm \ hat {Z} $ odpowiadających 1 i 0 był arbitralny. Równie dobrze mógłbyś chcieć, żeby 1 odpowiadało $ + \ hat {Z} $ własnemu stanowi, a 0 odpowiadało $ - \ hat {X} $ eigenstate. Jednym ze sposobów osiągnięcia tego jest określenie innego operatora pomiaru dla każdego polecenia, jak pokazano na poniższym rysunku:

Wymaga to użycia tfq.layers.Expectation . Teraz twoje dane wejściowe urosły i obejmują trzy obiekty: obwód, polecenie i operator. Wynik jest nadal wartością oczekiwaną.

3.1 Nowa definicja modelu

Przyjrzyjmy się modelowi, aby wykonać to zadanie:

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

Oto sieć kontrolerów:

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

Połącz obwód i kontroler w jeden keras.Model za pomocą 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 Zbiór danych

Teraz dodasz również operatory, które chcesz mierzyć dla każdego punktu danych, który model_circuit dla 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 Szkolenie

Teraz, gdy masz nowe wejścia i wyjścia, możesz ponownie trenować za pomocą 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)
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

Funkcja straty spadła do zera.

controller jest dostępny jako samodzielny model. Zadzwoń do kontrolera i sprawdź jego odpowiedź na każdy sygnał polecenia. random_rotations porównanie tych wyników z zawartością random_rotations wymagałoby trochę pracy.

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