{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"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\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')\nimport os\n\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    print(\"FOLDER:\", dirname)\n    for filename in filenames[:20]:\n        print(\"   \", filename)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:35:24.236764Z","iopub.execute_input":"2026-09-27T18:35:24.23692Z","iopub.status.idle":"2026-09-27T18:35:26.059573Z","shell.execute_reply.started":"2026-09-27T18:35:24.2369Z","shell.execute_reply":"2026-09-27T18:35:26.058846Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport zipfile\nimport glob\n\n# Dataset path\ndataset_path = \"/kaggle/input/competitions/dogs-vs-cats-redux-kernels-edition\"\n\n# Train ZIP file\ntrain_zip = os.path.join(dataset_path, \"train.zip\")\n\n# Extract location\nextract_path = \"/kaggle/working/train\"\n\n# Extract train images\nif not os.path.exists(extract_path):\n    os.makedirs(extract_path)\n\nwith zipfile.ZipFile(train_zip, 'r') as zip_ref:\n    zip_ref.extractall(extract_path)\n\nprint(\"Dataset extracted successfully!\")\n\n# Find images\nimage_files = glob.glob(\n    extract_path + \"/**/*.jpg\",\n    recursive=True\n)\n\nprint(\"Total images found:\", len(image_files))\n\n# Show first 10 image paths\nprint(\"\\nFirst 10 images:\")\nfor img in image_files[:10]:\n    print(img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:36:51.283839Z","iopub.execute_input":"2026-09-27T18:36:51.284098Z","iopub.status.idle":"2026-09-27T18:36:57.45776Z","shell.execute_reply.started":"2026-09-27T18:36:51.284078Z","shell.execute_reply":"2026-09-27T18:36:57.457139Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport matplotlib.pyplot as plt\n\n# Create labels from filenames\n# cat = 0\n# dog = 1\n\ndata = []\n\nfor filepath in image_files:\n    filename = os.path.basename(filepath).lower()\n\n    if filename.startswith(\"cat.\"):\n        label = 0\n        class_name = \"Cat\"\n    elif filename.startswith(\"dog.\"):\n        label = 1\n        class_name = \"Dog\"\n    else:\n        continue\n\n    data.append([filepath, label, class_name])\n\ndf = pd.DataFrame(\n    data,\n    columns=[\"filepath\", \"label\", \"class\"]\n)\n\nprint(\"Total images:\", len(df))\nprint(\"\\nClass distribution:\")\nprint(df[\"class\"].value_counts())\n\nprint(\"\\nFirst 5 rows:\")\ndisplay(df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:37:59.556595Z","iopub.execute_input":"2026-09-27T18:37:59.556841Z","iopub.status.idle":"2026-09-27T18:37:59.693212Z","shell.execute_reply.started":"2026-09-27T18:37:59.556821Z","shell.execute_reply":"2026-09-27T18:37:59.692604Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 6: IMAGE PREPROCESSING\n# ============================================================\n\nfrom sklearn.model_selection import train_test_split\n\nIMG_SIZE = 64\n\nX = []\ny = []\n\nprint(\"Processing images...\")\n\nfor _, row in df.iterrows():\n\n    try:\n        # Open image and convert to grayscale\n        img = Image.open(row[\"filepath\"]).convert(\"L\")\n\n        # Resize image to 64 x 64\n        img = img.resize((IMG_SIZE, IMG_SIZE))\n\n        # Convert image to numpy array\n        img_array = np.array(img)\n\n        # Normalize pixel values from 0-255 to 0-1\n        img_array = img_array / 255.0\n\n        # Flatten 64x64 image into 4096 features\n        img_array = img_array.flatten()\n\n        X.append(img_array)\n        y.append(row[\"label\"])\n\n    except Exception as e:\n        print(\"Error:\", row[\"filepath\"], e)\n\nX = np.array(X)\ny = np.array(y)\n\nprint(\"\\nPreprocessing completed!\")\nprint(\"X shape:\", X.shape)\nprint(\"y shape:\", y.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:38:47.2362Z","iopub.execute_input":"2026-09-27T18:38:47.236419Z","iopub.status.idle":"2026-09-27T18:39:22.950382Z","shell.execute_reply.started":"2026-09-27T18:38:47.236402Z","shell.execute_reply":"2026-09-27T18:39:22.949554Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 7: TRAIN / VALIDATION / TEST SPLIT\n# ============================================================\n\nfrom sklearn.model_selection import train_test_split\n\n# 70% Training, 15% Validation, 15% Testing\n\nX_train, X_temp, y_train, y_temp = train_test_split(\n    X,\n    y,\n    test_size=0.30,\n    random_state=42,\n    stratify=y\n)\n\nX_val, X_test, y_val, y_test = train_test_split(\n    X_temp,\n    y_temp,\n    test_size=0.50,\n    random_state=42,\n    stratify=y_temp\n)\n\nprint(\"Training samples:\", len(X_train))\nprint(\"Validation samples:\", len(X_val))\nprint(\"Testing samples:\", len(X_test))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:44:31.117583Z","iopub.execute_input":"2026-09-27T18:44:31.117938Z","iopub.status.idle":"2026-09-27T18:44:31.259379Z","shell.execute_reply.started":"2026-09-27T18:44:31.11792Z","shell.execute_reply":"2026-09-27T18:44:31.258723Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 8: TRAIN LOGISTIC REGRESSION MODEL\n# ============================================================\n\nfrom sklearn.linear_model import LogisticRegression\n\nmodel = LogisticRegression(\n    max_iter=1000,\n    solver=\"liblinear\",\n    random_state=42\n)\n\nprint(\"Training Logistic Regression model...\")\n\nmodel.fit(X_train, y_train)\n\nprint(\"Training completed successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:45:03.837273Z","iopub.execute_input":"2026-09-27T18:45:03.837532Z","iopub.status.idle":"2026-09-27T18:47:09.688548Z","shell.execute_reply.started":"2026-09-27T18:45:03.837513Z","shell.execute_reply":"2026-09-27T18:47:09.68781Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 9: PREDICTIONS AND ACCURACY\n# ============================================================\n\nfrom sklearn.metrics import accuracy_score\n\n# Predictions\ntrain_pred = model.predict(X_train)\nval_pred = model.predict(X_val)\ntest_pred = model.predict(X_test)\n\n# Probabilities\ntrain_prob = model.predict_proba(X_train)[:, 1]\nval_prob = model.predict_proba(X_val)[:, 1]\ntest_prob = model.predict_proba(X_test)[:, 1]\n\n# Accuracy\ntrain_accuracy = accuracy_score(y_train, train_pred)\nval_accuracy = accuracy_score(y_val, val_pred)\ntest_accuracy = accuracy_score(y_test, test_pred)\n\nprint(\"Training Accuracy   :\", round(train_accuracy, 4))\nprint(\"Validation Accuracy :\", round(val_accuracy, 4))\nprint(\"Test Accuracy       :\", round(test_accuracy, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:51:31.581073Z","iopub.execute_input":"2026-09-27T18:51:31.581312Z","iopub.status.idle":"2026-09-27T18:51:31.71449Z","shell.execute_reply.started":"2026-09-27T18:51:31.581294Z","shell.execute_reply":"2026-09-27T18:51:31.714025Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 10: CONFUSION MATRIX\n# ============================================================\n\nfrom sklearn.metrics import confusion_matrix\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\n# Create confusion matrix\ncm = confusion_matrix(y_test, test_pred)\n\nprint(\"Confusion Matrix:\")\nprint(cm)\n\n# Plot\nplt.figure(figsize=(7, 6))\n\nsns.heatmap(\n    cm,\n    annot=True,\n    fmt=\"d\",\n    cmap=\"Blues\",\n    xticklabels=[\"Cat\", \"Dog\"],\n    yticklabels=[\"Cat\", \"Dog\"]\n)\n\nplt.xlabel(\"Predicted Label\")\nplt.ylabel(\"Actual Label\")\nplt.title(\"Confusion Matrix - Logistic Regression\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:52:54.531273Z","iopub.execute_input":"2026-09-27T18:52:54.531522Z","iopub.status.idle":"2026-09-27T18:52:54.936497Z","shell.execute_reply.started":"2026-09-27T18:52:54.531502Z","shell.execute_reply":"2026-09-27T18:52:54.935851Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save Confusion Matrix for Word document\n\nplt.figure(figsize=(7, 6))\n\nsns.heatmap(\n    cm,\n    annot=True,\n    fmt=\"d\",\n    cmap=\"Blues\",\n    xticklabels=[\"Cat\", \"Dog\"],\n    yticklabels=[\"Cat\", \"Dog\"]\n)\n\nplt.xlabel(\"Predicted Label\")\nplt.ylabel(\"Actual Label\")\nplt.title(\"Confusion Matrix - Logistic Regression\")\n\nplt.tight_layout()\n\nplt.savefig(\n    \"/kaggle/working/confusion_matrix.png\",\n    dpi=300,\n    bbox_inches=\"tight\"\n)\n\nplt.show()\n\nprint(\"Confusion Matrix saved successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:54:21.863587Z","iopub.execute_input":"2026-09-27T18:54:21.863864Z","iopub.status.idle":"2026-09-27T18:54:22.187154Z","shell.execute_reply.started":"2026-09-27T18:54:21.863842Z","shell.execute_reply":"2026-09-27T18:54:22.186512Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# STEP 12: TRAINING VS VALIDATION ACCURACY\n# ============================================================\n\naccuracy_values = [\n    train_accuracy,\n    val_accuracy\n]\n\nlabels = [\n    \"Training\",\n    \"Validation\"\n]\n\nplt.figure(figsize=(8, 5))\n\nbars = plt.bar(\n    labels,\n    accuracy_values\n)\n\nplt.ylim(0, 1)\nplt.ylabel(\"Accuracy\")\nplt.xlabel(\"Dataset\")\nplt.title(\"Training vs Validation Accuracy\")\n\n# Show values on bars\nfor bar, value in zip(bars, accuracy_values):\n    plt.text(\n        bar.get_x() + bar.get_width() / 2,\n        value + 0.02,\n        f\"{value:.2%}\",\n        ha=\"center\"\n    )\n\nplt.tight_layout()\n\nplt.savefig(\n    \"/kaggle/working/training_validation_accuracy.png\",\n    dpi=300,\n    bbox_inches=\"tight\"\n)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:55:02.147558Z","iopub.execute_input":"2026-09-27T18:55:02.147822Z","iopub.status.idle":"2026-09-27T18:55:02.434123Z","shell.execute_reply.started":"2026-09-27T18:55:02.147803Z","shell.execute_reply":"2026-09-27T18:55:02.43344Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import roc_curve, auc\nimport matplotlib.pyplot as plt\n\n# ROC Curve\nfpr, tpr, thresholds = roc_curve(y_test, test_prob)\nroc_auc = auc(fpr, tpr)\n\nplt.figure(figsize=(8, 6))\nplt.plot(fpr, tpr, label=f'Logistic Regression (AUC = {roc_auc:.3f})')\nplt.plot([0, 1], [0, 1], '--')\n\nplt.xlabel(\"False Positive Rate\")\nplt.ylabel(\"True Positive Rate\")\nplt.title(\"ROC Curve - Logistic Regression\")\nplt.legend()\nplt.grid(True)\nplt.tight_layout()\n\n# Save graph\nplt.savefig(\"/kaggle/working/roc_curve.png\", dpi=300, bbox_inches=\"tight\")\n\nplt.show()\n\nprint(\"ROC-AUC:\", roc_auc)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:56:36.484174Z","iopub.execute_input":"2026-09-27T18:56:36.484418Z","iopub.status.idle":"2026-09-27T18:56:36.902804Z","shell.execute_reply.started":"2026-09-27T18:56:36.4844Z","shell.execute_reply":"2026-09-27T18:56:36.902062Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\n\n# Logistic Regression coefficients\ncoefficients = model.coef_[0]\n\n# Convert 4096 features back into 64x64 image shape\nimportance_map = coefficients.reshape(64, 64)\n\nplt.figure(figsize=(8, 7))\n\nplt.imshow(importance_map, cmap=\"coolwarm\")\nplt.colorbar(label=\"Logistic Regression Coefficient\")\n\nplt.title(\"Feature Importance Heatmap - Logistic Regression\")\nplt.axis(\"off\")\n\nplt.tight_layout()\n\n# Save image\nplt.savefig(\n    \"/kaggle/working/feature_importance.png\",\n    dpi=300,\n    bbox_inches=\"tight\"\n)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T18:59:02.072109Z","iopub.execute_input":"2026-09-27T18:59:02.072366Z","iopub.status.idle":"2026-09-27T18:59:02.604768Z","shell.execute_reply.started":"2026-09-27T18:59:02.072346Z","shell.execute_reply":"2026-09-27T18:59:02.603988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras import layers, models\n\nprint(\"TensorFlow version:\", tf.__version__)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T19:00:21.574544Z","iopub.execute_input":"2026-09-27T19:00:21.574792Z","iopub.status.idle":"2026-09-27T19:00:36.292666Z","shell.execute_reply.started":"2026-09-27T19:00:21.574773Z","shell.execute_reply":"2026-09-27T19:00:36.291899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Prepare a small subset for the auxiliary CNN / Grad-CAM\n\nimport numpy as np\n\n# Use a manageable subset so Kaggle runs faster\ngradcam_size = 3000\n\nX_gradcam = X[:gradcam_size].reshape(-1, 64, 64, 1)\ny_gradcam = y[:gradcam_size]\n\nprint(\"Grad-CAM dataset shape:\", X_gradcam.shape)\nprint(\"Labels shape:\", y_gradcam.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T19:07:53.254741Z","iopub.execute_input":"2026-09-27T19:07:53.255263Z","iopub.status.idle":"2026-09-27T19:07:53.259975Z","shell.execute_reply.started":"2026-09-27T19:07:53.25524Z","shell.execute_reply":"2026-09-27T19:07:53.259391Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Build a small CNN model for Grad-CAM\n\ncnn_model = models.Sequential([\n    layers.Input(shape=(64, 64, 1)),\n\n    layers.Conv2D(16, (3, 3), activation=\"relu\", name=\"conv1\"),\n    layers.MaxPooling2D((2, 2)),\n\n    layers.Conv2D(32, (3, 3), activation=\"relu\", name=\"conv2\"),\n    layers.MaxPooling2D((2, 2)),\n\n    layers.Conv2D(64, (3, 3), activation=\"relu\", name=\"gradcam_layer\"),\n\n    layers.GlobalAveragePooling2D(),\n    layers.Dense(1, activation=\"sigmoid\")\n])\n\ncnn_model.compile(\n    optimizer=\"adam\",\n    loss=\"binary_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\ncnn_model.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T19:08:25.049677Z","iopub.execute_input":"2026-09-27T19:08:25.049947Z","iopub.status.idle":"2026-09-27T19:08:25.159747Z","shell.execute_reply.started":"2026-09-27T19:08:25.049925Z","shell.execute_reply":"2026-09-27T19:08:25.159124Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train the CNN model\n\nhistory = cnn_model.fit(\n    X_gradcam,\n    y_gradcam,\n    epochs=5,\n    batch_size=32,\n    validation_split=0.2,\n    shuffle=True,\n    verbose=1\n)\n\nprint(\"CNN training completed successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T19:09:56.508361Z","iopub.execute_input":"2026-09-27T19:09:56.508557Z","iopub.status.idle":"2026-09-27T19:10:07.301209Z","shell.execute_reply.started":"2026-09-27T19:09:56.50854Z","shell.execute_reply":"2026-09-27T19:10:07.300366Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a Functional version of the trained CNN for Grad-CAM\n\nimport tensorflow as tf\nimport numpy as np\nimport matplotlib.pyplot as plt\n\n# New explicit input\ngradcam_input = tf.keras.Input(shape=(64, 64, 1))\n\nx = gradcam_input\n\n# Reuse the already trained layers\nfor layer in cnn_model.layers:\n    x = layer(x)\n\ngradcam_functional = tf.keras.Model(\n    inputs=gradcam_input,\n    outputs=x\n)\n\nprint(\"Functional Grad-CAM model created successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T19:29:37.863004Z","iopub.execute_input":"2026-09-27T19:29:37.863273Z","iopub.status.idle":"2026-09-27T19:29:37.877247Z","shell.execute_reply.started":"2026-09-27T19:29:37.86325Z","shell.execute_reply":"2026-09-27T19:29:37.876588Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Reliable Functional CNN for Grad-CAM\n\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# Build Functional CNN\ninputs = tf.keras.Input(shape=(64, 64, 1))\n\nx = tf.keras.layers.Conv2D(16, (3, 3), activation=\"relu\")(inputs)\nx = tf.keras.layers.MaxPooling2D((2, 2))(x)\n\nx = tf.keras.layers.Conv2D(32, (3, 3), activation=\"relu\")(x)\nx = tf.keras.layers.MaxPooling2D((2, 2))(x)\n\nlast_conv = tf.keras.layers.Conv2D(\n    64, (3, 3), activation=\"relu\", name=\"gradcam_layer\"\n)(x)\n\nx = tf.keras.layers.GlobalAveragePooling2D()(last_conv)\noutputs = tf.keras.layers.Dense(1, activation=\"sigmoid\")(x)\n\ngradcam_model = tf.keras.Model(inputs, outputs)\n\ngradcam_model.compile(\n    optimizer=\"adam\",\n    loss=\"binary_crossentropy\",\n    metrics=[\"accuracy\"]\n)\n\nprint(\"Functional Grad-CAM model created successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T19:30:59.856397Z","iopub.execute_input":"2026-09-27T19:30:59.856634Z","iopub.status.idle":"2026-09-27T19:30:59.88922Z","shell.execute_reply.started":"2026-09-27T19:30:59.856617Z","shell.execute_reply":"2026-09-27T19:30:59.888381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train the Functional CNN for Grad-CAM\n\ngradcam_history = gradcam_model.fit(\n    X_gradcam,\n    y_gradcam,\n    epochs=3,\n    batch_size=32,\n    validation_split=0.2,\n    shuffle=True,\n    verbose=1\n)\n\nprint(\"Grad-CAM CNN training completed!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T19:31:20.953121Z","iopub.execute_input":"2026-09-27T19:31:20.953403Z","iopub.status.idle":"2026-09-27T19:31:28.550517Z","shell.execute_reply.started":"2026-09-27T19:31:20.953378Z","shell.execute_reply":"2026-09-27T19:31:28.549792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Generate Grad-CAM visualization\n\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nimport numpy as np\n\n# Select an image\nsample_index = 0\n\nimg = X_gradcam[sample_index].astype(\"float32\")\nimg_tensor = tf.convert_to_tensor(img[None, ...])\n\n# Get the last convolutional layer\nlast_conv_layer = gradcam_model.get_layer(\"gradcam_layer\")\n\n# Create model for Grad-CAM\ngrad_model = tf.keras.Model(\n    inputs=gradcam_model.inputs,\n    outputs=[last_conv_layer.output, gradcam_model.output]\n)\n\n# Calculate gradients\nwith tf.GradientTape() as tape:\n    conv_outputs, preds = grad_model(img_tensor, training=False)\n    score = preds[:, 0]\n\ngrads = tape.gradient(score, conv_outputs)\n\n# Average gradients\npooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n\n# Create heatmap\nconv_outputs = conv_outputs[0]\n\nheatmap = tf.reduce_sum(\n    conv_outputs * pooled_grads,\n    axis=-1\n)\n\nheatmap = tf.maximum(heatmap, 0)\n\nheatmap /= tf.maximum(\n    tf.reduce_max(heatmap),\n    1e-8\n)\n\nheatmap = heatmap.numpy()\n\n# Display original image and Grad-CAM\nplt.figure(figsize=(12, 5))\n\nplt.subplot(1, 2, 1)\nplt.imshow(img.squeeze(), cmap=\"gray\")\nplt.title(\"Original Image\")\nplt.axis(\"off\")\n\nplt.subplot(1, 2, 2)\nplt.imshow(img.squeeze(), cmap=\"gray\")\nplt.imshow(heatmap, cmap=\"jet\", alpha=0.5)\nplt.title(\"Grad-CAM\")\nplt.axis(\"off\")\n\nplt.tight_layout()\n\n# Save image\nplt.savefig(\n    \"/kaggle/working/gradcam.png\",\n    dpi=300,\n    bbox_inches=\"tight\"\n)\n\nplt.show()\n\nprint(\"Grad-CAM generated successfully!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-27T19:43:54.780381Z","iopub.execute_input":"2026-09-27T19:43:54.780626Z","iopub.status.idle":"2026-09-27T19:43:55.389717Z","shell.execute_reply.started":"2026-09-27T19:43:54.780607Z","shell.execute_reply":"2026-09-27T19:43:55.38893Z"}},"outputs":[],"execution_count":null}]}