Se usó la API de Cloud Translation para traducir esta página.
Switch to English

Clasificación MNIST

Ver en TensorFlow.org Ejecutar en Google Colab Ver código fuente en GitHub Descargar cuaderno

Este tutorial construye una red neuronal cuántica (QNN) para clasificar una versión simplificada de MNIST, similar al enfoque utilizado en Farhi et al . El rendimiento de la red neuronal cuántica en este problema de datos clásico se compara con una red neuronal clásica.

Preparar

pip install -q tensorflow==2.1.0

Instale TensorFlow Quantum:

pip install -q tensorflow-quantum

Ahora importe 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. Cargue los datos

En este tutorial, construirá un clasificador binario para distinguir entre los dígitos 3 y 6, siguiendo a Farhi et al. Esta sección cubre el manejo de datos que:

  • Carga los datos sin procesar de Keras.
  • Filtra el conjunto de datos a solo 3s y 6s.
  • Reduce las imágenes para que quepan en una computadora cuántica.
  • Elimina cualquier ejemplo contradictorio.
  • Convierte las imágenes binarias en circuitos Cirq.
  • Convierte los circuitos Circ en circuitos cuánticos TensorFlow.

1.1 Cargar los datos sin procesar

Cargue 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))
 
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz
11493376/11490434 [==============================] - 0s 0us/step
Number of original training examples: 60000
Number of original test examples: 10000

Filtre el conjunto de datos para mantener solo los 3 y 6, elimine las otras clases. Al mismo tiempo, convierta la etiqueta, y , a booleana: True para 3 y 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))
 
Number of filtered training examples: 12049
Number of filtered test examples: 1968

Muestra el primer ejemplo:

 print(y_train[0])

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

<matplotlib.colorbar.Colorbar at 0x7f9169ae6a58>

png

1.2 Reduzca la escala de las imágenes

Un tamaño de imagen de 28x28 es demasiado grande para las computadoras cuánticas actuales. Cambiar el tamaño de la imagen a 4x4:

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

Nuevamente, muestre el primer ejemplo de entrenamiento, después de cambiar el tamaño:

 print(y_train[0])

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

<matplotlib.colorbar.Colorbar at 0x7f91643ac828>

png

1.3 Eliminar ejemplos contradictorios

De la sección 3.3 Aprender a distinguir dígitos de Farhi et al. , filtre el conjunto de datos para eliminar imágenes etiquetadas como pertenecientes a ambas clases.

Este no es un procedimiento estándar de aprendizaje automático, pero se incluye con el interés de seguir el documento.

 def remove_contradicting(xs, ys):
    mapping = collections.defaultdict(set)
    # Determine the set of labels for each unique image:
    for x,y in zip(xs,ys):
       mapping[tuple(x.flatten())].add(y)
    
    new_x = []
    new_y = []
    for x,y in zip(xs, ys):
      labels = mapping[tuple(x.flatten())]
      if len(labels) == 1:
          new_x.append(x)
          new_y.append(list(labels)[0])
      else:
          # Throw out images that match more than one label.
          pass
    
    num_3 = sum(1 for value in mapping.values() if True in value)
    num_6 = sum(1 for value in mapping.values() if False in value)
    num_both = sum(1 for value in mapping.values() if len(value) == 2)

    print("Number of unique images:", len(mapping.values()))
    print("Number of 3s: ", num_3)
    print("Number of 6s: ", num_6)
    print("Number of contradictory images: ", num_both)
    print()
    print("Initial number of examples: ", len(xs))
    print("Remaining non-contradictory examples: ", len(new_x))
    
    return np.array(new_x), np.array(new_y)
 

Los recuentos resultantes no coinciden estrechamente con los valores informados, pero no se especifica el procedimiento exacto.

También vale la pena señalar aquí que la aplicación de ejemplos contradictorios de filtrado en este punto no impide totalmente que el modelo reciba ejemplos de entrenamiento contradictorios: el siguiente paso binariza los datos que causarán más colisiones.

 x_train_nocon, y_train_nocon = remove_contradicting(x_train_small, y_train)
 
Number of unique images: 10387
Number of 3s:  4961
Number of 6s:  5475
Number of contradictory images:  49

Initial number of examples:  12049
Remaining non-contradictory examples:  11520

1.3 Codificar los datos como circuitos cuánticos

Para procesar imágenes usando una computadora cuántica, Farhi et al. propuso representar cada píxel con un qubit, con el estado dependiendo del valor del píxel. El primer paso es convertir a una 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 eliminara imágenes contradictorias en este punto, solo le quedarían 193, probablemente no lo suficiente para un entrenamiento efectivo.

 _ = remove_contradicting(x_train_bin, y_train_nocon)
 
Number of unique images: 193
Number of 3s:  124
Number of 6s:  113
Number of contradictory images:  44

Initial number of examples:  11520
Remaining non-contradictory examples:  3731

Los qubits en los índices de píxeles con valores que exceden un umbral, se rotan a través de una puerta $ X $.

 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]
 

Aquí está el circuito creado para el primer ejemplo (los diagramas de circuito no muestran qubits con puertas cero):

 SVGCircuit(x_train_circ[0])
 
findfont: Font family ['Arial'] not found. Falling back to DejaVu Sans.

svg

Compare este circuito con los índices donde el valor de la imagen excede el umbral:

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

Convierta estos circuitos Cirq en 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 guía para una estructura de circuito cuántico que clasifique las imágenes. Dado que la clasificación se basa en la expectativa del qubit de lectura, Farhi et al. proponga usar dos puertas qubit, con la qubit de lectura siempre actuada. Esto es similar en algunos aspectos a ejecutar un RNN unitario pequeño a través de los píxeles.

2.1 Construir el circuito modelo

El siguiente ejemplo muestra este enfoque por capas. Cada capa utiliza n instancias de la misma puerta, y cada uno de los qubits de datos actúa en el qubit de lectura.

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

Cree un ejemplo de capa de circuito para ver cómo se ve:

 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)
 

svg

Ahora construya un modelo de dos capas, que coincida con el tamaño del circuito de datos e incluya 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 Envuelva el circuito modelo en un modelo tfq-keras

Construye el modelo Keras con los componentes cuánticos. Este modelo se alimenta de los "datos cuánticos", de x_train_circ , que codifica los datos clásicos. Utiliza una capa de Circuito Cuántico Parametrizado , tfq.layers.PQC , para entrenar el circuito modelo, en los datos cuánticos.

Para clasificar estas imágenes, Farhi et al. propuso tomar la expectativa de un qubit de lectura en un circuito parametrizado. La expectativa 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, describa el procedimiento de capacitación para el modelo, utilizando el método de compile .

Dado que la lectura esperada está en el rango [-1,1] , optimizar la pérdida de la bisagra es un ajuste algo natural.

Para usar la pérdida de la bisagra aquí, necesita hacer dos pequeños ajustes. Primero convierta las etiquetas, y_train_nocon , de booleano a [-1,1] , como se esperaba por la pérdida de la bisagra.

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

En segundo lugar, use una métrica custiom hinge_accuracy que maneje correctamente [-1, 1] como argumento de etiquetas y_true . tf.losses.BinaryAccuracy(threshold=0.0) espera que y_true sea ​​un valor booleano, por lo que no se puede usar con pérdida de bisagra).

 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())
 
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
pqc (PQC)                    (None, 1)                 32        
=================================================================
Total params: 32
Trainable params: 32
Non-trainable params: 0
_________________________________________________________________
None

Entrena el modelo cuántico

Ahora entrene al modelo, esto toma alrededor de 45 minutos. Si no desea esperar tanto tiempo, use un pequeño subconjunto de datos (establezca NUM_EXAMPLES=500 , a continuación). Esto realmente no afecta el progreso del modelo durante el entrenamiento (solo tiene 32 parámetros y no necesita muchos datos para restringirlos). El uso de menos ejemplos solo finaliza el entrenamiento antes (5 minutos), pero se ejecuta el tiempo suficiente para mostrar que está progresando en 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]
 

La capacitación de este modelo para la convergencia debería lograr una precisión> 85% en el conjunto de 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)
 
Train on 11520 samples, validate on 1968 samples
Epoch 1/3
11520/11520 [==============================] - 401s 35ms/sample - loss: 0.7029 - hinge_accuracy: 0.6921 - val_loss: 0.4285 - val_hinge_accuracy: 0.8241
Epoch 2/3
11520/11520 [==============================] - 398s 35ms/sample - loss: 0.3755 - hinge_accuracy: 0.8405 - val_loss: 0.3465 - val_hinge_accuracy: 0.8427
Epoch 3/3
11520/11520 [==============================] - 401s 35ms/sample - loss: 0.3373 - hinge_accuracy: 0.8474 - val_loss: 0.3372 - val_hinge_accuracy: 0.8528
1968/1968 [==============================] - 3s 1ms/sample - loss: 0.3372 - hinge_accuracy: 0.8528

3. Red neuronal clásica

Si bien la red neuronal cuántica funciona para este problema simplificado de MNIST, una red neuronal clásica básica puede superar fácilmente a un QNN en esta tarea. Después de una sola época, una red neuronal clásica puede lograr> 98% de precisión en el conjunto de retención.

En el siguiente ejemplo, se usa una red neuronal clásica para el problema de clasificación 3-6 usando toda la imagen de 28x28 en lugar de submuestrear la imagen. Esto converge fácilmente a casi el 100% de precisión 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: "sequential_1"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
conv2d (Conv2D)              (None, 26, 26, 32)        320       
_________________________________________________________________
conv2d_1 (Conv2D)            (None, 24, 24, 64)        18496     
_________________________________________________________________
max_pooling2d (MaxPooling2D) (None, 12, 12, 64)        0         
_________________________________________________________________
dropout (Dropout)            (None, 12, 12, 64)        0         
_________________________________________________________________
flatten (Flatten)            (None, 9216)              0         
_________________________________________________________________
dense (Dense)                (None, 128)               1179776   
_________________________________________________________________
dropout_1 (Dropout)          (None, 128)               0         
_________________________________________________________________
dense_1 (Dense)              (None, 1)                 129       
=================================================================
Total params: 1,198,721
Trainable params: 1,198,721
Non-trainable params: 0
_________________________________________________________________

 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)
 
Train on 12049 samples, validate on 1968 samples
12049/12049 [==============================] - 3s 276us/sample - loss: 0.0423 - accuracy: 0.9825 - val_loss: 0.0024 - val_accuracy: 0.9995
1968/1968 [==============================] - 0s 106us/sample - loss: 0.0024 - accuracy: 0.9995

El modelo anterior tiene casi 1.2 millones de parámetros. Para una comparación más justa, pruebe un modelo de 37 parámetros, en las imágenes submuestreadas:

 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: "sequential_2"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
flatten_1 (Flatten)          (None, 16)                0         
_________________________________________________________________
dense_2 (Dense)              (None, 2)                 34        
_________________________________________________________________
dense_3 (Dense)              (None, 1)                 3         
=================================================================
Total params: 37
Trainable params: 37
Non-trainable params: 0
_________________________________________________________________

 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)
 
Train on 11520 samples, validate on 1968 samples
Epoch 1/20
11520/11520 - 0s - loss: 0.6458 - accuracy: 0.5267 - val_loss: 0.6227 - val_accuracy: 0.5991
Epoch 2/20
11520/11520 - 0s - loss: 0.5767 - accuracy: 0.6639 - val_loss: 0.5467 - val_accuracy: 0.7165
Epoch 3/20
11520/11520 - 0s - loss: 0.4923 - accuracy: 0.7615 - val_loss: 0.4647 - val_accuracy: 0.7754
Epoch 4/20
11520/11520 - 0s - loss: 0.4170 - accuracy: 0.7968 - val_loss: 0.3992 - val_accuracy: 0.7912
Epoch 5/20
11520/11520 - 0s - loss: 0.3586 - accuracy: 0.8244 - val_loss: 0.3507 - val_accuracy: 0.8176
Epoch 6/20
11520/11520 - 0s - loss: 0.3166 - accuracy: 0.8576 - val_loss: 0.3160 - val_accuracy: 0.8288
Epoch 7/20
11520/11520 - 0s - loss: 0.2870 - accuracy: 0.8600 - val_loss: 0.2911 - val_accuracy: 0.8283
Epoch 8/20
11520/11520 - 0s - loss: 0.2661 - accuracy: 0.8603 - val_loss: 0.2733 - val_accuracy: 0.8277
Epoch 9/20
11520/11520 - 0s - loss: 0.2510 - accuracy: 0.8600 - val_loss: 0.2601 - val_accuracy: 0.8323
Epoch 10/20
11520/11520 - 0s - loss: 0.2400 - accuracy: 0.8615 - val_loss: 0.2502 - val_accuracy: 0.8277
Epoch 11/20
11520/11520 - 0s - loss: 0.2317 - accuracy: 0.8629 - val_loss: 0.2428 - val_accuracy: 0.8277
Epoch 12/20
11520/11520 - 0s - loss: 0.2256 - accuracy: 0.8815 - val_loss: 0.2373 - val_accuracy: 0.8664
Epoch 13/20
11520/11520 - 0s - loss: 0.2208 - accuracy: 0.8820 - val_loss: 0.2326 - val_accuracy: 0.8664
Epoch 14/20
11520/11520 - 0s - loss: 0.2174 - accuracy: 0.8821 - val_loss: 0.2291 - val_accuracy: 0.8664
Epoch 15/20
11520/11520 - 0s - loss: 0.2147 - accuracy: 0.8830 - val_loss: 0.2266 - val_accuracy: 0.8674
Epoch 16/20
11520/11520 - 0s - loss: 0.2126 - accuracy: 0.8924 - val_loss: 0.2244 - val_accuracy: 0.9141
Epoch 17/20
11520/11520 - 0s - loss: 0.2109 - accuracy: 0.9122 - val_loss: 0.2229 - val_accuracy: 0.9141
Epoch 18/20
11520/11520 - 0s - loss: 0.2096 - accuracy: 0.9122 - val_loss: 0.2215 - val_accuracy: 0.9141
Epoch 19/20
11520/11520 - 0s - loss: 0.2085 - accuracy: 0.9122 - val_loss: 0.2204 - val_accuracy: 0.9141
Epoch 20/20
11520/11520 - 0s - loss: 0.2077 - accuracy: 0.9122 - val_loss: 0.2194 - val_accuracy: 0.9141
1968/1968 [==============================] - 0s 22us/sample - loss: 0.2194 - accuracy: 0.9141

4. Comparación

La entrada de mayor resolución y un modelo más potente facilitan este problema para la CNN. Mientras que un modelo clásico de potencia similar (~ 32 parámetros) entrena con una precisión similar en una fracción del tiempo. De una forma u otra, la red neuronal clásica supera fácilmente a la red neuronal cuántica. Para los datos clásicos, es difícil vencer 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(["Quantum", "Classical, full", "Classical, fair"],
            [qnn_accuracy, cnn_accuracy, fair_nn_accuracy])
 
<matplotlib.axes._subplots.AxesSubplot at 0x7f90e8430b70>

png