Здравствуй, много миров

В этом руководстве показано, как классическая нейронная сеть может научиться исправлять ошибки калибровки кубитов. Он представляет Cirq , платформу Python для создания, редактирования и вызова схем Noisy Intermediate Scale Quantum (NISQ), и демонстрирует, как Cirq взаимодействует с TensorFlow Quantum.

Настраивать

pip install tensorflow==2.7.0

Установите TensorFlow Quantum:

pip install tensorflow-quantum
# Update package resources to account for version changes.
import importlib, pkg_resources
importlib
.reload(pkg_resources)
<module 'pkg_resources' from '/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/pkg_resources/__init__.py'>

Теперь импортируйте TensorFlow и зависимости модуля:

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
2022-02-04 12:27:31.677071: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected

1. Основы

1.1 Cirq и параметризованные квантовые схемы

Прежде чем приступить к изучению TensorFlow Quantum (TFQ), давайте рассмотрим некоторые основы Cirq . Cirq — это библиотека Python для квантовых вычислений от Google. Вы используете его для определения цепей, включая статические и параметризованные вентили.

Cirq использует символы SymPy для представления свободных параметров.

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

Следующий код создает схему из двух кубитов, используя ваши параметры:

# 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

Для оценки цепей можно использовать интерфейс cirq.Simulator . Вы заменяете свободные параметры в цепи определенными номерами, передавая объект cirq.ParamResolver . Следующий код вычисляет исходный вектор состояния вашей параметризованной схемы:

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

Векторы состояния не доступны напрямую вне симуляции (обратите внимание на комплексные числа в выходных данных выше). Чтобы быть физически реалистичным, вы должны указать измерение, которое преобразует вектор состояния в действительное число, понятное классическим компьютерам. Cirq задает измерения, используя комбинации операторов Паули X^, Y^и Z^. В качестве иллюстрации следующий код измеряет Z^0 и 12Z^0+X^1 в только что смоделированном векторе состояния:

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. Квантовые схемы как тензоры

TensorFlow Quantum (TFQ) предоставляет tfq.convert_to_tensor функцию, которая преобразует объекты Cirq в тензоры. Это позволяет отправлять объекты Cirq в наши квантовые слои и квантовые операции . Функцию можно вызывать для списков или массивов Cirq Circuits и 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'>

Это кодирует объекты Cirq как тензоры tf.string , которые операции tfq декодируют по мере необходимости.

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

1.3 Моделирование схемы дозирования

TFQ предоставляет методы для вычисления значений ожидания, выборок и векторов состояния. А пока давайте сосредоточимся на ожидаемых значениях .

Интерфейс самого высокого уровня для вычисления ожидаемых значений — слой tfq.layers.Expectation , который представляет собой tf.keras.Layer . В своей простейшей форме этот уровень эквивалентен моделированию параметризованной схемы с множеством cirq.ParamResolvers ; однако TFQ позволяет выполнять пакетную обработку в соответствии с семантикой TensorFlow, а схемы моделируются с использованием эффективного кода C++.

Создайте пакет значений для замены наших параметров a и b :

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

Пакетное выполнение схемы по значениям параметров в Cirq требует цикла:

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.66652703]
 [ 0.49764055]
 [ 0.67326665]
 [-0.95549959]
 [-0.81297827]]

Эта же операция упрощена в 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.666526  ],
       [ 0.49764216],
       [ 0.6732664 ],
       [-0.9554999 ],
       [-0.8129788 ]], dtype=float32)>

2. Гибридная квантово-классическая оптимизация

Теперь, когда вы ознакомились с основами, давайте воспользуемся TensorFlow Quantum для построения гибридной квантово-классической нейронной сети . Вы будете обучать классическую нейронную сеть управлять одним кубитом. Управление будет оптимизировано для правильной подготовки кубита в состоянии 0 или 1 , преодолевая смоделированную систематическую ошибку калибровки. На этом рисунке показана архитектура:

Даже без нейронной сети решить эту задачу несложно, но тема похожа на реальные проблемы квантового управления, которые вы можете решить с помощью TFQ. Он демонстрирует сквозной пример квантово-классического вычисления с использованием tfq.layers.ControlledPQC (параметризованная квантовая схема) внутри tf.keras.Model .

Для реализации этого руководства эта архитектура разделена на 3 части:

  • Входная схема или схема точки данных: первые три R .
  • Управляемая схема : остальные три R .
  • Контроллер : классическая нейросеть, задающая параметры управляемой цепи.

2.1 Определение управляемой цепи

Определите обучаемое вращение одного бита, как показано на рисунке выше. Это будет соответствовать нашей управляемой схеме.

# 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 Контроллер

Теперь определите сеть контроллера:

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

Получив пакет команд, контроллер выдает пакет управляющих сигналов для управляемой схемы.

Контроллер инициализируется случайным образом, поэтому эти выходы пока бесполезны.

controller(tf.constant([[0.0],[1.0]])).numpy()
array([[0.        , 0.        , 0.        ],
       [0.5815686 , 0.21376055, 0.57181627]], dtype=float32)

2.3 Подключите контроллер к цепи

Используйте tfq для подключения контроллера к управляемой схеме, как одиночный keras.Model .

Дополнительные сведения об этом стиле определения модели см. в руководстве по функциональному API Keras .

Сначала определите входные данные для модели:

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

Затем примените операции к этим входам, чтобы определить вычисление.

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 :

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

Архитектура сети указана на графике модели ниже. Сравните этот график модели с архитектурной диаграммой, чтобы проверить правильность.

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

png

Эта модель принимает два входа: команды для контроллера и входную схему, выход которой контроллер пытается исправить.

2.4 Набор данных

Модель пытается вывести правильное значение измерения Z^ для каждой команды. Команды и правильные значения определены ниже.

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

Это не весь обучающий набор данных для этой задачи. Каждая точка данных в наборе данных также нуждается во входной цепи.

2.4 Определение входной цепи

Входная схема ниже определяет случайную неточность, которую модель научится исправлять.

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

Есть две копии схемы, по одной для каждой точки данных.

datapoint_circuits.shape
TensorShape([2])

2.5 Обучение

Определив входные данные, вы можете протестировать модель tfq .

model([datapoint_circuits, commands]).numpy()
array([[0.95853525],
       [0.6272128 ]], dtype=float32)

Теперь запустите стандартный процесс обучения, чтобы настроить эти значения в соответствии с 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

Из этого графика видно, что нейронная сеть научилась преодолевать систематическую раскалибровку.

2.6 Проверка выходных данных

Теперь используйте обученную модель, чтобы исправить ошибки калибровки кубита. С Цирком:

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.6788422   0.3395225  -0.59394693]
Which gives an actual expectation of: 0.9171845316886902

For a desired output (expectation) of [-1.] with noisy preparation, the controller
network found the following values for theta: [-5.203663   -0.29528576  3.2887425 ]
Which gives an actual expectation of: -0.9511058330535889

Значение функции потерь во время обучения дает приблизительное представление о том, насколько хорошо обучается модель. Чем ниже потери, тем ближе ожидаемые значения в приведенной выше ячейке к desired_values . Если вас не интересуют значения параметров, вы всегда можете проверить выходные данные сверху, используя tfq :

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

3 Учимся готовить собственные состояния разных операторов

Выбор собственных состояний ±Z^ , соответствующих 1 и 0, был произвольным. С тем же успехом вы могли бы захотеть, чтобы 1 соответствовало собственному состоянию +Z^ а 0 — собственному состоянию X^ . Один из способов добиться этого — указать разные операторы измерения для каждой команды, как показано на рисунке ниже:

Для этого необходимо использовать tfq.layers.Expectation . Теперь ваш ввод расширился до трех объектов: схема, команда и оператор. Результат по-прежнему является ожидаемым значением.

3.1 Новое определение модели

Давайте посмотрим на модель для выполнения этой задачи:

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

Вот сеть контроллера:

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

Объедините схему и контроллер в один keras.Model с помощью 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 Набор данных

Теперь вы также включите операторы, которые вы хотите измерить для каждой точки данных, которую вы предоставляете для 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 Обучение

Теперь, когда у вас есть новые входные и выходные данные, вы можете снова тренироваться с помощью 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 320ms/step - loss: 2.4404
Epoch 2/30
1/1 [==============================] - 0s 3ms/step - loss: 1.8713
Epoch 3/30
1/1 [==============================] - 0s 3ms/step - loss: 1.1400
Epoch 4/30
1/1 [==============================] - 0s 3ms/step - loss: 0.5071
Epoch 5/30
1/1 [==============================] - 0s 3ms/step - loss: 0.1611
Epoch 6/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0426
Epoch 7/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0117
Epoch 8/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0032
Epoch 9/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0147
Epoch 10/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0452
Epoch 11/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0670
Epoch 12/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0648
Epoch 13/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0471
Epoch 14/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0289
Epoch 15/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0180
Epoch 16/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0138
Epoch 17/30
1/1 [==============================] - 0s 2ms/step - loss: 0.0130
Epoch 18/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0137
Epoch 19/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0148
Epoch 20/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0156
Epoch 21/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0157
Epoch 22/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0149
Epoch 23/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0135
Epoch 24/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0119
Epoch 25/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0100
Epoch 26/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0082
Epoch 27/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0064
Epoch 28/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0047
Epoch 29/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0034
Epoch 30/30
1/1 [==============================] - 0s 3ms/step - loss: 0.0024
plt.plot(history.history['loss'])
plt
.title("Learning to Control a Qubit")
plt
.xlabel("Iterations")
plt
.ylabel("Error in Control")
plt
.show()

png

Функция потерь упала до нуля.

controller доступен как автономная модель. Вызовите контроллер и проверьте его реакцию на каждый командный сигнал. Потребуется некоторая работа, чтобы правильно сравнить эти выходные данные с содержимым random_rotations .

controller.predict(np.array([0,1]))
array([[3.6335812 , 1.8470774 , 0.71675825],
       [5.3085413 , 0.08116499, 2.8337662 ]], dtype=float32)