<jupyter_start><jupyter_text>Authors: <NAME>, <NAME>, <NAME> (in alphabetical order of last name)In this notebook we use tensorflow-quantum to train a quantum neural network (QNN) model for breast cancer analysis. We use the freely available breast cancer dataset (with labels) for this purpose. To reduce complexity we have restricted our attention to the top 4 most relevant features. This was ascertained separately via 'feature selection'. After standard preprocessing we prepared two training datasets as follows:1. 'Amplitude Encoding' approach - each feature is modeled by one qubit. After rescaling all input values are between 0 and 1. So we apply the 'rotation along y-axis' proportional to the input value to each qubit state.2. 'Binary Encoding' approach - each feature is modeled by one qubit. After rescaling all input values are between 0 and 1. We define an intermediate theshold value and map each input value less than the threshold value to 0 and greater than the threshold value to 1. In this manner we obtain a binary dataset and then we apply the X-gate to those qubits, whose corresponding input value is one.Finally, we adapt the QNN construction in this tutorial https://www.tensorflow.org/quantum/tutorials/mnist to our setting and compare the results of the two approaches.<jupyter_code>import pandas as pd
import tensorflow as tf
import tensorflow_quantum as tfq
import time
import cirq
import sympy
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
from cirq.contrib.svg import SVGCircuit
# The breast cancer dataset can be obtained from here - https://github.com/snigdhayan/NeuralNetworks/blob/master/Ludwig/breast_cancer_dataset.csv
df = pd.read_csv('./breast_cancer_dataset.csv')
df.head()
scaling_table = {}
for column in df:
    column_max = df[column].max()
    column_min = df[column].min()
    df[column] = (df[column] - column_min) / (column_max - column_min)
    scaling_table[column] = {'min': column_min, 'max': column_max}
df.head()
y_columns = df['label'].values
THRESHOLD = 0.5

worst_concave_points = df['worst_concave_points']
worst_perimeter = df['worst_perimeter']
mean_concave_points = df['mean_concave_points']
worst_radius = df['worst_radius']

worst_concave_points_bin = df['worst_concave_points'].apply(lambda x : 0 if x < THRESHOLD else 1)
worst_perimeter_bin = df['worst_perimeter'].apply(lambda x : 0 if x < THRESHOLD else 1)
mean_concave_points_bin = df['mean_concave_points'].apply(lambda x : 0 if x < THRESHOLD else 1)
worst_radius_bin = df['worst_radius'].apply(lambda x : 0 if x < THRESHOLD else 1)

sel_df = pd.concat([worst_concave_points, worst_perimeter, mean_concave_points, worst_radius], axis=1)
sel_df.rename(columns={0: 'wcp', 1: 'wp', 2: 'mcp', 3: 'wr'}, inplace=True)

sel_df_bin = pd.concat([worst_concave_points_bin, worst_perimeter_bin, mean_concave_points_bin, worst_radius_bin], axis=1)#pd.concat([mean,error,worst], axis=1)
sel_df_bin.rename(columns={0: 'wcp', 1: 'wp', 2: 'mcp', 3: 'wr'}, inplace=True)

train_test_split = 400
y_train = y_columns.reshape(1,-1)[0]
y_test = y_train[train_test_split:]
y_train = y_train[:train_test_split]

x_train = sel_df.values
x_test = x_train[train_test_split:]
x_train = x_train[:train_test_split]

x_train_bin = sel_df_bin.values
x_test_bin = x_train_bin[train_test_split:]
x_train_bin = x_train_bin[:train_test_split]

def convert_to_circuit(data):
    """Encode prepared data into quantum datapoint - Amplitude Encoding."""
    values = np.ndarray.flatten(data)
    qubits = cirq.GridQubit.rect(4, 1)
    circuit = cirq.Circuit()
    for i, value in enumerate(values):
        rad = (value) * np.pi
        circuit.append(cirq.ry(rad)(qubits[i]))
            
    return circuit
def convert_to_circuit_bin(data):
    """Encode prepared data into quantum datapoint - Binary Encoding."""
    values = np.ndarray.flatten(data)
    qubits = cirq.GridQubit.rect(4, 1)
    circuit = cirq.Circuit()
    for i, value in enumerate(values):
        if value == 1:
            circuit.append(cirq.X(qubits[i]))
            
    return circuit
x_train_circ = [convert_to_circuit(x) for x in x_train]
x_test_circ = [convert_to_circuit(x) for x in x_test]

SVGCircuit(x_train_circ[1])
x_train_circ_bin = [convert_to_circuit_bin(x) for x in x_train_bin]
x_test_circ_bin = [convert_to_circuit_bin(x) for x in x_test_bin]

SVGCircuit(x_train_circ_bin[1])
x_train_tfcirc = tfq.convert_to_tensor(x_train_circ)
x_test_tfcirc = tfq.convert_to_tensor(x_test_circ)

x_train_tfcirc_bin = tfq.convert_to_tensor(x_train_circ_bin)
x_test_tfcirc_bin = tfq.convert_to_tensor(x_test_circ_bin)
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)
def create_quantum_model():
    """Create a QNN model circuit and readout operation to go along with it."""
    data_qubits = cirq.GridQubit.rect(4, 1)  # 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()
SVGCircuit(model_circuit)
# Build the Keras model.
qnn_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),
])
# Build the Keras model.
qnn_model_bin = 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),
])
y_train_hinge = 2.0*y_train-1.0
y_test_hinge = 2.0*y_test-1.0
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)
qnn_model.compile(
    loss=tf.keras.losses.Hinge(),
    optimizer=tf.keras.optimizers.Adam(),
    metrics=[hinge_accuracy])
qnn_model.summary()
qnn_model_bin.compile(
    loss=tf.keras.losses.Hinge(),
    optimizer=tf.keras.optimizers.Adam(),
    metrics=[hinge_accuracy])
qnn_model_bin.summary()
EPOCHS = 5
BATCH_SIZE = 8

NUM_EXAMPLES = len(x_train_tfcirc)
x_train_tfcirc_sub = x_train_tfcirc[:NUM_EXAMPLES]
y_train_hinge_sub = y_train_hinge[:NUM_EXAMPLES]
x_train_tfcirc_sub_bin = x_train_tfcirc_bin[:NUM_EXAMPLES]
y_train_hinge_sub_bin = y_train_hinge[:NUM_EXAMPLES]
start_time = time.time()
qnn_history = qnn_model.fit(
      x_train_tfcirc_sub, y_train_hinge_sub,
      batch_size=BATCH_SIZE,
      epochs=EPOCHS,
      verbose=1,
      validation_data=(x_test_tfcirc, y_test_hinge))

qnn_results = qnn_model.evaluate(x_test_tfcirc, y_test)
qnn_training_time = time.time() - start_time
start_time = time.time()
qnn_history_bin = qnn_model_bin.fit(
      x_train_tfcirc_sub_bin, y_train_hinge_sub_bin,
      batch_size=BATCH_SIZE,
      epochs=EPOCHS,
      verbose=1,
      validation_data=(x_test_tfcirc_bin, y_test_hinge))

qnn_results_bin = qnn_model.evaluate(x_test_tfcirc_bin, y_test)
qnn_training_time_bin = time.time() - start_time
qnn_accuracy = qnn_results[1]
qnn_accuracy_bin = qnn_results_bin[1]
sns.barplot(x=['Amplitude Encoding','Binary Encoding'],y=[qnn_accuracy,qnn_accuracy_bin])
plt.title('Hinge Accuracy Score')
amplitude_training_time = round(qnn_training_time,2)
binary_training_time = round(qnn_training_time_bin,2)

sns.barplot(x=['Amplitude Encoding','Binary Encoding'],y=[amplitude_training_time,binary_training_time])
plt.title('Training Time (in seconds)')
plt.plot(qnn_history.history['val_loss'], label='Amplitude Encoding')
plt.plot(qnn_history_bin.history['val_loss'], label='Binary Encoding')
plt.title('Validation Loss')
plt.legend(('Amplitude Encoding', 'Binary Encoding'))
plt.plot(qnn_history.history['val_hinge_accuracy'])
plt.plot(qnn_history_bin.history['val_hinge_accuracy'])
plt.title('Validation Hinge Accuracy')
plt.legend(('Amplitude Encoding', 'Binary Encoding'))<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10807129072539448, 'fraction_numerical': 0.00896810080599387, 'mean_word_length': 5.013651877133106, 'pattern_counts': {'":': 0, '<': 12, '<?xml version=': 0, '>': 10, 'https://': 2, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '44486', 'license': 'MIT', 'n_tokens_mistral': 3229, 'n_tokens_neox': 3099, 'n_words': 728, 'path': 'Tensorflow-Quantum/MyTFQ/BreastCancerAnalysisViaQNN.ipynb', 'repo_name': 'snigdhayan/NeuralNetworks', 'size': '8844'}