```python
gpu_info = !nvidia-smi
gpu_info = '\n'.join(gpu_info)
if gpu_info.find('failed') >= 0:
  print('Select the Runtime > "Change runtime type" menu to enable a GPU accelerator, ')
  print('and then re-execute this cell.')
else:
  print(gpu_info)
```

    Tue Jun 29 09:58:26 2021
    +-----------------------------------------------------------------------------+
    | NVIDIA-SMI 465.27       Driver Version: 460.32.03    CUDA Version: 11.2     |
    |-------------------------------+----------------------+----------------------+
    | GPU  Name        Persistence-M| Bus-Id        Disp.A | Volatile Uncorr. ECC |
    | Fan  Temp  Perf  Pwr:Usage/Cap|         Memory-Usage | GPU-Util  Compute M. |
    |                               |                      |               MIG M. |
    |===============================+======================+======================|
    |   0  Tesla P100-PCIE...  Off  | 00000000:00:04.0 Off |                    0 |
    | N/A   38C    P0    26W / 250W |      0MiB / 16280MiB |      0%      Default |
    |                               |                      |                  N/A |
    +-------------------------------+----------------------+----------------------+

    +-----------------------------------------------------------------------------+
    | Processes:                                                                  |
    |  GPU   GI   CI        PID   Type   Process name                  GPU Memory |
    |        ID   ID                                                   Usage      |
    |=============================================================================|
    |  No running processes found                                                 |
    +-----------------------------------------------------------------------------+

```python
from psutil import virtual_memory
ram_gb = virtual_memory().total / 1e9
print('Your runtime has {:.1f} gigabytes of available RAM\n'.format(ram_gb))

if ram_gb < 20:
  print('To enable a high-RAM runtime, select the Runtime > "Change runtime type"')
  print('menu, and then select High-RAM in the Runtime shape dropdown. Then, ')
  print('re-execute this cell.')
else:
  print('You are using a high-RAM runtime!')
```

    Your runtime has 27.3 gigabytes of available RAM

    You are using a high-RAM runtime!

```python
!pip install tensorflow==2.4.1
!pip install tensorflow-quantum

from IPython.display import clear_output
clear_output()
```

```python
import tensorflow as tf
import tensorflow_quantum as tfq

import cirq
import sympy
import numpy as np

# visualization tools
%matplotlib inline
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit

import h5py
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
```

## Loading the Dataset

```python
with h5py.File("/content/drive/My Drive/Projects/GSoC 2021/electron-photon-dataset/photon.hdf5", "r") as f:
  px = np.asarray(f['X'][()], dtype=np.float32)
  py = np.asarray(f['y'][()], dtype=np.float32)
  print("Loaded photon dataset!")

with h5py.File("/content/drive/My Drive/Projects/GSoC 2021/electron-photon-dataset/electron.hdf5", "r") as f:
  ex = np.asarray(f['X'][()], dtype=np.float32)
  ey = np.asarray(f['y'][()], dtype=np.float32)
  print("Loaded electron dataset!")

print("Photon dataset shape:", px.shape, py.shape)
print("Electron dataset shape:", ex.shape, ey.shape)
```

    Loaded photon dataset!
    Loaded electron dataset!
    Photon dataset shape: (249000, 32, 32, 2) (249000,)
    Electron dataset shape: (249000, 32, 32, 2) (249000,)

```python
# photon average image

plt.imshow(np.mean(px[:, :, :, 0], axis=0))
```

```python
# electron average image

plt.imshow(np.mean(ex[:, :, :, 0], axis=0))
```

```python
max_photon, min_photon = np.max(px[:, :, :, 0]), np.min(px[:, :, :, 0])
max_electron, min_electron = np.max(ex[:, :, :, 0]), np.min(ex[:, :, :, 0])

print(max_photon, min_photon)
print(max_electron, min_electron)
max(max_photon, abs(min_photon), max_electron, abs(min_electron))
```

    1.4849443 0.0
    1.431813 0.0

    1.4849443

```python
X = np.concatenate((px[:, :, :, 0], ex[:, :, :, 0]), axis=0)
y = np.concatenate((py, ey), axis=0)

print(X.shape, y.shape)
```

    (498000, 32, 32) (498000,)

## Dataset Preprocessing

Position of maximum value pixel.

```python
print("Proton:", np.argmax(np.mean(px[:, :, :, 0], axis=0)))
print("Electron:", np.argmax(np.mean(ex[:, :, :, 0], axis=0)))
```

    Proton: 528
    Electron: 528

Crop from 32x32 to 16x16, maintaining the maximum value pixel at the center.

```python
center = [int(528/32), 528%32]

img_size = 8

X = X[:, (center[0]-int(img_size/2)):(center[0]+int(img_size/2)), (center[1]-int(img_size/2)):(center[1]+int(img_size/2))]
X.shape
```

    (498000, 8, 8)

Train-Test Split and Scaling

```python
x_train, x_test, y_train, y_test = train_test_split(X, y, test_size=0.15, random_state=2021)

scaler = StandardScaler()
scaler.fit(x_train.reshape(-1, img_size*img_size))
x_train = scaler.transform(x_train.reshape(-1, img_size*img_size))
x_train = x_train.reshape(-1, img_size, img_size)
y_train = tf.keras.utils.to_categorical(y_train)

x_test = scaler.transform(x_test.reshape(-1, img_size*img_size))
x_test = x_test.reshape(-1, img_size, img_size)
y_test = tf.keras.utils.to_categorical(y_test)

print("Train set shape:", x_train.shape, y_train.shape)
print("Test set shape:", x_test.shape, y_test.shape)

plt.plot(scaler.scale_)
```

```python
# photon average image

plt.imshow(np.mean(X[:int(len(X)/2), :, :], axis=0))
```

```python
# electron average image

plt.imshow(np.mean(X[int(len(X)/2):, :, :], axis=0))
```

```python
del px, py, ex, ey
```

## Graph-Convolution Preprocessing

### Adjacency Matrix

```python
img_size = 32
adjacency = np.zeros((img_size**2, img_size**2), dtype=np.float64)
gaussian_scale = np.pi

# k = y-axis of the A matrix
# i = y-axis of the image
# j = x-axis of the image
for k in range(img_size**2):
    for i in range(img_size):
        for j in range(img_size):
            x_1 = k%img_size  # x coordinate of the pixel-k in the A matrix
            x_2 = j  # x coordinate of the image

            y_1 = int(k/img_size)  # y coordinate of the pixel-k in the A matrix
            y_2 = i  # y coordinate of the image

            adjacency[k, i*img_size + j] = np.exp(-np.sqrt((x_1-x_2)**2 + (y_1-y_2)**2)/(gaussian_scale**2))
```

```python
plt.imshow(np.log10(adjacency))
```

### Average Plot of A^2 X

```python
# photon

plt.imshow((adjacency@adjacency@(np.mean(px[:, :, :, 0], axis=0).reshape(-1,))).reshape(32,32))
```

```python
# electron

plt.imshow((adjacency@adjacency@(np.mean(ex[:, :, :, 0], axis=0).reshape(-1,))).reshape(32,32))
```

```python
# photon

fig=plt.figure(figsize=(10, 10))
columns = 5
rows = 3
random_integer = np.random.randint(0, high=len(px), size=columns*rows)
for i in range(1, columns*rows +1):
    img = (adjacency@adjacency@px[random_integer[i-1], :, :, 0].reshape(-1,)).reshape(32,32)
    fig.add_subplot(rows, columns, i)
    plt.imshow(img, cmap='viridis')
plt.show()
```

```python
fig=plt.figure(figsize=(10, 10))
columns = 5
rows = 3
random_integer = np.random.randint(0, high=len(ex), size=columns*rows)
for i in range(1, columns*rows +1):
    img = (adjacency@adjacency@(ex[random_integer[i-1], :, :, 0].reshape(-1,))).reshape(32,32)
    fig.add_subplot(rows, columns, i)
    plt.imshow(img, cmap='viridis')
plt.show()
```

## Building the Networks

```python
def one_qubit_rotation(qubit, symbols):
    """
    Returns Cirq gates that apply a rotation of the bloch sphere about the X,
    Y and Z axis, specified by the values in `symbols`.
    """
    return [cirq.rx(symbols[0])(qubit),
            cirq.ry(symbols[1])(qubit),
            cirq.rz(symbols[2])(qubit)]

```

```python
def entangling_layer(qubits):
    """
    Returns a layer of CZ entangling gates on `qubits` (arranged in a circular topology).
    """
    cz_ops = [cirq.CZ(q0, q1) for q0, q1 in zip(qubits, qubits[1:])]
    cz_ops += ([cirq.CZ(qubits[0], qubits[-1])] if len(qubits) != 2 else [])
    return cz_ops
```

```python
def generate_circuit(qubits, n_layers, filter_size, use_entanglement=True):
    """Prepares a data re-uploading circuit on `qubits` with `n_layers` layers."""
    # Number of qubits
    n_qubits = len(qubits)

    # Number of padding
    # Zero-pad the inputs and params if it is not a multiple of 3
    padding = (3 - ((filter_size*filter_size)%3))%3

    # Sympy symbols for weights and bias parameters
    params = sympy.symbols(f'theta(0:{(filter_size*filter_size + padding)*n_layers*n_qubits})')
    params = np.asarray(params).reshape((n_layers, n_qubits, (filter_size*filter_size + padding)))

    # Sympy symbols for input features
    # Repeat the input features for every layer and qubit
    inputs = sympy.symbols(f'x(0:{(filter_size*filter_size + padding)*n_layers*n_qubits})')
    inputs = np.asarray(inputs).reshape((n_layers, n_qubits, (filter_size*filter_size + padding)))

    # Define circuit
    circuit = cirq.Circuit()
    for l in range(n_layers):
        for gate in range(int(np.ceil((filter_size*filter_size)/3))):
            # Encoding layer
            circuit += cirq.Circuit(one_qubit_rotation(q, inputs[l, i, gate*3:(gate+1)*3]) for i, q in enumerate(qubits))
            # Variational layer
            circuit += cirq.Circuit(one_qubit_rotation(q, params[l, i, gate*3:(gate+1)*3]) for i, q in enumerate(qubits))

        if n_qubits >= 2 and l != (n_layers-1) and use_entanglement:
            circuit += entangling_layer(qubits)

    return circuit, list(params.flat), list(inputs.flat)

```

```python
n_qubits, n_layers = 2, 2
filter_size = 2

qubits = cirq.GridQubit.rect(1, n_qubits)
circuit, _, inputs_ex = generate_circuit(qubits, n_layers, filter_size, use_entanglement=True)
SVGCircuit(circuit)
```

    findfont: Font family ['Arial'] not found. Falling back to DejaVu Sans.

```python
class ReUploadingPQC(tf.keras.layers.Layer):
    def __init__(self, qubits, n_layers, observables, filter_size, use_entanglement=True, name="re-uploading_PQC"):
        super(ReUploadingPQC, self).__init__(name=name)
        self.qubits = qubits
        self.n_layers = n_layers
        self.n_qubits = len(self.qubits)
        self.observables = observables
        self.filter_size = filter_size
        self.use_entanglement = use_entanglement
        self.main_name = name

        # generate the data re-uploading circuit
        circuit, theta_symbols, input_symbols = generate_circuit(self.qubits, n_layers, filter_size, use_entanglement=self.use_entanglement)

        # Define explicit symbol order.
        symbols = [str(symb) for symb in theta_symbols + input_symbols]
        self.indices = tf.constant([sorted(symbols).index(a) for a in symbols])

        # thetas (bias) initialization
        thetas_init = tf.random_uniform_initializer(minval=-np.pi/2, maxval=np.pi/2)
        self.thetas = tf.Variable(
            initial_value=thetas_init(shape=(1, len(theta_symbols)), dtype="float32"),
            trainable=True, name="thetas"
        )

        # weights initialization
        w_init = tf.random_uniform_initializer(minval=-1, maxval=1)
        self.w = tf.Variable(
            initial_value=w_init(shape=(1, len(input_symbols)), dtype="float32"),
            trainable=True, name="weights"
        )

        # dummy inputs initialization
        # using the empty circuits as hacks for ControlledPQC
        self.empty_circuit = tfq.convert_to_tensor([cirq.Circuit()])

        self.computation_layer = tfq.layers.ControlledPQC(circuit, self.observables)

    def get_config(self):

        config = super().get_config().copy()
        config.update({
            'qubits': self.qubits,
            'n_layers': self.n_layers,
            'observables': self.observables,
            'filter_size': self.filter_size,
            'use_entanglement': self.use_entanglement,
            'name': self.main_name,
        })
        return config

    def call(self, inputs):
        batch_size = tf.shape(inputs)[0]

        # Flatten inputs (from 2D images to 1D array)
        inputs_flattened = tf.keras.layers.Flatten()(inputs)

        # Pad the inputs if it is not a multiple of 3
        padding = 3 - inputs_flattened.shape[1]%3
        if padding%3 != 0:
            inputs_flattened = tf.pad(inputs_flattened, tf.constant([[0, 0,], [0, padding]]))

        # Repeat the inputs for every layer and qubit
        inputs_flattened = tf.tile(inputs_flattened, tf.constant([1,self.n_layers*self.n_qubits]))

        # Weight the inputs
        inputs_weighted = tf.math.multiply(self.w, inputs_flattened, name=self.main_name + 'weighted_inputs')

        # Duplicate parameters and dummy inputs
        # One for every batch
        thetas_batch = tf.tile(self.thetas, multiples=[batch_size, 1], name=self.main_name + 'tiled_up_thetas')
        empty_circuit_batch = tf.repeat(self.empty_circuit, repeats=batch_size, name=self.main_name + 'tiled_up_empty_circuits')

        joined_params = tf.concat([thetas_batch, inputs_weighted], axis=-1)
        joined_params = tf.gather(joined_params, self.indices, axis=-1, name=self.main_name + 'joined_params')

        return self.computation_layer([empty_circuit_batch, joined_params])
```

```python
def QConv_layer(qubits, n_layers, observables, filter_size, stride, inputs, use_entanglement=True, conv_id='', name="QConv_layer"):
    iter = int(1+(inputs.shape[1]-filter_size)/stride)

    pqc = ReUploadingPQC(qubits, n_layers, observables, filter_size, use_entanglement=True, name=name+conv_id)
    conv = []
    for i in range(iter):
        for j in range(iter):
            temp = pqc(inputs[:, stride*i:stride*i+filter_size, stride*j:stride*j+filter_size])
            conv += [temp]
    output_concat = tf.keras.layers.Concatenate(axis=1)(conv)
    output_reshape = tf.keras.layers.Reshape((iter, iter))(output_concat)

    return output_reshape
```

```python
filter_size = 3
stride = 1

int(1+(6-filter_size)/stride)
```

    4

```python
n_qubits, n_layers = 1, 1
qubits = cirq.GridQubit.rect(1, n_qubits)

from functools import reduce
#ops = [cirq.Z(q) for q in qubits]
#observables = [reduce((lambda x, y: x * y), ops)] # Z_0*Z_1*...*Z_n_qubits
observables = [cirq.Z(qubits[-1])]  # Measure only the last qubit

input_tensor = tf.keras.Input(shape=(8,8, ), dtype=tf.dtypes.float32, name='input')

filter_size = [3, 3]
stride = [1, 1]

conv_1 = QConv_layer(qubits, n_layers, observables, filter_size[0], stride[0], input_tensor, use_entanglement=True, conv_id='1')
conv_2 = QConv_layer(qubits, n_layers, observables, filter_size[1], stride[1], conv_1, use_entanglement=True, conv_id='2')
#conv_3 = QConv_layer(qubits, n_layers, observables, filter_size, stride, conv_2, use_entanglement=True, conv_id='3')

conv_output = tf.keras.layers.Flatten()(conv_2)

dense_1 = tf.keras.layers.Dense(8, activation='relu')(conv_output)
dense_2 = tf.keras.layers.Dense(2, activation='softmax')(dense_1)
#dense_3 = tf.keras.layers.Dense(2, activation='softmax')(dense_2)

model = tf.keras.Model(inputs=[input_tensor], outputs=dense_2)
```

```python
model.summary()
```

    Model: "model_3"
    __________________________________________________________________________________________________
    Layer (type)                    Output Shape         Param #     Connected to
    ==================================================================================================
    input (InputLayer)              [(None, 8, 8)]       0
    __________________________________________________________________________________________________
    tf.__operators__.getitem_156 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_157 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_158 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_159 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_160 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_161 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_162 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_163 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_164 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_165 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_166 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_167 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_168 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_169 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_170 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_171 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_172 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_173 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_174 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_175 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_176 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_177 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_178 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_179 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_180 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_181 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_182 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_183 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_184 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_185 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_186 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_187 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_188 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_189 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_190 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_191 (S (None, 3, 3)         0           input[0][0]
    __________________________________________________________________________________________________
    QConv_layer1 (ReUploadingPQC)   (None, 1)            18          tf.__operators__.getitem_156[0][0
                                                                     tf.__operators__.getitem_157[0][0
                                                                     tf.__operators__.getitem_158[0][0
                                                                     tf.__operators__.getitem_159[0][0
                                                                     tf.__operators__.getitem_160[0][0
                                                                     tf.__operators__.getitem_161[0][0
                                                                     tf.__operators__.getitem_162[0][0
                                                                     tf.__operators__.getitem_163[0][0
                                                                     tf.__operators__.getitem_164[0][0
                                                                     tf.__operators__.getitem_165[0][0
                                                                     tf.__operators__.getitem_166[0][0
                                                                     tf.__operators__.getitem_167[0][0
                                                                     tf.__operators__.getitem_168[0][0
                                                                     tf.__operators__.getitem_169[0][0
                                                                     tf.__operators__.getitem_170[0][0
                                                                     tf.__operators__.getitem_171[0][0
                                                                     tf.__operators__.getitem_172[0][0
                                                                     tf.__operators__.getitem_173[0][0
                                                                     tf.__operators__.getitem_174[0][0
                                                                     tf.__operators__.getitem_175[0][0
                                                                     tf.__operators__.getitem_176[0][0
                                                                     tf.__operators__.getitem_177[0][0
                                                                     tf.__operators__.getitem_178[0][0
                                                                     tf.__operators__.getitem_179[0][0
                                                                     tf.__operators__.getitem_180[0][0
                                                                     tf.__operators__.getitem_181[0][0
                                                                     tf.__operators__.getitem_182[0][0
                                                                     tf.__operators__.getitem_183[0][0
                                                                     tf.__operators__.getitem_184[0][0
                                                                     tf.__operators__.getitem_185[0][0
                                                                     tf.__operators__.getitem_186[0][0
                                                                     tf.__operators__.getitem_187[0][0
                                                                     tf.__operators__.getitem_188[0][0
                                                                     tf.__operators__.getitem_189[0][0
                                                                     tf.__operators__.getitem_190[0][0
                                                                     tf.__operators__.getitem_191[0][0
    __________________________________________________________________________________________________
    concatenate_6 (Concatenate)     (None, 36)           0           QConv_layer1[0][0]
                                                                     QConv_layer1[1][0]
                                                                     QConv_layer1[2][0]
                                                                     QConv_layer1[3][0]
                                                                     QConv_layer1[4][0]
                                                                     QConv_layer1[5][0]
                                                                     QConv_layer1[6][0]
                                                                     QConv_layer1[7][0]
                                                                     QConv_layer1[8][0]
                                                                     QConv_layer1[9][0]
                                                                     QConv_layer1[10][0]
                                                                     QConv_layer1[11][0]
                                                                     QConv_layer1[12][0]
                                                                     QConv_layer1[13][0]
                                                                     QConv_layer1[14][0]
                                                                     QConv_layer1[15][0]
                                                                     QConv_layer1[16][0]
                                                                     QConv_layer1[17][0]
                                                                     QConv_layer1[18][0]
                                                                     QConv_layer1[19][0]
                                                                     QConv_layer1[20][0]
                                                                     QConv_layer1[21][0]
                                                                     QConv_layer1[22][0]
                                                                     QConv_layer1[23][0]
                                                                     QConv_layer1[24][0]
                                                                     QConv_layer1[25][0]
                                                                     QConv_layer1[26][0]
                                                                     QConv_layer1[27][0]
                                                                     QConv_layer1[28][0]
                                                                     QConv_layer1[29][0]
                                                                     QConv_layer1[30][0]
                                                                     QConv_layer1[31][0]
                                                                     QConv_layer1[32][0]
                                                                     QConv_layer1[33][0]
                                                                     QConv_layer1[34][0]
                                                                     QConv_layer1[35][0]
    __________________________________________________________________________________________________
    reshape_6 (Reshape)             (None, 6, 6)         0           concatenate_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_192 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_193 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_194 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_195 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_196 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_197 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_198 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_199 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_200 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_201 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_202 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_203 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_204 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_205 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_206 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    tf.__operators__.getitem_207 (S (None, 3, 3)         0           reshape_6[0][0]
    __________________________________________________________________________________________________
    QConv_layer2 (ReUploadingPQC)   (None, 1)            18          tf.__operators__.getitem_192[0][0
                                                                     tf.__operators__.getitem_193[0][0
                                                                     tf.__operators__.getitem_194[0][0
                                                                     tf.__operators__.getitem_195[0][0
                                                                     tf.__operators__.getitem_196[0][0
                                                                     tf.__operators__.getitem_197[0][0
                                                                     tf.__operators__.getitem_198[0][0
                                                                     tf.__operators__.getitem_199[0][0
                                                                     tf.__operators__.getitem_200[0][0
                                                                     tf.__operators__.getitem_201[0][0
                                                                     tf.__operators__.getitem_202[0][0
                                                                     tf.__operators__.getitem_203[0][0
                                                                     tf.__operators__.getitem_204[0][0
                                                                     tf.__operators__.getitem_205[0][0
                                                                     tf.__operators__.getitem_206[0][0
                                                                     tf.__operators__.getitem_207[0][0
    __________________________________________________________________________________________________
    concatenate_7 (Concatenate)     (None, 16)           0           QConv_layer2[0][0]
                                                                     QConv_layer2[1][0]
                                                                     QConv_layer2[2][0]
                                                                     QConv_layer2[3][0]
                                                                     QConv_layer2[4][0]
                                                                     QConv_layer2[5][0]
                                                                     QConv_layer2[6][0]
                                                                     QConv_layer2[7][0]
                                                                     QConv_layer2[8][0]
                                                                     QConv_layer2[9][0]
                                                                     QConv_layer2[10][0]
                                                                     QConv_layer2[11][0]
                                                                     QConv_layer2[12][0]
                                                                     QConv_layer2[13][0]
                                                                     QConv_layer2[14][0]
                                                                     QConv_layer2[15][0]
    __________________________________________________________________________________________________
    reshape_7 (Reshape)             (None, 4, 4)         0           concatenate_7[0][0]
    __________________________________________________________________________________________________
    flatten_159 (Flatten)           (None, 16)           0           reshape_7[0][0]
    __________________________________________________________________________________________________
    dense_6 (Dense)                 (None, 8)            136         flatten_159[0][0]
    __________________________________________________________________________________________________
    dense_7 (Dense)                 (None, 2)            18          dense_6[0][0]
    ==================================================================================================
    Total params: 190
    Trainable params: 190
    Non-trainable params: 0
    __________________________________________________________________________________________________

```python
model(x_train[0:3])
```

    <tf.Tensor: shape=(3, 2), dtype=float32, numpy=
    array([[0.70037377, 0.29962617],
           [0.73294896, 0.26705104],
           [0.83659154, 0.16340846]], dtype=float32)>

```python
# Training parameters
BATCH_SIZE = 128  # orig paper trained all networks with batch_size=128
EPOCHS = 200 # 200

# Print parameters for sanity check
print("Batch size, epochs:", BATCH_SIZE, EPOCHS)
```

    Batch size, epochs: 128 200

```python
def lr_schedule(epoch):
    """Learning Rate Schedule

    Learning rate is scheduled to be reduced after 80, 120, 160, 180 epochs.
    Called automatically every epoch as part of callbacks during training.

    # Arguments
        epoch (int): The number of epochs

    # Returns
        lr (float32): learning rate
    """
    lr = 1e-3
    if epoch > 180:
        lr *= 0.5e-3
    elif epoch > 160:
        lr *= 1e-3
    elif epoch > 120:
        lr *= 1e-2
    elif epoch > 80:
        lr *= 1e-1
    print('Learning rate: ', lr)
    return lr
```

```python
model.compile(loss='categorical_crossentropy',
              optimizer=tf.keras.optimizers.Adam(lr=lr_schedule(0)),
              metrics=['accuracy', tf.keras.metrics.AUC()])
```

    Learning rate:  0.001

```python
# Prepare callbacks for model saving and for learning rate adjustment.
lr_scheduler = tf.keras.callbacks.LearningRateScheduler(lr_schedule)

lr_reducer = tf.keras.callbacks.ReduceLROnPlateau(factor=np.sqrt(0.1),
                               cooldown=0,
                               patience=5,
                               min_lr=0.5e-6)

cp_filepath = "/content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-{epoch:02d}epoch.h5"
checkpoint_callback_epoch = tf.keras.callbacks.ModelCheckpoint(
    cp_filepath, monitor='val_loss', verbose=1, save_best_only=False,
    save_weights_only=True, mode='auto', save_freq='epoch'
)

callbacks = [lr_reducer, lr_scheduler, checkpoint_callback_epoch]
```

```python
import time
start_time = time.time()

last_epoch = 0

H = model.fit(x_train, y_train, initial_epoch=last_epoch,
              batch_size=BATCH_SIZE,
              epochs=10,
              validation_data=(x_test, y_test),
              shuffle=True,
              callbacks=callbacks)
```

    Epoch 1/10
    Learning rate:  0.001
    3308/3308 [==============================] - 4330s 1s/step - loss: 0.6823 - accuracy: 0.5682 - auc_3: 0.5949 - val_loss: 0.6554 - val_accuracy: 0.6115 - val_auc_3: 0.6551

    Epoch 00001: saving model to /content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-01epoch.h5
    Epoch 2/10
    Learning rate:  0.001
    3308/3308 [==============================] - 4255s 1s/step - loss: 0.6510 - accuracy: 0.6225 - auc_3: 0.6651 - val_loss: 0.6390 - val_accuracy: 0.6372 - val_auc_3: 0.6863

    Epoch 00002: saving model to /content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-02epoch.h5
    Epoch 3/10
    Learning rate:  0.001
    3308/3308 [==============================] - 4247s 1s/step - loss: 0.6343 - accuracy: 0.6475 - auc_3: 0.6951 - val_loss: 0.6292 - val_accuracy: 0.6481 - val_auc_3: 0.7016

    Epoch 00003: saving model to /content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-03epoch.h5
    Epoch 4/10
    Learning rate:  0.001
    3308/3308 [==============================] - 4326s 1s/step - loss: 0.6258 - accuracy: 0.6560 - auc_3: 0.7074 - val_loss: 0.6193 - val_accuracy: 0.6628 - val_auc_3: 0.7164

    Epoch 00004: saving model to /content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-04epoch.h5
    Epoch 5/10
    Learning rate:  0.001
    3308/3308 [==============================] - 4311s 1s/step - loss: 0.6187 - accuracy: 0.6617 - auc_3: 0.7170 - val_loss: 0.6154 - val_accuracy: 0.6662 - val_auc_3: 0.7220

    Epoch 00005: saving model to /content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-05epoch.h5
    Epoch 6/10
    Learning rate:  0.001
    3308/3308 [==============================] - 4262s 1s/step - loss: 0.6151 - accuracy: 0.6667 - auc_3: 0.7222 - val_loss: 0.6137 - val_accuracy: 0.6704 - val_auc_3: 0.7250

    Epoch 00006: saving model to /content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-06epoch.h5
    Epoch 7/10
    Learning rate:  0.001
    3308/3308 [==============================] - 4252s 1s/step - loss: 0.6122 - accuracy: 0.6699 - auc_3: 0.7261 - val_loss: 0.6146 - val_accuracy: 0.6645 - val_auc_3: 0.7231

    Epoch 00007: saving model to /content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-07epoch.h5
    Epoch 8/10
    Learning rate:  0.001
    3308/3308 [==============================] - 4253s 1s/step - loss: 0.6109 - accuracy: 0.6713 - auc_3: 0.7276 - val_loss: 0.6164 - val_accuracy: 0.6669 - val_auc_3: 0.7214

    Epoch 00008: saving model to /content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-08epoch.h5
    Epoch 9/10
    Learning rate:  0.001
    3308/3308 [==============================] - 4253s 1s/step - loss: 0.6097 - accuracy: 0.6729 - auc_3: 0.7295 - val_loss: 0.6089 - val_accuracy: 0.6743 - val_auc_3: 0.7312

    Epoch 00009: saving model to /content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-09epoch.h5
    Epoch 10/10
    Learning rate:  0.001
    3308/3308 [==============================] - 4258s 1s/step - loss: 0.6084 - accuracy: 0.6747 - auc_3: 0.7310 - val_loss: 0.6119 - val_accuracy: 0.6689 - val_auc_3: 0.7273

    Epoch 00010: saving model to /content/drive/MyDrive/Projects/GSoC 2021/Model_Hybrid/QCNN_Type1_L1Q1-10epoch.h5

```python
print(H)
```

```python
from matplotlib import pyplot as plt

auc_test = [0.6551, 0.6863,0.7016, 0.7164, 0.7220, 0.7250, 0.7231, 0.7214, 0.7312, 0.7273]
auc_train = [0.5949, 0.6651, 0.6951, 0.7074, 0.7170, 0.7222, 0.7261, 0.7276, 0.7295, 0.7310]

plt.plot(auc_train, label='Train')
plt.plot(auc_test, label='Test')

plt.xlabel("Epoch")
plt.ylabel("AUC")
plt.legend()

plt.show()
```

```python
# ResNet: +/- 250k trainable params, input 32x32, 200 epoch (2-3 hours), AUC 0.81
```

```python

```

```python

```

```python

```


```python
pip install tensorflow==2.4.1 tensorflow-quantum
```

```python
# Update package resources to account for version changes.
import importlib, pkg_resources
importlib.reload(pkg_resources)
```

    <module 'pkg_resources' from '/usr/local/lib/python3.7/dist-packages/pkg_resources/__init__.py'>

```python

```

```python

```

```python

```

```python

from google.colab import drive
drive.mount('/content/drive')
```

    Mounted at /content/drive

```python
import sys
sys.path.append('/content/drive/My Drive')
```

```python
import eecs598
import torch
import torchvision
import matplotlib.pyplot as plt
import statistics
import numpy as np
```

```python
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
```

```python

# Control qrid size for visualization
plt.rcParams['figure.figsize'] = (10.0, 8.0)
plt.rcParams['font.size'] = 16
```

```python
x_train, y_train, x_test, y_test = eecs598.data.cifar10()

print('Training set:', )
print('  data shape:', x_train.shape)
print('  labels shape: ', y_train.shape)
print('Test set:')
print('  data shape: ', x_test.shape)
print('  labels shape', y_test.shape)
```

    Downloading https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz to ./cifar-10-python.tar.gz

      0%|          | 0/170498071 [00:00<?, ?it/s]

    Extracting ./cifar-10-python.tar.gz to .
    Training set:
      data shape: torch.Size([50000, 3, 32, 32])
      labels shape:  torch.Size([50000])
    Test set:
      data shape:  torch.Size([10000, 3, 32, 32])
      labels shape torch.Size([10000])

```python
import random
from torchvision.utils import make_grid

classes = ['plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck']
samples_per_class = 12
samples = []
for y, cls in enumerate(classes):
    plt.text(-4, 34 * y + 18, cls, ha='right')
    idxs, = (y_train == y).nonzero(as_tuple=True)
    for i in range(samples_per_class):
        idx = idxs[random.randrange(idxs.shape[0])].item()
        samples.append(x_train[idx])
img = torchvision.utils.make_grid(samples, nrow=samples_per_class)
plt.imshow(eecs598.tensor_to_image(img))
plt.axis('off')
plt.show()
```

```python

```

```python
from tensorflow.keras.datasets import cifar10
(train_images, train_labels), (test_images, test_labels) = cifar10.load_data()
```

    Downloading data from https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz
    170500096/170498071 [==============================] - 6s 0us/step

```python
y_train=train_labels.flatten()
y_test=test_labels.flatten()
# We have reduced the dimension
```

```python
x_train=tf.image.rgb_to_grayscale(train_images)
x_test=tf.image.rgb_to_grayscale(test_images)
#to convert grayscale
```

```python
def filter_36(x, y):
    keep = (y == 3) | (y == 6)
    x, y = x[keep], y[keep]
    y = y == 3
    return x,y
```

```python
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: 10000
    Number of filtered test examples: 2000

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

```python
x_train_s = tf.image.resize(x_train, (4,4)).numpy()
x_test_s = tf.image.resize(x_test, (4,4)).numpy()
```

```python

THRESHOLD = 0.5

x_train_bin = np.array(x_train_s > THRESHOLD, dtype=np.float32)
x_test_bin = np.array(x_test_s > THRESHOLD, dtype=np.float32)
```

```python
qubits = cirq.GridQubit.rect(4, 4)

```

```python
xx=cirq.Y(qubits[1])

```

```python
cirq.X(qubits[1])
cirq.Y(qubits[1])
cirq.Z
```

    Y((0, 1))

```python
np.random.uniform(size=(4, 3))
```

    array([[0.7967969 , 0.30777665, 0.44788952],
           [0.18320573, 0.26701107, 0.49590493],
           [0.05564715, 0.00566145, 0.81495824],
           [0.77629675, 0.28633108, 0.49930407]])

```python
cirq.Circuit(cirq.X(qubits[1]), cirq.Y(qubits[1]))
```

(0, 1): ───X───Y───

```python

```

```python
def convert_to_circuit(image):
    """Encode truncated classical image into quantum datapoint."""
    values = np.ndarray.flatten(image)
    qubits = cirq.GridQubit.rect(6, 6)
    circuit = cirq.Circuit()
    for i, value in enumerate(values):
        if value:
            cirq.X(qubits[i])
            circuit.append((cirq.X(qubits[i]), cirq.Y(qubits[i]), cirq.Z(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]
```

```python
SVGCircuit(x_train_circ[0])

```

    findfont: Font family ['Arial'] not found. Falling back to DejaVu Sans.

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

```python
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)
```

```python
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)
```

```python
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)
```

```python
model_circuit, model_readout = create_quantum_model()
```

```python
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),
])
```

```python
y_train_hinge = 2.0*y_train-1.0
y_test_hinge = 2.0*y_test-1.0

```

```python
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)
```

```python
model.compile(
    loss=tf.keras.losses.Hinge(),
    optimizer=tf.keras.optimizers.Adam(),
    metrics=[hinge_accuracy])

```

```python
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

```python
EPOCHS = 3
BATCH_SIZE = 128

NUM_EXAMPLES = len(x_train_tfcirc)

```

```python
x_train_tfcirc_sub = x_train_tfcirc[:NUM_EXAMPLES]
y_train_hinge_sub = y_train_hinge[:NUM_EXAMPLES]

```

```python
import time
start_time = time.time()
```

```python
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
     12/313 [>.............................] - ETA: 3:09:25 - loss: 1.0009 - hinge_accuracy: 0.5213