Quantum Convolutional Neural Networks with Cirq and Tensorflow-Quantum
Qui vediamo come implementare una CNN per classificare le immagini di gatti e cani con il Quantum computing.
Dopo aver scaricato diverse centinaia di immagini di cani e gatti si procede nella definizione degli elementi necessari che sono:
- Cluster State
- Dataset input
- Quantum Convolution
- Quantum Pooling
- PQC (Parametrized Quantum Circuit Layer)
- Modello

Programma in Python
!pip install cirq
!pip install tensorflow==2.1.0
!pip install tensorflow_quantum

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
from tensorflow.keras.preprocessing.image import ImageDataGenerator
import os

nr = 2000
epochs = 15
IMG_HEIGHT = 4
IMG_WIDTH = 4
URL = 'https://storage.googleapis.com/mledu-datasets/cats_and_dogs_filtered.zip'
path = tf.keras.utils.get_file('cats_and_dogs.zip', origin=URL, extract=True)
PATH = os.path.join(os.path.dirname(path), 'cats_and_dogs_filtered')
percorso = os.path.join(PATH, 'train')
percorso_cats = os.path.join(percorso, 'cats')
percorso_dogs = os.path.join(percorso, 'dogs')
num_cats = len(os.listdir(percorso_cats )
num_dogs = len(os.listdir(percorso_dogs ))

total_train = num_cats + num_dogs
train_data = train_image_generator.flow_from_directory(batch_size=nr,
directory=percorso,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH),
class_mode='binary')
esempio_image, _ = next(train_data)
def plotImages(images):
fig, axes = plt.subplots(1, 5, figsize=(28,28))
axes = axes.flatten()
for img, ax in zip( images, axes):
ax.imshow(img)
ax.axis('off')
plt.tight_layout()
plt.show()

plotImages(esempio_images[:5])



#data aumentation rotazione-flip-zoom
image_generata = ImageDataGenerator(
rescale=1./255,
rotation_range=45,
width_shift_range=.15,
height_shift_range=.15,
horizontal_flip=True,
zoom_range=0.5
)

train_image = image_generata.flow_from_directory(batch_size=batch_size,
directory=percorso,
shuffle=True,
target_size=(IMG_HEIGHT, IMG_WIDTH),
class_mode='binary')
#carico dati images e labels
XX,yy = next(train_image)

XX= XX[:,:,:,0] #elimino ultima dim
XX= XX[..., np.newaxis] #aggiungo dim vuota

def genera_data(x,y):
#creo input per modello inserendo dati immagini
#circuito creato convertito in tensor x modello
qubits = cirq.GridQubit.rect(1, 16)
nr = len(x)
train = []
labels = []
for n in range(nr):
circuit = cirq.Circuit()
beta = np.ndarray.flatten(x)
for i,bit in enumerate(qubits):
circuit += cirq.H(bit)
circuit += cirq.ry(beta[i])(bit)
circuit += cirq.rz(beta[i])(bit)
circuit += cirq.rx(beta[i])(bit)
train.append(circuit)
labels.append(1 if (y[n] == 1 ) else -1)
perc = int(len(train) * 0.7)
x_train = train[:perc]
test= train[perc:]
#print(x_train[0])
train_label = labels[:perc]
test_label = labels[perc:]
return tfq.convert_to_tensor(x_train), np.array(train_label), \
tfq.convert_to_tensor(test), np.array(test_label)

#creo elenchi training set a test set e label for training e test
tm, tlm, teem, telm = genera_data(XX,yy)


One qubit unitary esegue una rotazione in base al valore in sumbol sugli assi x,y, ed z Two qubit unitary costruisce un parameterized two qubit unitary The two_qubit_pool rappresenta un CNOT usando il one_qubit_unitary sul control e target qubits
def one_qubit_unitary(bit, symbols):
"""creo circuit con rotazione asse X,Y,Z"""
return cirq.Circuit(
cirq.X(bit)**symbols[0],
cirq.Y(bit)**symbols[1],
cirq.Z(bit)**symbols[2])

SVGCircuit(one_qubit_unitary(cirq.GridQubit(0, 0), sympy.symbols('x0:3')))


def two_qubit_unitary(bits, symbols):
"""Creo circuit 2 qubit unitary."""
circuit = cirq.Circuit()
circuit += one_qubit_unitary(bits[0], symbols[0:3])
circuit += one_qubit_unitary(bits[1], symbols[3:6])
circuit += [cirq.ZZ(*bits)**symbols[7]]
circuit += [cirq.YY(*bits)**symbols[8]]
circuit += [cirq.XX(*bits)**symbols[9]]
circuit += one_qubit_unitary(bits[0], symbols[9:12])
circuit += one_qubit_unitary(bits[1], symbols[12:])
return circuit



def two_qubit_pool(source_qubit, sink_qubit, symbols):
""""circuit con parameterized pooling ridurre entanglement da 2 qubits a 1"""
pool_circuit = cirq.Circuit()
sink_basis_selector = one_qubit_unitary(sink_qubit, symbols[0:3])
source_basis_selector = one_qubit_unitary(source_qubit, symbols[3:6])
pool_circuit.append(sink_basis_selector)
pool_circuit.append(source_basis_selector)
pool_circuit.append(cirq.CNOT(control=source_qubit, target=sink_qubit))
pool_circuit.append(sink_basis_selector**-1)
return pool_circuit




Qui il Quantum_conv_circuit applica la convolution che prende in input n qubits e restituisce in output n/2 qubits
def quantum_conv_circuit(bits, symbols):
"""Quantum Convolution Layer two_qubit_unitary ai qubits pari"""
circuit = cirq.Circuit()
for first, second in zip(bits[0::2], bits[1::2]):
circuit += two_qubit_unitary([first, second], symbols)
for first, second in zip(bits[1::2], bits[2::2] + [bits[0]]):
circuit += two_qubit_unitary([first, second], symbols)
return circuit

SVGCircuit( quantum_conv_circuit(cirq.GridQubit.rect(1, 4), sympy.symbols('x0:15')))




Ora si definisce il Quantum Pooling Circuit che prende in input n qubits(elaborati prima da quantum_conv_circuit) e restituisce in output n/2 qubits
#Test quantum_pool_circuit
test_bits = cirq.GridQubit.rect(1, 4)

SVGCircuit(quantum_pool_circuit(test_bits[:2], test_bits[2:], sympy.symbols('x0:6')))



def quantum_pool_circuit(source_bits, sink_bits, symbols):
"""quantum pooling operation tries to learn relevant information from two qubits onto 1"""
circuit = cirq.Circuit()
for source, sink in zip(source_bits, sink_bits):
circuit += two_qubit_pool(source, sink, symbols)
return circuit

testQuantum = cirq.GridQubit.rect(1, 16)

SVGCircuit(
quantum_pool_circuit(testQuantum[:8], testQuantum[8:], sympy.symbols('x0:6')))

#Nell'immagine sottostante si riproduce una parte del circuito creato con quantum_pool_circuit




Ora si definisce il CLUSTER STATE che poi verrĂ  inserito nel modello TFQ con la funzione tfq.layers.AddCircuit

def clusterStateCircuit(qubits):
circuit = cirq.Circuit()
circuit.append(cirq.H.on_each(bits))
for bit1, bit2 in zip(bits, bits[1:] + [bits[0]]):
circuit.append(cirq.CZ(bit1, bit2))
return circuit

#Custom accuracy metric - per valutare accuracy del modello
@tf.function
def custom_accuracy(y_true, y_pred):
y_true = tf.squeeze(y_true)
y_pred = tf.map_fn(lambda x: 1.0 if x >= 0 else -1.0, y_pred)
return tf.keras.backend.mean(tf.keras.backend.equal(y_true, y_pred))


#Definizione READ OUT(output)
cs = cirq.GridQubit.rect(1, 16)
#prendo valori degli ultimi 8 bit con Measurement del circuito
readouts = [cirq.Z(bit) for bit in cs[8:]]

def readoutModel(qubits):
"""Sequenza di convolution and pooling
circuit = cirq.Circuit()
symbols = sympy.symbols('qconv0:63')
#sympy.Symbols per utilizzare diversi parametri
circuit += quantum_conv_circuit(qubits, symbols[0:15])
circuit += quantum_pool_circuit(qubits[:4], qubits[4:],
symbols[15:21])
circuit += quantum_conv_circuit(qubits[4:], symbols[21:36])
circuit += quantum_pool_circuit(qubits[4:6], qubits[6:],
symbols[36:42])
circuit += quantum_conv_circuit(qubits[6:], symbols[42:57])
circuit += quantum_pool_circuit([qubits[6]], [qubits[7]],
symbols[57:63])
return circuit

#definizione dell'input come stringa nel modello
inputModel = tf.keras.Input(shape=(), dtype=tf.dtypes.string)

#Con layer ADDCircuit inseriamo all'inizio del circuito i gates H e ry e l'input definito
clusterStateModel = tfq.layers.AddCircuit()(
inputModel, prepend=clusterStateCircuit(cs))

#layer PQC
quantumModel = tfq.layers.PQC(
readoutModel(cluster_state), readouts)(clusterStateModel)
#hidden layer
HLayer = tf.keras.layers.Dense(32)(quantumModel)
HLayer1 = tf.keras.layers.Dense(64)(HLayer)
#output layer
OLayer = tf.keras.layers.Dense(1)(HLayer1)

#Creazione modello con indicazione input ed output
model = tf.keras.Model(inputs=[inputModel], outputs=[OLayer])

#Visualizzazione architettura del modello creato
tf.keras.utils.plot_model(model,
show_shapes=True,
show_layer_names=True,
dpi=70)

model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=0.0002),
loss=tf.losses.mse,
metrics=[custom_accuracy])

#elaborazione del modello
history = model.fit(x=tm,
y=tlm,
batch_size=64,
epochs=5,
verbose=1,
validation_data=(teem,
telm))

plt.plot(history.history['loss'], label='Training')
plt.plot(history.history['val_loss'], label='Validation')
plt.title('Training Quantum CNN to Classification Cats and Dogs')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()


plt.plot(history.history['custom_accuracy'][:],label='Training')
plt.plot(history.history['val_custom_accuracy'] [:],label='Testing')
plt.title('Training Quantum CNN to Classification Cats and Dogs')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()