Classificação MNIST

Veja no TensorFlow.org Executar no Google Colab Ver fonte no GitHub Baixar caderno

Este tutorial cria uma rede neural quântica (QNN) para classificar uma versão simplificada do MNIST, semelhante à abordagem usada em Farhi et al . O desempenho da rede neural quântica neste problema de dados clássico é comparado com uma rede neural clássica.

Configurar

pip install tensorflow==2.7.0

Instale o 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'>

Agora importe o TensorFlow e as dependências do 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
2022-02-04 12:29:39.759643: E tensorflow/stream_executor/cuda/cuda_driver.cc:271] failed call to cuInit: CUDA_ERROR_NO_DEVICE: no CUDA-capable device is detected

1. Carregue os dados

Neste tutorial você construirá um classificador binário para distinguir entre os dígitos 3 e 6, seguindo Farhi et al. Esta seção abrange o tratamento de dados que:

  • Carrega os dados brutos do Keras.
  • Filtra o conjunto de dados para apenas 3s e 6s.
  • Reduz as imagens para que elas caibam em um computador quântico.
  • Remove quaisquer exemplos contraditórios.
  • Converte as imagens binárias em circuitos Cirq.
  • Converte os circuitos Cirq em circuitos TensorFlow Quantum.

1.1 Carregar os dados brutos

Carregue o conjunto de dados MNIST distribuído com 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
11501568/11490434 [==============================] - 0s 0us/step
Number of original training examples: 60000
Number of original test examples: 10000

Filtre o conjunto de dados para manter apenas os 3s e 6s, remova as outras classes. Ao mesmo tempo, converta o rótulo, y , para booleano: True para 3 e 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

Mostre o primeiro exemplo:

print(y_train[0])

plt.imshow(x_train[0, :, :, 0])
plt.colorbar()
True
<matplotlib.colorbar.Colorbar at 0x7fac6ad4bd90>

png

1.2 Reduzir as imagens

Um tamanho de imagem de 28x28 é muito grande para os computadores quânticos atuais. Redimensione a imagem para 4x4:

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

Novamente, exiba o primeiro exemplo de treinamento—após o redimensionamento:

print(y_train[0])

plt.imshow(x_train_small[0,:,:,0], vmin=0, vmax=1)
plt.colorbar()
True
<matplotlib.colorbar.Colorbar at 0x7fabf807fe10>

png

1.3 Remova exemplos contraditórios

Da seção 3.3 Aprendendo a distinguir dígitos de Farhi et al. , filtre o conjunto de dados para remover imagens rotuladas como pertencentes a ambas as classes.

Este não é um procedimento padrão de aprendizado de máquina, mas está incluído no interesse de seguir o artigo.

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)

As contagens resultantes não correspondem aos valores relatados, mas o procedimento exato não é especificado.

Também vale a pena notar aqui que aplicar filtragem de exemplos contraditórios neste ponto não impede totalmente que o modelo receba exemplos de treinamento contraditórios: o próximo passo binariza os dados que causarão mais colisões.

x_train_nocon, y_train_nocon = remove_contradicting(x_train_small, y_train)
Number of unique images: 10387
Number of unique 3s:  4912
Number of unique 6s:  5426
Number of unique contradicting labels (both 3 and 6):  49

Initial number of images:  12049
Remaining non-contradicting unique images:  10338

1.4 Codifique os dados como circuitos quânticos

Para processar imagens usando um computador quântico, Farhi et al. proposto representando cada pixel com um qubit, com o estado dependendo do valor do pixel. O primeiro passo é converter para uma codificação binária.

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)

Se você removesse imagens contraditórias neste ponto, ficaria com apenas 193, provavelmente não o suficiente para um treinamento eficaz.

_ = remove_contradicting(x_train_bin, y_train_nocon)
Number of unique images: 193
Number of unique 3s:  80
Number of unique 6s:  69
Number of unique contradicting labels (both 3 and 6):  44

Initial number of images:  10338
Remaining non-contradicting unique images:  149

Os qubits nos índices de pixel com valores que excedem um limite são girados através de uma porta \(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]

Aqui está o circuito criado para o primeiro exemplo (os diagramas de circuito não mostram qubits com portas zero):

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

svg

Compare este circuito com os índices onde o valor da imagem excede o limite:

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

Converta estes circuitos Cirq em tensores para tfq :

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

2. Rede neural quântica

Há pouca orientação para uma estrutura de circuito quântico que classifica imagens. Como a classificação é baseada na expectativa do qubit de leitura, Farhi et al. proponha o uso de duas portas de qubit, com o qubit de leitura sempre atuado. Isso é semelhante, em alguns aspectos, à execução de um RNN unitário pequeno nos pixels.

2.1 Construir o circuito modelo

Este exemplo a seguir mostra essa abordagem em camadas. Cada camada usa n instâncias do mesmo portão, com cada um dos qubits de dados atuando no qubit de leitura.

Comece com uma classe simples que adicionará uma camada dessas portas a um 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)

Construa uma camada de circuito de exemplo para ver como fica:

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

Agora construa um modelo de duas camadas, correspondendo ao tamanho do circuito de dados, e inclua as operações de preparação e leitura.

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 Envolva o circuito modelo em um modelo tfq-keras

Construa o modelo Keras com os componentes quânticos. Este modelo é alimentado com os "dados quânticos", de x_train_circ , que codificam os dados clássicos. Ele usa uma camada de circuito quântico parametrizado, tfq.layers.PQC , para treinar o circuito modelo, nos dados quânticos.

Para classificar essas imagens, Farhi et al. propôs tomar a expectativa de um qubit de leitura em um circuito parametrizado. A expectativa retorna um valor entre 1 e -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),
])

Em seguida, descreva o procedimento de treinamento para o modelo, usando o método de compile .

Como a leitura esperada está na faixa [-1,1] , otimizar a perda da dobradiça é um ajuste um tanto natural.

Para usar a perda de dobradiça aqui você precisa fazer dois pequenos ajustes. Primeiro converta os rótulos, y_train_nocon , de boolean para [-1,1] , conforme esperado pela perda de dobradiça.

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

Em segundo lugar, use uma métrica de dobradiça_accuracy personalizada que trata corretamente [-1, 1] como o y_true hinge_accuracy tf.losses.BinaryAccuracy(threshold=0.0) espera que y_true seja um booleano e, portanto, não pode ser usado com perda de dobradiça).

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

Treine o modelo quântico

Agora treine o modelo - isso leva cerca de 45 min. Se você não quiser esperar tanto, use um pequeno subconjunto dos dados (conjunto NUM_EXAMPLES=500 , abaixo). Isso realmente não afeta o progresso do modelo durante o treinamento (ele tem apenas 32 parâmetros e não precisa de muitos dados para restringi-los). Usar menos exemplos apenas termina o treinamento mais cedo (5min), mas é executado o suficiente para mostrar que está progredindo nos logs de validação.

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]

Treinar este modelo para convergência deve atingir >85% de precisão no conjunto de teste.

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)
Epoch 1/3
324/324 [==============================] - 68s 207ms/step - loss: 0.6745 - hinge_accuracy: 0.7719 - val_loss: 0.3959 - val_hinge_accuracy: 0.8004
Epoch 2/3
324/324 [==============================] - 68s 209ms/step - loss: 0.3964 - hinge_accuracy: 0.8291 - val_loss: 0.3498 - val_hinge_accuracy: 0.8997
Epoch 3/3
324/324 [==============================] - 66s 204ms/step - loss: 0.3599 - hinge_accuracy: 0.8854 - val_loss: 0.3395 - val_hinge_accuracy: 0.9042
62/62 [==============================] - 3s 41ms/step - loss: 0.3395 - hinge_accuracy: 0.9042

3. Rede neural clássica

Embora a rede neural quântica funcione para esse problema MNIST simplificado, uma rede neural clássica básica pode facilmente superar um QNN nessa tarefa. Após uma única época, uma rede neural clássica pode atingir > 98% de precisão no conjunto de validação.

No exemplo a seguir, uma rede neural clássica é usada para o problema de classificação 3-6 usando toda a imagem 28x28 em vez de subamostrar a imagem. Isso converge facilmente para quase 100% de precisão do conjunto de teste.

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)
95/95 [==============================] - 3s 31ms/step - loss: 0.0400 - accuracy: 0.9842 - val_loss: 0.0057 - val_accuracy: 0.9970
62/62 [==============================] - 0s 3ms/step - loss: 0.0057 - accuracy: 0.9970

O modelo acima tem quase 1,2 milhões de parâmetros. Para uma comparação mais justa, experimente um modelo de 37 parâmetros, nas imagens subamostradas:

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)
Epoch 1/20
81/81 - 1s - loss: 0.6678 - accuracy: 0.6546 - val_loss: 0.6326 - val_accuracy: 0.7358 - 503ms/epoch - 6ms/step
Epoch 2/20
81/81 - 0s - loss: 0.6186 - accuracy: 0.7654 - val_loss: 0.5787 - val_accuracy: 0.7515 - 98ms/epoch - 1ms/step
Epoch 3/20
81/81 - 0s - loss: 0.5629 - accuracy: 0.7861 - val_loss: 0.5247 - val_accuracy: 0.7764 - 104ms/epoch - 1ms/step
Epoch 4/20
81/81 - 0s - loss: 0.5150 - accuracy: 0.8301 - val_loss: 0.4825 - val_accuracy: 0.8196 - 103ms/epoch - 1ms/step
Epoch 5/20
81/81 - 0s - loss: 0.4762 - accuracy: 0.8493 - val_loss: 0.4490 - val_accuracy: 0.8293 - 97ms/epoch - 1ms/step
Epoch 6/20
81/81 - 0s - loss: 0.4438 - accuracy: 0.8527 - val_loss: 0.4216 - val_accuracy: 0.8298 - 99ms/epoch - 1ms/step
Epoch 7/20
81/81 - 0s - loss: 0.4169 - accuracy: 0.8555 - val_loss: 0.3986 - val_accuracy: 0.8313 - 98ms/epoch - 1ms/step
Epoch 8/20
81/81 - 0s - loss: 0.3951 - accuracy: 0.8595 - val_loss: 0.3794 - val_accuracy: 0.8313 - 105ms/epoch - 1ms/step
Epoch 9/20
81/81 - 0s - loss: 0.3773 - accuracy: 0.8596 - val_loss: 0.3635 - val_accuracy: 0.8328 - 98ms/epoch - 1ms/step
Epoch 10/20
81/81 - 0s - loss: 0.3620 - accuracy: 0.8611 - val_loss: 0.3499 - val_accuracy: 0.8333 - 97ms/epoch - 1ms/step
Epoch 11/20
81/81 - 0s - loss: 0.3488 - accuracy: 0.8714 - val_loss: 0.3382 - val_accuracy: 0.8720 - 98ms/epoch - 1ms/step
Epoch 12/20
81/81 - 0s - loss: 0.3372 - accuracy: 0.8831 - val_loss: 0.3279 - val_accuracy: 0.8720 - 95ms/epoch - 1ms/step
Epoch 13/20
81/81 - 0s - loss: 0.3271 - accuracy: 0.8831 - val_loss: 0.3187 - val_accuracy: 0.8725 - 97ms/epoch - 1ms/step
Epoch 14/20
81/81 - 0s - loss: 0.3181 - accuracy: 0.8832 - val_loss: 0.3107 - val_accuracy: 0.8725 - 96ms/epoch - 1ms/step
Epoch 15/20
81/81 - 0s - loss: 0.3101 - accuracy: 0.8833 - val_loss: 0.3035 - val_accuracy: 0.8725 - 96ms/epoch - 1ms/step
Epoch 16/20
81/81 - 0s - loss: 0.3030 - accuracy: 0.8833 - val_loss: 0.2972 - val_accuracy: 0.8725 - 105ms/epoch - 1ms/step
Epoch 17/20
81/81 - 0s - loss: 0.2966 - accuracy: 0.8833 - val_loss: 0.2913 - val_accuracy: 0.8725 - 104ms/epoch - 1ms/step
Epoch 18/20
81/81 - 0s - loss: 0.2908 - accuracy: 0.8928 - val_loss: 0.2861 - val_accuracy: 0.8725 - 104ms/epoch - 1ms/step
Epoch 19/20
81/81 - 0s - loss: 0.2856 - accuracy: 0.8955 - val_loss: 0.2816 - val_accuracy: 0.8725 - 99ms/epoch - 1ms/step
Epoch 20/20
81/81 - 0s - loss: 0.2809 - accuracy: 0.8952 - val_loss: 0.2773 - val_accuracy: 0.8725 - 101ms/epoch - 1ms/step
62/62 [==============================] - 0s 895us/step - loss: 0.2773 - accuracy: 0.8725

4. Comparação

Uma entrada de resolução mais alta e um modelo mais poderoso facilitam esse problema para a CNN. Enquanto um modelo clássico de potência semelhante (~32 parâmetros) treina com uma precisão semelhante em uma fração do tempo. De uma forma ou de outra, a rede neural clássica supera facilmente a rede neural quântica. Para dados clássicos, é difícil superar uma rede neural clássica.

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])
/tmpfs/src/tf_docs_env/lib/python3.7/site-packages/seaborn/_decorators.py:43: FutureWarning: Pass the following variables as keyword args: x, y. From version 0.12, the only valid positional argument will be `data`, and passing other arguments without an explicit keyword will result in an error or misinterpretation.
  FutureWarning
<AxesSubplot:>

png