{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":3004,"databundleVersionId":861823,"sourceType":"competition"}],"dockerImageVersionId":30746,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2025-06-07T12:43:35.372759Z","iopub.execute_input":"2025-06-07T12:43:35.373184Z","iopub.status.idle":"2025-06-07T12:43:36.071929Z","shell.execute_reply.started":"2025-06-07T12:43:35.373140Z","shell.execute_reply":"2025-06-07T12:43:36.070147Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Hybrid QCNN-CNN approach**","metadata":{}},{"cell_type":"code","source":"!pip install tensorflow tensorflow-quantum cirq","metadata":{"execution":{"iopub.status.busy":"2025-06-07T12:43:39.757027Z","iopub.execute_input":"2025-06-07T12:43:39.757538Z","iopub.status.idle":"2025-06-07T12:44:53.680012Z","shell.execute_reply.started":"2025-06-07T12:43:39.757506Z","shell.execute_reply":"2025-06-07T12:44:53.678600Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Creating the Quantum Circuit and QCNN model**","metadata":{}},{"cell_type":"code","source":"import tensorflow as tf\nimport tensorflow_quantum as tfq\nimport cirq\nimport sympy\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# Create a quantum data layer\ndef create_quantum_data_layer(qubits):\n    return tf.keras.layers.Input(shape=(), dtype=tf.string)\n\n# Build quantum circuit\ndef create_quantum_circuit(qubits):\n    circuit = cirq.Circuit()\n    for qubit in qubits:\n        circuit.append(cirq.rx(sympy.Symbol(f'rx_{qubit}')).on(qubit))\n        circuit.append(cirq.rz(sympy.Symbol(f'rz_{qubit}')).on(qubit))\n    return circuit\n\n# Define the quantum model\ndef create_quantum_model(qubits):\n    circuit = create_quantum_circuit(qubits)\n    inputs = create_quantum_data_layer(qubits)\n    quantum_layer = tfq.layers.PQC(circuit, cirq.Z(qubits[0]))(inputs)\n    return tf.keras.Model(inputs=inputs, outputs=quantum_layer)","metadata":{"execution":{"iopub.status.busy":"2025-06-07T12:44:53.682054Z","iopub.execute_input":"2025-06-07T12:44:53.682384Z","iopub.status.idle":"2025-06-07T12:45:09.880930Z","shell.execute_reply.started":"2025-06-07T12:44:53.682356Z","shell.execute_reply":"2025-06-07T12:45:09.879729Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## **Creating the Classical CNN model**\n* The preprocess_images function resizes the MNIST images and converts them into quantum circuits so that they could be passed as inputs into the QCNN layers\n* We are utilizing 4 qubits here and combining the model together in the create_hybrid_model function.","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras import layers, models\n\n# Number of qubits (4 qubits)\nn_qubits = 4\nqubits = [cirq.GridQubit(0, i) for i in range(n_qubits)]\n\n# Quantum model\nquantum_model = create_quantum_model(qubits)\n\n# Visualize the quantum circuit\ndef visualize_quantum_circuit(circuit):\n    print(circuit)\n    fig, ax = plt.subplots()\n    ax.axis('off')\n    plt.show()\n\n# Create and visualize the circuit\ncircuit = create_quantum_circuit(qubits)\nvisualize_quantum_circuit(circuit)\n\n# Classical CNN model\ndef create_classical_model():\n    model = models.Sequential()\n    model.add(layers.Conv2D(32, (3, 3), activation='relu', input_shape=(28, 28, 1)))\n    model.add(layers.MaxPooling2D((2, 2)))\n    model.add(layers.Conv2D(64, (3, 3), activation='relu'))\n    model.add(layers.MaxPooling2D((2, 2)))\n    model.add(layers.Conv2D(64, (3, 3), activation='relu'))\n    model.add(layers.Flatten())\n    model.add(layers.Dense(64, activation='relu'))\n    return model\n\nclassical_model = create_classical_model()\n\n# Function to preprocess images outside the graph context\ndef preprocess_images_py(images):\n    images = tf.image.resize(images, (1, n_qubits))\n    images = tf.reshape(images, (-1, n_qubits)).numpy()\n    circuits = [cirq.Circuit(cirq.X(qubits[j]) for j in range(n_qubits) if pixel[j] > 0.5) for pixel in images]\n    return tfq.convert_to_tensor(circuits)\n\n# Wrapper to use the function in a tf.data pipeline\ndef preprocess_images(images):\n    return tf.py_function(func=preprocess_images_py, inp=[images], Tout=tf.dtypes.string)\n\n# Combine quantum and classical models\ndef create_hybrid_model(quantum_model, classical_model):\n    classical_input = tf.keras.layers.Input(shape=(28, 28, 1))\n    preprocessed_images = tf.keras.layers.Lambda(preprocess_images)(classical_input)\n    quantum_output = quantum_model(preprocessed_images)\n    classical_output = classical_model(classical_input)\n    outputs = tf.keras.layers.concatenate([quantum_output, classical_output])\n    outputs = tf.keras.layers.Dense(10, activation='softmax')(outputs)\n    return tf.keras.Model(inputs=classical_input, outputs=outputs)\n\nhybrid_model = create_hybrid_model(quantum_model, classical_model)\n","metadata":{"execution":{"iopub.status.busy":"2025-06-07T12:46:51.161928Z","iopub.execute_input":"2025-06-07T12:46:51.162742Z","iopub.status.idle":"2025-06-07T12:46:53.023269Z","shell.execute_reply.started":"2025-06-07T12:46:51.162706Z","shell.execute_reply":"2025-06-07T12:46:53.022071Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"The Above diagram is being visualized using matplotlib.","metadata":{}},{"cell_type":"code","source":"# Load MNIST dataset\nmnist = tf.keras.datasets.mnist\n(x_train, y_train), (x_test, y_test) = mnist.load_data()\nx_train = x_train[..., tf.newaxis] / 255.0\nx_test = x_test[..., tf.newaxis] / 255.0\n\n# Compile and train the model\nhybrid_model.compile(optimizer='adam',\n                     loss='sparse_categorical_crossentropy',\n                     metrics=['accuracy'])\n\n# Train the model and capture history\nhistory = hybrid_model.fit(\n    x_train, y_train,\n    epochs=5,\n    batch_size=32,\n    validation_split=0.1\n)\n\n# Evaluate the model\ntest_loss, test_acc = hybrid_model.evaluate(x_test, y_test, verbose=2)\nprint(f'Test accuracy: {test_acc}')\n","metadata":{"execution":{"iopub.status.busy":"2025-06-07T12:46:59.209080Z","iopub.execute_input":"2025-06-07T12:46:59.209504Z","iopub.status.idle":"2025-06-07T12:51:36.713705Z","shell.execute_reply.started":"2025-06-07T12:46:59.209475Z","shell.execute_reply":"2025-06-07T12:51:36.712415Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Classical Only model\ndef create_classical_only_model():\n    model = create_classical_model()\n    model.add(tf.keras.layers.Dense(10, activation='softmax'))\n    return model\n\nclassical_only_model = create_classical_only_model()\nclassical_only_model.compile(optimizer='adam',\n                             loss='sparse_categorical_crossentropy',\n                             metrics=['accuracy'])\nclassical_only_model.fit(x_train, y_train, epochs=5, batch_size=32, validation_split=0.1)\nloss_cl, acc_cl = classical_only_model.evaluate(x_test, y_test, verbose=0)\nprint(f\"Classical Only Accuracy: {acc_cl:.4f}, Loss: {loss_cl:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T13:02:59.418448Z","iopub.execute_input":"2025-06-07T13:02:59.419085Z","iopub.status.idle":"2025-06-07T13:05:19.650432Z","shell.execute_reply.started":"2025-06-07T13:02:59.419046Z","shell.execute_reply":"2025-06-07T13:05:19.648861Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.manifold import TSNE\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\n# Helper to extract intermediate features\ndef build_feature_model(full_model):\n    # Get the layer right before final Dense(10)\n    return tf.keras.Model(inputs=full_model.input,\n                          outputs=full_model.layers[-2].output)\n\n# Build models to extract features\nfeature_model_hybrid = build_feature_model(hybrid_model)\nfeature_model_classical = build_feature_model(classical_only_model)\n\n# Get a small subset for visualization (e.g., 1000 test samples)\nx_vis = x_test[:1000]\ny_vis = y_test[:1000]\n\n# Extract features\nfeatures_hybrid = feature_model_hybrid.predict(x_vis)\nfeatures_classical = feature_model_classical.predict(x_vis)\n\n# Run t-SNE (2D)\ntsne = TSNE(n_components=2, perplexity=30, random_state=42)\ntsne_hybrid = tsne.fit_transform(features_hybrid)\ntsne_classical = tsne.fit_transform(features_classical)\n\n# Plotting function\ndef plot_tsne(tsne_data, labels, title):\n    plt.figure(figsize=(8, 6))\n    sns.scatterplot(x=tsne_data[:, 0], y=tsne_data[:, 1], hue=labels, palette='tab10', legend='full')\n    plt.title(title)\n    plt.xlabel(\"t-SNE Dim 1\")\n    plt.ylabel(\"t-SNE Dim 2\")\n    plt.tight_layout()\n    plt.show()\n\n# Plot both\nplot_tsne(tsne_hybrid, y_vis, \"t-SNE of Hybrid (Quantum + Classical) Feature Embeddings\")\nplot_tsne(tsne_classical, y_vis, \"t-SNE of Classical-Only Feature Embeddings\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T13:05:53.710620Z","iopub.execute_input":"2025-06-07T13:05:53.711621Z","iopub.status.idle":"2025-06-07T13:06:03.917460Z","shell.execute_reply.started":"2025-06-07T13:05:53.711583Z","shell.execute_reply":"2025-06-07T13:06:03.916346Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Plot training & validation loss\nplt.figure(figsize=(10,5))\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Validation Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Train vs Validation Loss')\nplt.legend()\nplt.grid(True)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T12:51:36.715693Z","iopub.execute_input":"2025-06-07T12:51:36.716098Z","iopub.status.idle":"2025-06-07T12:51:37.060289Z","shell.execute_reply.started":"2025-06-07T12:51:36.716068Z","shell.execute_reply":"2025-06-07T12:51:37.059208Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\nimport numpy as np\n\n# Get predictions on test set\ny_pred_probs = hybrid_model.predict(x_test)\ny_preds = np.argmax(y_pred_probs, axis=1)\n\n# Compute and plot confusion matrix\ncm = confusion_matrix(y_test, y_preds)\ndisp = ConfusionMatrixDisplay(confusion_matrix=cm)\ndisp.plot(cmap='Blues')\nplt.title(\"Confusion Matrix\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T12:51:37.061529Z","iopub.execute_input":"2025-06-07T12:51:37.061957Z","iopub.status.idle":"2025-06-07T12:51:44.355245Z","shell.execute_reply.started":"2025-06-07T12:51:37.061919Z","shell.execute_reply":"2025-06-07T12:51:44.354100Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport numpy as np\n\n# Pick one sample from test set to analyze\nsample_index = 0\nx_sample = x_test[sample_index:sample_index+1]\ny_sample = y_test[sample_index]\n\n# Get the quantum model's trainable variables (symbols like rx_q0, rz_q1, etc.)\nquantum_vars = quantum_model.trainable_variables\n\n# Build a wrapper for hybrid model that outputs class logits\n@tf.function\ndef get_logit_for_class(x):\n    with tf.GradientTape() as tape:\n        tape.watch(quantum_vars)\n        preds = hybrid_model(x, training=False)\n        target_logit = preds[:, y_sample]  # probability/logit of the correct class\n    grads = tape.gradient(target_logit, quantum_vars)\n    return grads\n\n# Compute gradients for one input sample\ngrads = get_logit_for_class(x_sample)\n\n# Visualize sensitivity\nimport matplotlib.pyplot as plt\n\nparam_names = [var.name for var in quantum_vars]\ngrad_mags = [tf.reduce_mean(tf.abs(g)).numpy() for g in grads]\n\nplt.figure(figsize=(10, 4))\nplt.bar(param_names, grad_mags)\nplt.xticks(rotation=90)\nplt.title(f\"Sensitivity of Quantum Parameters (Sample #{sample_index}, True Class = {y_sample})\")\nplt.ylabel(\"Average |Gradient|\")\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-06-07T13:01:00.319163Z","iopub.execute_input":"2025-06-07T13:01:00.319981Z","iopub.status.idle":"2025-06-07T13:01:00.872567Z","shell.execute_reply.started":"2025-06-07T13:01:00.319921Z","shell.execute_reply":"2025-06-07T13:01:00.871180Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_test = pd.read_csv(\"/kaggle/input/digit-recognizer/test.csv\")","metadata":{"execution":{"iopub.status.busy":"2024-07-16T21:15:34.972605Z","iopub.execute_input":"2024-07-16T21:15:34.973020Z","iopub.status.idle":"2024-07-16T21:15:36.941640Z","shell.execute_reply.started":"2024-07-16T21:15:34.972984Z","shell.execute_reply":"2024-07-16T21:15:36.940702Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# **Testing its validation loss and validation accuracy**","metadata":{}},{"cell_type":"code","source":"val_loss, val_acc = hybrid_model.evaluate(x_test, y_test)\nprint(f'Validation Loss: {val_loss}, Validation Accuracy: {val_acc}')\n\npredictions = hybrid_model.predict(df_test.values.reshape(-1, 28, 28, 1) / 255.0)\npredicted_labels = np.argmax(predictions, axis=1)\n\nprint(\"Predictions:\")\nprint(predicted_labels)","metadata":{"execution":{"iopub.status.busy":"2024-07-16T21:15:48.536797Z","iopub.execute_input":"2024-07-16T21:15:48.537728Z","iopub.status.idle":"2024-07-16T21:16:14.481198Z","shell.execute_reply.started":"2024-07-16T21:15:48.537693Z","shell.execute_reply":"2024-07-16T21:16:14.480010Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"* Accuracy : 99.16%\n* Loss : 0.0254","metadata":{}},{"cell_type":"markdown","source":"# **Submitting the results**","metadata":{}},{"cell_type":"code","source":"submission = pd.DataFrame({'ImageId': list(range(1, len(predicted_labels) + 1)),\n                              'Label': predicted_labels})\nsubmission.to_csv('submission.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2024-07-16T21:18:07.650403Z","iopub.execute_input":"2024-07-16T21:18:07.651478Z","iopub.status.idle":"2024-07-16T21:18:07.719909Z","shell.execute_reply.started":"2024-07-16T21:18:07.651434Z","shell.execute_reply":"2024-07-16T21:18:07.718788Z"},"trusted":true},"outputs":[],"execution_count":null}]}