Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
tensorflow
GitHub Repository: tensorflow/docs-l10n
Path: blob/master/site/es-419/quantum/tutorials/mnist.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.

Clasificación MNIST

En este tutorial creamos una red neuronal cuántica (QNN) para clasificar una versión simplificada de MNIST, similar al método utilizado en Farhi et al. Se compara el desempeño de una red neuronal cuántica con el de una clásica frente a un problema de datos clásico.

Preparación

!pip install tensorflow==2.7.0

Instalar TensorFlow Quantum:

!pip install tensorflow-quantum==0.7.2
# Update package resources to account for version changes. import importlib, pkg_resources importlib.reload(pkg_resources)

Ahora, hay que importar TensorFlow y las dependencias del módulo:

import tensorflow as tf import tensorflow_quantum as tfq import cirq import sympy import numpy as np import seaborn as sns import collections # visualization tools %matplotlib inline import matplotlib.pyplot as plt from cirq.contrib.svg import SVGCircuit

1. Carga de los datos

En este tutorial crearemos un clasificador binario para distinguir entre los dígitos 3 y 6, siguiendo lo expuesto por Farhi et al.. En esta sección se tratan los temas de manipulación de datos que incluye lo siguiente:

  • Cargar los datos sin procesar desde Keras.

  • Filtrar el conjunto de datos a solamente los 3 y los 6.

  • Reducir la escala de las imágenes para que quepan en una computadora cuántica.

  • Quitar cualquier ejemplo contradictorio.

  • Convertir las imágenes binarias a circuitos Cirq.

  • Convertir los circuitos Cirq a circuitos de TensorFlow Quantum.

1.1 Carga de los datos sin procesar

Cargamos el conjunto de datos MNIST distribuido con Keras.

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

Filtramos el conjunto de datos para conservar solamente los 3 y los 6, y eliminar las otras clases. Al mismo tiempo, convertimos la etiqueta y al booleano: True para 3 and False para 6.

def filter_36(x, y): keep = (y == 3) | (y == 6) x, y = x[keep], y[keep] y = y == 3 return x,y
x_train, y_train = filter_36(x_train, y_train) x_test, y_test = filter_36(x_test, y_test) print("Number of filtered training examples:", len(x_train)) print("Number of filtered test examples:", len(x_test))

Mostramos el primer ejemplo:

print(y_train[0]) plt.imshow(x_train[0, :, :, 0]) plt.colorbar()

1.2 Reducción de las imágenes a escala

El tamaño de una imagen de 28×28 es demasiado grande para las computadores cuánticas actuales. Hay que cambiarles el tamaño a 4×4:

x_train_small = tf.image.resize(x_train, (4,4)).numpy() x_test_small = tf.image.resize(x_test, (4,4)).numpy()

Nuevamente, mostramos el primer ejemplo de entrenamiento; después del cambio de tamaño:

print(y_train[0]) plt.imshow(x_train_small[0,:,:,0], vmin=0, vmax=1) plt.colorbar()

1.3 Eliminación de los ejemplos contradictorios

De la sección 3.3 Learning to Distinguish Digits (Cómo aprender a distinguir dígitos) de Farhi et al., filtre el conjunto de datos para quitar las imágenes etiquetadas como pertenecientes a ambas clases.

No es un procedimiento de aprendizaje automático estándar, pero se incluye a fin de seguir lo expuesto en la publicación.

def remove_contradicting(xs, ys): mapping = collections.defaultdict(set) orig_x = {} # Determine the set of labels for each unique image: for x,y in zip(xs,ys): orig_x[tuple(x.flatten())] = x mapping[tuple(x.flatten())].add(y) new_x = [] new_y = [] for flatten_x in mapping: x = orig_x[flatten_x] labels = mapping[flatten_x] if len(labels) == 1: new_x.append(x) new_y.append(next(iter(labels))) else: # Throw out images that match more than one label. pass num_uniq_3 = sum(1 for value in mapping.values() if len(value) == 1 and True in value) num_uniq_6 = sum(1 for value in mapping.values() if len(value) == 1 and False in value) num_uniq_both = sum(1 for value in mapping.values() if len(value) == 2) print("Number of unique images:", len(mapping.values())) print("Number of unique 3s: ", num_uniq_3) print("Number of unique 6s: ", num_uniq_6) print("Number of unique contradicting labels (both 3 and 6): ", num_uniq_both) print() print("Initial number of images: ", len(xs)) print("Remaining non-contradicting unique images: ", len(new_x)) return np.array(new_x), np.array(new_y)

Los resultados de los conteos no coinciden con los valores informados, pero no se ha especificado un procedimiento exacto.

Tampoco importa, en este caso, que la aplicación de filtros a ejemplos contradictorios en este punto no evite por completo que el modelo reciba ejemplos contradictorios de entrenamiento: en el paso siguiente se binarizan los datos que causarán más colisiones.

x_train_nocon, y_train_nocon = remove_contradicting(x_train_small, y_train)

1.4 Codificación de los datos como circuitos cuánticos

Para procesar las imágenes con una computadora cuántica, Farhi et al. propusieron representar cada pixel con un bit cuántico, con el estado pendiente con respecto al valor del pixel. El primer paso es convertir la codificación binaria.

THRESHOLD = 0.5 x_train_bin = np.array(x_train_nocon > THRESHOLD, dtype=np.float32) x_test_bin = np.array(x_test_small > THRESHOLD, dtype=np.float32)

Si quitáramos las imágenes contradictorias en este punto, solamente quedarían 193 y, probablemente, no serían suficientes para hacer un entrenamiento efectivo.

_ = remove_contradicting(x_train_bin, y_train_nocon)

Los bits cuánticos de los índices del pixel con valores que exceden el límite se rotan a través de una puerta XX.

def convert_to_circuit(image): """Encode truncated classical image into quantum datapoint.""" values = np.ndarray.flatten(image) qubits = cirq.GridQubit.rect(4, 4) circuit = cirq.Circuit() for i, value in enumerate(values): if value: circuit.append(cirq.X(qubits[i])) return circuit x_train_circ = [convert_to_circuit(x) for x in x_train_bin] x_test_circ = [convert_to_circuit(x) for x in x_test_bin]

El siguiente es el circuito creado para el primer ejemplo (en los diagramas del circuito no se muestran los bits cuánticos con puertas en cero):

SVGCircuit(x_train_circ[0])

Comparamos este circuito con los índices en los que el valor de la imagen excede al límite:

bin_img = x_train_bin[0,:,:,0] indices = np.array(np.where(bin_img)).T indices

Convertimos estos circuitos Cirq a tensores para tfq:

x_train_tfcirc = tfq.convert_to_tensor(x_train_circ) x_test_tfcirc = tfq.convert_to_tensor(x_test_circ)

2. Red neuronal cuántica

Hay poca información relacionada con la estructura de los circuitos cuánticos que clasifican imágenes. Dado que la clasificación se basa en la esperanza del bit cuántico que se lee, Farhi et al. proponen utilizar dos puertas de bit cuántico con el cúbit leído sobre el que siempre nos basamos. Es parecido, en cierto modo, a ejecutar en una pequeña proporción una red neuronal recurrente unitaria a través de los pixeles.

2.1 Creación del circuito modelo

En el siguiente ejemplo se muestra esta aproximación por capas. Cada capa usa n instancias de la misma puerta, con cada uno de los bits cuánticos de datos que actúan sobre le bit cuántico leído.

Empezamos con una clase simple que agregará una capa de estas puertas a un circuito:

class CircuitLayerBuilder(): def __init__(self, data_qubits, readout): self.data_qubits = data_qubits self.readout = readout def add_layer(self, circuit, gate, prefix): for i, qubit in enumerate(self.data_qubits): symbol = sympy.Symbol(prefix + '-' + str(i)) circuit.append(gate(qubit, self.readout)**symbol)

Creamos una capa del circuito de ejemplo para ver cómo queda:

demo_builder = CircuitLayerBuilder(data_qubits = cirq.GridQubit.rect(4,1), readout=cirq.GridQubit(-1,-1)) circuit = cirq.Circuit() demo_builder.add_layer(circuit, gate = cirq.XX, prefix='xx') SVGCircuit(circuit)

Ahora, construimos un modelo con dos capas, que coincida con el tamaño del circuito y los datos, e incluimos las operaciones de preparación y lectura.

def create_quantum_model(): """Create a QNN model circuit and readout operation to go along with it.""" data_qubits = cirq.GridQubit.rect(4, 4) # a 4x4 grid. readout = cirq.GridQubit(-1, -1) # a single qubit at [-1,-1] circuit = cirq.Circuit() # Prepare the readout qubit. circuit.append(cirq.X(readout)) circuit.append(cirq.H(readout)) builder = CircuitLayerBuilder( data_qubits = data_qubits, readout=readout) # Then add layers (experiment by adding more). builder.add_layer(circuit, cirq.XX, "xx1") builder.add_layer(circuit, cirq.ZZ, "zz1") # Finally, prepare the readout qubit. circuit.append(cirq.H(readout)) return circuit, cirq.Z(readout)
model_circuit, model_readout = create_quantum_model()

2.2 Encapsulamiento de un circuito modelo en un modelo tfq-keras

Creamos el modelo Keras con componentes cuánticos. Este modelo se alimenta con "datos cuánticos", de x_train_circ, que codifican los datos clásicos. Usa una capa del circuito cuántico parametrizado, tfq.layers.PQC, para entrenar el circuito del modelo sobre los datos cuánticos.

Para clasificar estas imágenes, Farhi et al. propuso tomar la esperanza de un bit cuántico de lectura en un circuito parametrizado. La esperanza devuelve un valor entre 1 y -1.

# Build the Keras model. model = tf.keras.Sequential([ # The input is the data-circuit, encoded as a tf.string tf.keras.layers.Input(shape=(), dtype=tf.string), # The PQC layer returns the expected value of the readout gate, range [-1,1]. tfq.layers.PQC(model_circuit, model_readout), ])

A continuación, describimos el procedimiento de entrenamiento para el modelo, con el método compile.

Como la lectura esperada se encuentra en el rango de [-1,1], la optimización de la pérdida de articulación es, en cierto modo, natural.

Nota: Otra opción válida sería cambiar el rango de salida a [0,1] y tratarlo como la probabilidad que le asigna el modelo a la clase 3. Se podría usar con un estándar, una pérdida tf.losses.BinaryCrossentropy.

Para usar la pérdida de articulación es necesario hacer dos ajustes pequeños. Primero debemos convertir las etiquetas y_train_nocon, de booleanas a [-1,1], tal como se espera para la pérdida de articulación.

y_train_hinge = 2.0*y_train_nocon-1.0 y_test_hinge = 2.0*y_test-1.0

En segundo lugar, usaremos una métrica de hinge_accuracy personalizada que administra correctamente [-1, 1] como el argumento de las etiquetas y_true. tf.losses.BinaryAccuracy(threshold=0.0) espera que y_true sea booleano y, por lo tanto, no se puede usar con la pérdida de articulación.

def hinge_accuracy(y_true, y_pred): y_true = tf.squeeze(y_true) > 0.0 y_pred = tf.squeeze(y_pred) > 0.0 result = tf.cast(y_true == y_pred, tf.float32) return tf.reduce_mean(result)
model.compile( loss=tf.keras.losses.Hinge(), optimizer=tf.keras.optimizers.Adam(), metrics=[hinge_accuracy])
print(model.summary())

Entrenamiento del modelo cuántico

Ahora, entrenemos el modelo. Tomará alrededor de 45 minutos. Si no queremos esperar tanto tiempo, podemos usar un subconjunto pequeño de datos (establecer NUM_EXAMPLES=500, como a continuación). Realmente no afectará al progreso del modelo durante el entrenamiento (solamente tiene 32 parámetros y no necesita muchos datos que los restrinjan). Con menos ejemplos, simplemente, termina antes (en 5 minutos), pero se ejecuta el tiempo suficiente como para mostrar que se producen avances con los registros de validación.

EPOCHS = 3 BATCH_SIZE = 32 NUM_EXAMPLES = len(x_train_tfcirc)
x_train_tfcirc_sub = x_train_tfcirc[:NUM_EXAMPLES] y_train_hinge_sub = y_train_hinge[:NUM_EXAMPLES]

El entrenamiento de este modelo para la convergencia debería alcanzar >85% de precisión en el conjunto de la prueba.

qnn_history = model.fit( x_train_tfcirc_sub, y_train_hinge_sub, batch_size=32, epochs=EPOCHS, verbose=1, validation_data=(x_test_tfcirc, y_test_hinge)) qnn_results = model.evaluate(x_test_tfcirc, y_test)

Nota: La exactitud del entrenamiento presenta el promedio sobre la época. La exactitud de la validación se evalúa al final de cada época.

3. Red neuronal clásica

Mientras que la red neuronal cuántica funciona bien con este problema de MNIST simplificado, una red neuronal clásica, fácilmente, puede tener un mejor desempeño que una QNN en esta tarea. Después de una sola época, la red neuronal clásica puede lograr >98% de exactitud con el conjunto retenido.

En el siguiente ejemplo, se usa una red neuronal clásica para el problema de clasificación de 3-6 con una imagen entera de 28×28, en vez de utilizar una submuestra de la imagen. Fácilmente converge en aproximadamente un 100% de exactitud del conjunto de prueba.

def create_classical_model(): # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/ model = tf.keras.Sequential() model.add(tf.keras.layers.Conv2D(32, [3, 3], activation='relu', input_shape=(28,28,1))) model.add(tf.keras.layers.Conv2D(64, [3, 3], activation='relu')) model.add(tf.keras.layers.MaxPooling2D(pool_size=(2, 2))) model.add(tf.keras.layers.Dropout(0.25)) model.add(tf.keras.layers.Flatten()) model.add(tf.keras.layers.Dense(128, activation='relu')) model.add(tf.keras.layers.Dropout(0.5)) model.add(tf.keras.layers.Dense(1)) return model model = create_classical_model() model.compile(loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), optimizer=tf.keras.optimizers.Adam(), metrics=['accuracy']) model.summary()
model.fit(x_train, y_train, batch_size=128, epochs=1, verbose=1, validation_data=(x_test, y_test)) cnn_results = model.evaluate(x_test, y_test)

Este modelo anterior tiene aproximadamente 1 200 000 parámetros. Para que la comparación resulte más justa, probemos con un modelo de 37 parámetros, con las submuestras de las imágenes:

def create_fair_classical_model(): # A simple model based off LeNet from https://keras.io/examples/mnist_cnn/ model = tf.keras.Sequential() model.add(tf.keras.layers.Flatten(input_shape=(4,4,1))) model.add(tf.keras.layers.Dense(2, activation='relu')) 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(), metrics=['accuracy']) model.summary()
model.fit(x_train_bin, y_train_nocon, batch_size=128, epochs=20, verbose=2, validation_data=(x_test_bin, y_test)) fair_nn_results = model.evaluate(x_test_bin, y_test)

4. Comparación

Mientras más alta sea la entrada de la resolución y más potente sea el modelo, el problema se hará más sencillo para la CNN. Mientras que un modelo clásico de potencia similar (con ~32 parámetros) entrena hasta lograr una exactitud similar en una fracción de tiempo. De un modo u otro, la red neuronal clásica tiene un mejor desempeño que la red neuronal cuántica. Para los datos clásicos, es difícil ganarle a una red neuronal clásica.

qnn_accuracy = qnn_results[1] cnn_accuracy = cnn_results[1] fair_nn_accuracy = fair_nn_results[1] sns.barplot(x=["Quantum", "Classical, full", "Classical, fair"], y=[qnn_accuracy, cnn_accuracy, fair_nn_accuracy])