Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/quantum/tutorials/quantum_data.ipynb
25118 views
Kernel: Python 3
#@title Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License.

Datos cuánticos

Partiendo de las comparaciones hechas en el tutorial sobre MNIST, se explora el trabajo reciente de Huang et al. que muestra de qué manera los diferentes conjuntos de datos afectan a las comparaciones sobre el desempeño. En el trabajo, los autores procuran entender cómo y cuándo los modelos de aprendizaje automático pueden aprender igual o mejor que los modelos cuánticos. El trabajo también presenta, a través de un conjunto de datos minuciosamente elaborado, una separación empírica del desempeño entre el modelo de aprendizaje automático clásico y el cuántico. Haremos lo siguiente:

  1. Preparar una dimensión reducida del conjunto de datos Fashion-MNIST.

  2. Usar circuitos cuánticos para volver a etiquetar el conjunto de datos y calcular las características del núcleo cuántico proyectado (PQK).

  3. Entrenar una red neuronal clásica con el conjunto de datos reetiquetado y comparar el desempeño con un modelo que tiene acceso a las características PQK.

Preparación

!pip install tensorflow==2.7.0 tensorflow-quantum==0.7.2
WARNING: pip is being invoked by an old script wrapper. This will fail in a future version of pip. Please see https://github.com/pypa/pip/issues/5599 for advice on fixing the underlying issue. To avoid this problem you can invoke Python with '-m pip' instead of running pip directly.
# Update package resources to account for version changes. import importlib, pkg_resources importlib.reload(pkg_resources)
import cirq import sympy import numpy as np import tensorflow as tf import tensorflow_quantum as tfq # visualization tools %matplotlib inline import matplotlib.pyplot as plt from cirq.contrib.svg import SVGCircuit np.random.seed(1234)

1. Preparación de los datos

Empezaremos por preparar el conjunto de datos fashion-MNIST para ejecutarlo en una computadora cuántica.

1.1 Descarga de fashion-MNIST

El primer paso es obtener el conjunto de datos fashion-mnist tradicional. Lo podemos hacer utilizando el módulo tf.keras.datasets.

(x_train, y_train), (x_test, y_test) = tf.keras.datasets.fashion_mnist.load_data() # Rescale the images from [0,255] to the [0.0,1.0] range. x_train, x_test = x_train/255.0, x_test/255.0 print("Number of original training examples:", len(x_train)) print("Number of original test examples:", len(x_test))
Number of original training examples: 60000 Number of original test examples: 10000

Filtramos el conjunto de datos para conservar solamente los tops, las camisetas y los vestidos, y eliminar las otras clases. Al mismo tiempo, convertimos la etiqueta y al booleano: verdadero para 0 and falso para 3.

def filter_03(x, y): keep = (y == 0) | (y == 3) x, y = x[keep], y[keep] y = y == 0 return x,y
x_train, y_train = filter_03(x_train, y_train) x_test, y_test = filter_03(x_test, y_test) print("Number of filtered training examples:", len(x_train)) print("Number of filtered test examples:", len(x_test))
Number of filtered training examples: 12000 Number of filtered test examples: 2000
print(y_train[0]) plt.imshow(x_train[0, :, :]) plt.colorbar()
True
<matplotlib.colorbar.Colorbar at 0x7f6db42c3460>
Image in a Jupyter notebook

1.2 Reducción de las imágenes a escala

Al igual que con el ejemplo de MNIST, deberá reducir a escala estas imágenes para estar dentro de los límites admitidos para las computadoras cuánticas actuales. Esta vez, sin embargo, usará una transformación PCA para reducir las dimensiones, en vez de usar una operación tf.image.resize.

def truncate_x(x_train, x_test, n_components=10): """Perform PCA on image dataset keeping the top `n_components` components.""" n_points_train = tf.gather(tf.shape(x_train), 0) n_points_test = tf.gather(tf.shape(x_test), 0) # Flatten to 1D x_train = tf.reshape(x_train, [n_points_train, -1]) x_test = tf.reshape(x_test, [n_points_test, -1]) # Normalize. feature_mean = tf.reduce_mean(x_train, axis=0) x_train_normalized = x_train - feature_mean x_test_normalized = x_test - feature_mean # Truncate. e_values, e_vectors = tf.linalg.eigh( tf.einsum('ji,jk->ik', x_train_normalized, x_train_normalized)) return tf.einsum('ij,jk->ik', x_train_normalized, e_vectors[:,-n_components:]), \ tf.einsum('ij,jk->ik', x_test_normalized, e_vectors[:, -n_components:])
DATASET_DIM = 10 x_train, x_test = truncate_x(x_train, x_test, n_components=DATASET_DIM) print(f'New datapoint dimension:', len(x_train[0]))
New datapoint dimension: 10

El último paso consiste en reducir el tamaño del conjunto de datos a solamente 1000 puntos de datos de entrenamiento y 200 puntos de datos de prueba.

N_TRAIN = 1000 N_TEST = 200 x_train, x_test = x_train[:N_TRAIN], x_test[:N_TEST] y_train, y_test = y_train[:N_TRAIN], y_test[:N_TEST]
print("New number of training examples:", len(x_train)) print("New number of test examples:", len(x_test))
New number of training examples: 1000 New number of test examples: 200

2. Reetiquetado y cálculo de las características del PQK

Ahora, prepararemos un conjunto de datos cuánticos "artificiales" mediante la incorporación de componentes cuánticos y el reetiquetado del conjunto de datos fashion-MNIST modificado que hemos creado arriba. Para obtener la mayor separación entre los métodos clásico y cuántico, primero, prepararemos las características de PQK y luego, reetiquetaremos las salidas según sus valores.

2.1 Codificación cuántica y características de PQK

Crearemos un conjunto nuevo de características, basado en x_train, y_train, x_test y y_test que está definido para ser el 1-RDM en todos los bits cuánticos de lo siguiente:

V(xtrain/ntrotter)ntrotterU1qb0V(x_{\text{train}} / n_{\text{trotter}}) ^ {n_{\text{trotter}}} U_{\text{1qb}} | 0 \rangle

Donde U1qbU_\text{1qb} es una pared de rotaciones de un solo bit cuántico y V(θ^)=eiiθi^(XiXi+1+YiYi+1+ZiZi+1)V(\hat{\theta}) = e^{-i\sum_i \hat{\theta_i} (X_i X_{i+1} + Y_i Y_{i+1} + Z_i Z_{i+1})}

Primero, podemos generar la pared de rotaciones de un solo bit cuántico:

def single_qubit_wall(qubits, rotations): """Prepare a single qubit X,Y,Z rotation wall on `qubits`.""" wall_circuit = cirq.Circuit() for i, qubit in enumerate(qubits): for j, gate in enumerate([cirq.X, cirq.Y, cirq.Z]): wall_circuit.append(gate(qubit) ** rotations[i][j]) return wall_circuit

Se puede verificar rápidamente observando el circuito:

SVGCircuit(single_qubit_wall( cirq.GridQubit.rect(1,4), np.random.uniform(size=(4, 3))))
Image in a Jupyter notebook

A continuación, podemos preparar V(θ^)V(\hat{\theta}) con la ayuda de tfq.util.exponential que puede elevar exponencialmente cualquier objeto cirq.PauliSum que conmute:

def v_theta(qubits): """Prepares a circuit that generates V(\theta).""" ref_paulis = [ cirq.X(q0) * cirq.X(q1) + \ cirq.Y(q0) * cirq.Y(q1) + \ cirq.Z(q0) * cirq.Z(q1) for q0, q1 in zip(qubits, qubits[1:]) ] exp_symbols = list(sympy.symbols('ref_0:'+str(len(ref_paulis)))) return tfq.util.exponential(ref_paulis, exp_symbols), exp_symbols

Este circuito podría ser un poco complicado de verificar solamente con la observación, pero aún es posible examinar un caso de dos bits cuánticos para ver qué está sucediendo:

test_circuit, test_symbols = v_theta(cirq.GridQubit.rect(1, 2)) print(f'Symbols found in circuit:{test_symbols}') SVGCircuit(test_circuit)
Symbols found in circuit:[ref_0]
Image in a Jupyter notebook

Ahora que tenemos todas las piezas del rompecabezas, debemos reunir a todos los circuitos de codificación completos:

def prepare_pqk_circuits(qubits, classical_source, n_trotter=10): """Prepare the pqk feature circuits around a dataset.""" n_qubits = len(qubits) n_points = len(classical_source) # Prepare random single qubit rotation wall. random_rots = np.random.uniform(-2, 2, size=(n_qubits, 3)) initial_U = single_qubit_wall(qubits, random_rots) # Prepare parametrized V V_circuit, symbols = v_theta(qubits) exp_circuit = cirq.Circuit(V_circuit for t in range(n_trotter)) # Convert to `tf.Tensor` initial_U_tensor = tfq.convert_to_tensor([initial_U]) initial_U_splat = tf.tile(initial_U_tensor, [n_points]) full_circuits = tfq.layers.AddCircuit()( initial_U_splat, append=exp_circuit) # Replace placeholders in circuits with values from `classical_source`. return tfq.resolve_parameters( full_circuits, tf.convert_to_tensor([str(x) for x in symbols]), tf.convert_to_tensor(classical_source*(n_qubits/3)/n_trotter))

Elijamos algunos bits cuánticos y preparemos los circuitos de codificación:

qubits = cirq.GridQubit.rect(1, DATASET_DIM + 1) q_x_train_circuits = prepare_pqk_circuits(qubits, x_train) q_x_test_circuits = prepare_pqk_circuits(qubits, x_test)

A continuación, calculemos las funciones PQK según el 1-RDM de los circuitos del conjunto de datos anterior y almacenemos los resultados en rdm, a tf.Tensor con la forma [n_points, n_qubits, 3]. Las entradas en rdm[i][j][k] = ψiOPjkψi\langle \psi_i | OP^k_j | \psi_i \rangle donde i indexa sobre puntos de datos; j, sobre bits cuánticos y k, sobre {X^,Y^,Z^}\lbrace \hat{X}, \hat{Y}, \hat{Z} \rbrace .

def get_pqk_features(qubits, data_batch): """Get PQK features based on above construction.""" ops = [[cirq.X(q), cirq.Y(q), cirq.Z(q)] for q in qubits] ops_tensor = tf.expand_dims(tf.reshape(tfq.convert_to_tensor(ops), -1), 0) batch_dim = tf.gather(tf.shape(data_batch), 0) ops_splat = tf.tile(ops_tensor, [batch_dim, 1]) exp_vals = tfq.layers.Expectation()(data_batch, operators=ops_splat) rdm = tf.reshape(exp_vals, [batch_dim, len(qubits), -1]) return rdm
x_train_pqk = get_pqk_features(qubits, q_x_train_circuits) x_test_pqk = get_pqk_features(qubits, q_x_test_circuits) print('New PQK training dataset has shape:', x_train_pqk.shape) print('New PQK testing dataset has shape:', x_test_pqk.shape)
New PQK training dataset has shape: (1000, 11, 3) New PQK testing dataset has shape: (200, 11, 3)

2.2 Reetiquetado basado en características del PQK

Ahora, que ya contamos con estas características generadas cuánticamente en x_train_pqk y x_test_pqk, es hora de reetiquetar el conjunto de datos. Para lograr la separación entre el desempeño clásico y el cuántico, podemos reetiquetar el conjunto de datos basándonos en la información del espectro, encontrada en x_train_pqk and x_test_pqk.

Nota: Esta preparación del conjunto de datos para maximizar explícitamente la separación del desempeño entre los modelos clásico y cuántico puede parecer engañosa, pero es muy importante, ya que prueba la existencia de conjuntos de datos que son complicados para computadoras clásicas y sencillos para que las computadoras cuánticas los modelen. No tendría sentido buscar una ventaja cuántica en el aprendizaje automático cuántico (QML) si, primero, no se pudiera crear algo como esto para demostrar la ventaja que ofrece.

def compute_kernel_matrix(vecs, gamma): """Computes d[i][j] = e^ -gamma * (vecs[i] - vecs[j]) ** 2 """ scaled_gamma = gamma / ( tf.cast(tf.gather(tf.shape(vecs), 1), tf.float32) * tf.math.reduce_std(vecs)) return scaled_gamma * tf.einsum('ijk->ij',(vecs[:,None,:] - vecs) ** 2) def get_spectrum(datapoints, gamma=1.0): """Compute the eigenvalues and eigenvectors of the kernel of datapoints.""" KC_qs = compute_kernel_matrix(datapoints, gamma) S, V = tf.linalg.eigh(KC_qs) S = tf.math.abs(S) return S, V
S_pqk, V_pqk = get_spectrum( tf.reshape(tf.concat([x_train_pqk, x_test_pqk], 0), [-1, len(qubits) * 3])) S_original, V_original = get_spectrum( tf.cast(tf.concat([x_train, x_test], 0), tf.float32), gamma=0.005) print('Eigenvectors of pqk kernel matrix:', V_pqk) print('Eigenvectors of original kernel matrix:', V_original)
Eigenvectors of pqk kernel matrix: tf.Tensor( [[-2.09569391e-02 1.05973557e-02 2.16634180e-02 ... 2.80352887e-02 1.55521873e-02 2.82677952e-02] [-2.29303762e-02 4.66355234e-02 7.91163836e-03 ... -6.14174758e-04 -7.07804322e-01 2.85902526e-02] [-1.77853629e-02 -3.00758495e-03 -2.55225878e-02 ... -2.40783971e-02 2.11018627e-03 2.69009806e-02] ... [ 6.05797209e-02 1.32483775e-02 2.69536003e-02 ... -1.38843581e-02 3.05043962e-02 3.85345481e-02] [ 6.33309558e-02 -3.04112374e-03 9.77444276e-03 ... 7.48321265e-02 3.42793856e-03 3.67484428e-02] [ 5.86028099e-02 5.84433973e-03 2.64811981e-03 ... 2.82612257e-02 -3.80136147e-02 3.29943895e-02]], shape=(1200, 1200), dtype=float32) Eigenvectors of original kernel matrix: tf.Tensor( [[ 0.03835681 0.0283473 -0.01169789 ... 0.02343717 0.0211248 0.03206972] [-0.04018159 0.00888097 -0.01388255 ... 0.00582427 0.717551 0.02881948] [-0.0166719 0.01350376 -0.03663862 ... 0.02467175 -0.00415936 0.02195409] ... [-0.03015648 -0.01671632 -0.01603392 ... 0.00100583 -0.00261221 0.02365689] [ 0.0039777 -0.04998879 -0.00528336 ... 0.01560401 -0.04330755 0.02782002] [-0.01665728 -0.00818616 -0.0432341 ... 0.00088256 0.00927396 0.01875088]], shape=(1200, 1200), dtype=float32)

Ahora tenemos todo lo necesario para reetiquetar el conjunto de datos. Podemos consultar el diagrama de flujo para entender mejor cómo maximizar la separación entre desempeños al reetiquetar el conjunto de datos:

A fin de maximizar la separación entre los modelos clásico y cuántico, intentaremos ampliar la diferencia geométrica entre el conjunto de datos original y las matrices de núcleos de características del PQK g(K1K2)=K2K11K2g(K_1 || K_2) = \sqrt{ || \sqrt{K_2} K_1^{-1} \sqrt{K_2} || _\infty} con S_pqk, V_pqk y S_original, V_original. Un valor grande de gg garantiza que, inicialmente, nos moviéramos a la derecha en el diagrama de flujo, hacia abajo y en dirección a la ventaja de predicción del caso cuántico.

Nota: El cálculo de las cantidades para ss y dd también es muy útil cuando buscamos entender mejor las separaciones entre desempeños. En este caso, asegurándonos de que un valor gg será suficiente para ver la separación entre los rendimientos.

def get_stilted_dataset(S, V, S_2, V_2, lambdav=1.1): """Prepare new labels that maximize geometric distance between kernels.""" S_diag = tf.linalg.diag(S ** 0.5) S_2_diag = tf.linalg.diag(S_2 / (S_2 + lambdav) ** 2) scaling = S_diag @ tf.transpose(V) @ \ V_2 @ S_2_diag @ tf.transpose(V_2) @ \ V @ S_diag # Generate new lables using the largest eigenvector. _, vecs = tf.linalg.eig(scaling) new_labels = tf.math.real( tf.einsum('ij,j->i', tf.cast(V @ S_diag, tf.complex64), vecs[-1])).numpy() # Create new labels and add some small amount of noise. final_y = new_labels > np.median(new_labels) noisy_y = (final_y ^ (np.random.uniform(size=final_y.shape) > 0.95)) return noisy_y
y_relabel = get_stilted_dataset(S_pqk, V_pqk, S_original, V_original) y_train_new, y_test_new = y_relabel[:N_TRAIN], y_relabel[N_TRAIN:]

3. Comparación de los modelos

Ya hemos preparado el conjunto de datos y llegó la hora de comparar el desempeño de los modelos. Crearemos dos redes neuronales prealimentadas pequeñas y compararemos el desempeño cuando se les otorgue acceso a las características del PQK que se encuentran en x_train_pqk.

3.1 Creación del modelo mejorad del PQK

Con las características de la biblioteca tf.keras estándar, ahora se puede crear y entrenar un modelo sobre los puntos de datos x_train_pqk y y_train_new:

#docs_infra: no_execute def create_pqk_model(): model = tf.keras.Sequential() model.add(tf.keras.layers.Dense(32, activation='sigmoid', input_shape=[len(qubits) * 3,])) model.add(tf.keras.layers.Dense(16, activation='sigmoid')) model.add(tf.keras.layers.Dense(1)) return model pqk_model = create_pqk_model() pqk_model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), optimizer=tf.keras.optimizers.Adam(learning_rate=0.003), metrics=['accuracy']) pqk_model.summary()
Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense (Dense) (None, 32) 1088 _________________________________________________________________ dense_1 (Dense) (None, 16) 528 _________________________________________________________________ dense_2 (Dense) (None, 1) 17 ================================================================= Total params: 1,633 Trainable params: 1,633 Non-trainable params: 0 _________________________________________________________________
#docs_infra: no_execute pqk_history = pqk_model.fit(tf.reshape(x_train_pqk, [N_TRAIN, -1]), y_train_new, batch_size=32, epochs=1000, verbose=0, validation_data=(tf.reshape(x_test_pqk, [N_TEST, -1]), y_test_new))

3.2 Creación de un modelo clásico

Similar a lo que sucede con el código anterior, también se puede crear un modelo clásico que no tiene acceso a las características del PQK del conjunto de datos artificial. Este modelo se puede entrenar con x_train y y_label_new.

#docs_infra: no_execute def create_fair_classical_model(): model = tf.keras.Sequential() model.add(tf.keras.layers.Dense(32, activation='sigmoid', input_shape=[DATASET_DIM,])) model.add(tf.keras.layers.Dense(16, activation='sigmoid')) model.add(tf.keras.layers.Dense(1)) return model model = create_fair_classical_model() model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), optimizer=tf.keras.optimizers.Adam(learning_rate=0.03), metrics=['accuracy']) model.summary()
Model: "sequential_1" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_3 (Dense) (None, 32) 352 _________________________________________________________________ dense_4 (Dense) (None, 16) 528 _________________________________________________________________ dense_5 (Dense) (None, 1) 17 ================================================================= Total params: 897 Trainable params: 897 Non-trainable params: 0 _________________________________________________________________
#docs_infra: no_execute classical_history = model.fit(x_train, y_train_new, batch_size=32, epochs=1000, verbose=0, validation_data=(x_test, y_test_new))

3.3 Comparación del desempeño

Ahora que ya hemos entrenado los dos modelos podemos graficar rápidamente las brechas de desempeño entre los datos de validación de dichos modelos. Por lo común, ambos modelos alcanzarán una precisión > 0.9 en los datos de entrenamiento. Sin embargo, para los datos de validación se hace evidente que solamente la información hallada en las características del PQK no es suficiente para generalizar bien el modelo para instancias no vistas.

#docs_infra: no_execute plt.figure(figsize=(10,5)) plt.plot(classical_history.history['accuracy'], label='accuracy_classical') plt.plot(classical_history.history['val_accuracy'], label='val_accuracy_classical') plt.plot(pqk_history.history['accuracy'], label='accuracy_quantum') plt.plot(pqk_history.history['val_accuracy'], label='val_accuracy_quantum') plt.xlabel('Epoch') plt.ylabel('Accuracy') plt.legend()
<matplotlib.legend.Legend at 0x7f6d846ecee0>
Image in a Jupyter notebook

Excelente. Hemos logrado concebir un conjunto de datos cuántico artificial que derrota intencionalmente a los modelos clásicos en un entorno equilibrado (pero forzado). Intente comparar los resultados con los de otros tipos de modelos clásicos. El paso siguiente consiste en intentar y ver si se pueden hallar nuevos conjuntos de datos interesantes que puedan superar a los modelos clásicos sin necesidad de modificarlos.

4. Conclusiones importantes

Se pueden sacar muchas conclusiones importantes a partir de este experimento y del llevado a cabo con MNIST:

  1. Es bastante improbable que los modelos cuánticos de hoy en día tengan un mejor desempeño que los modelos clásicos con datos clásicos. Especialmente, con los conjuntos de datos clásicos actuales, que pueden tener más de un millón de puntos de datos.

  2. El hecho de que los datos posiblemente provengan de un circuito cuántico difícil de simular como clásico, no significa necesariamente que los datos sean difíciles de aprender para un modelo clásico.

  3. Hay conjuntos de datos (en última instancia, de naturaleza cuántica) que son fáciles de aprender para los modelos cuánticos y difíciles para los clásicos, independientemente de la arquitectura del modelo o de los algoritmos de entrenamiento que se utilicen.