שלום, עולמות רבים

הצג באתר TensorFlow.org הפעל בגוגל קולאב צפה במקור ב-GitHub הורד מחברת

מדריך זה מראה כיצד רשת נוירונים קלאסית יכולה ללמוד לתקן שגיאות כיול קוויביט. הוא מציג את 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 עבור מחשוב קוונטי מגוגל. אתה משתמש בו כדי להגדיר מעגלים, כולל שערים סטטיים ופרמטרים.

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 מציין מדידות באמצעות שילובים של האופרטורים Pauli \(\hat{X}\), \(\hat{Y}\)ו- \(\hat{Z}\). כהמחשה, הקוד הבא מודד את \(\hat{Z}_0\) ו- \(\frac{1}{2}\hat{Z}_0 + \hat{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'>

זה מקודד את אובייקטי tf.string tfq שפעולות 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 (Parametrized Quantum Circuit) בתוך שכבת 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 יחיד.

עיין במדריך Keras Functional API למידע נוסף על סגנון זה של הגדרת המודל.

תחילה הגדר את התשומות למודל:

# 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 מערך הנתונים

המודל מנסה להוציא את ערך המדידה הנכון של \(\hat{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 אמת פלטים

כעת השתמש במודל המאומן, כדי לתקן את שגיאות כיול הקיוביט. עם 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.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 לימוד הכנת מצבים עצמיים של אופרטורים שונים

הבחירה של המצבים העצמיים \(\pm \hat{Z}\) המקבילים ל-1 ו-0 הייתה שרירותית. באותה מידה היית יכול לרצות ש-1 יתאים למצב \(+ \hat{Z}\) ו-0 יתאים למצב \(-\hat{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)