{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":11848,"databundleVersionId":862157,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nos.environ[\"CUDA_VISIBLE_DEVICES\"] = \"-1\"\nprint(\"CUDA_VISIBLE_DEVICES =\", os.environ.get(\"CUDA_VISIBLE_DEVICES\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T22:44:31.550916Z","iopub.execute_input":"2026-01-26T22:44:31.551263Z","iopub.status.idle":"2026-01-26T22:44:31.560745Z","shell.execute_reply.started":"2026-01-26T22:44:31.551231Z","shell.execute_reply":"2026-01-26T22:44:31.559248Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T22:44:35.278514Z","iopub.execute_input":"2026-01-26T22:44:35.278942Z","iopub.status.idle":"2026-01-26T22:44:35.640114Z","shell.execute_reply.started":"2026-01-26T22:44:35.278898Z","shell.execute_reply":"2026-01-26T22:44:35.639266Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\nlabels = pd.read_csv(\n    \"/kaggle/input/histopathologic-cancer-detection/train_labels.csv\"\n)\n\nlabels[\"filename\"] = labels[\"id\"] + \".tif\"\nlabels[\"label\"] = labels[\"label\"].astype(str)\n\ntrain_df, val_df = train_test_split(\n    labels,\n    test_size=0.2,\n    stratify=labels[\"label\"],\n    random_state=42\n)\n\nprint(\"Train size:\", train_df.shape)\nprint(\"Validation size:\", val_df.shape)\n\ntrain_df.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T22:44:37.872643Z","iopub.execute_input":"2026-01-26T22:44:37.873170Z","iopub.status.idle":"2026-01-26T22:44:39.343797Z","shell.execute_reply.started":"2026-01-26T22:44:37.873135Z","shell.execute_reply":"2026-01-26T22:44:39.343084Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\n\nBASE_DIR  = \"/kaggle/input/histopathologic-cancer-detection\"\nTRAIN_DIR = f\"{BASE_DIR}/train\"\n\nIMG_SIZE   = (96, 96)\nBATCH_SIZE = 32\n\ntrain_datagen = ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=20,\n    width_shift_range=0.05,\n    height_shift_range=0.05,\n    zoom_range=0.10,\n    horizontal_flip=True\n)\n\nval_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_gen = train_datagen.flow_from_dataframe(\n    dataframe=train_df,\n    directory=TRAIN_DIR,\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=\"binary\",\n    shuffle=True,\n    validate_filenames=False\n)\n\nval_gen = val_datagen.flow_from_dataframe(\n    dataframe=val_df,\n    directory=TRAIN_DIR,\n    x_col=\"filename\",\n    y_col=\"label\",\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode=\"binary\",\n    shuffle=False,\n    validate_filenames=False\n)\n\nprint(\"Class indices:\", train_gen.class_indices)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T22:44:42.849253Z","iopub.execute_input":"2026-01-26T22:44:42.849593Z","iopub.status.idle":"2026-01-26T22:44:47.189938Z","shell.execute_reply.started":"2026-01-26T22:44:42.849565Z","shell.execute_reply":"2026-01-26T22:44:47.188109Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras import layers, models\n\nmodel = models.Sequential([\n    layers.Input(shape=(96, 96, 3)),\n\n    layers.Conv2D(32, 3, activation=\"relu\"),\n    layers.MaxPooling2D(),\n\n    layers.Conv2D(64, 3, activation=\"relu\"),\n    layers.MaxPooling2D(),\n\n    layers.Conv2D(128, 3, activation=\"relu\"),\n    layers.MaxPooling2D(),\n\n    layers.Flatten(),\n    layers.Dropout(0.5),\n    layers.Dense(128, activation=\"relu\"),\n    layers.Dense(1, activation=\"sigmoid\")\n])\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),\n    loss=\"binary_crossentropy\",\n    metrics=[\"accuracy\", tf.keras.metrics.AUC(name=\"auc\")]\n)\n\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T22:47:10.138474Z","iopub.execute_input":"2026-01-26T22:47:10.139268Z","iopub.status.idle":"2026-01-26T22:47:10.295839Z","shell.execute_reply.started":"2026-01-26T22:47:10.139231Z","shell.execute_reply":"2026-01-26T22:47:10.294804Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping\n\nearly_stop = EarlyStopping(\n    monitor='val_auc',\n    mode='max',\n    patience=3,\n    restore_best_weights=True\n)\n\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=8,\n    steps_per_epoch=30,\n    validation_steps=15,\n    callbacks=[early_stop]\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T23:04:01.592801Z","iopub.execute_input":"2026-01-26T23:04:01.593234Z","iopub.status.idle":"2026-01-26T23:06:11.980003Z","shell.execute_reply.started":"2026-01-26T23:04:01.593203Z","shell.execute_reply":"2026-01-26T23:06:11.979109Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nplt.plot(history.history['auc'], marker='o', label='Train AUC')\nplt.plot(history.history['val_auc'], marker='o', label='Val AUC')\n\nplt.xlabel('Epoch')\nplt.ylabel('AUC')\nplt.title('Training vs Validation AUC')\nplt.legend()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T23:18:24.409452Z","iopub.execute_input":"2026-01-26T23:18:24.409860Z","iopub.status.idle":"2026-01-26T23:18:24.596471Z","shell.execute_reply.started":"2026-01-26T23:18:24.409819Z","shell.execute_reply":"2026-01-26T23:18:24.595570Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"_ = model(tf.zeros((1, 96, 96, 3)))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T23:24:23.258020Z","iopub.execute_input":"2026-01-26T23:24:23.258433Z","iopub.status.idle":"2026-01-26T23:24:23.296710Z","shell.execute_reply.started":"2026-01-26T23:24:23.258402Z","shell.execute_reply":"2026-01-26T23:24:23.295698Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\n\n# Create a Functional wrapper around your existing trained Sequential model\ninputs = tf.keras.Input(shape=(96, 96, 3))\noutputs = model(inputs)\nmodel_f = tf.keras.Model(inputs, outputs)\n\nprint(\"Functional model created.\")\nprint(\"Input:\", model_f.input)\nprint(\"Output:\", model_f.output)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T23:28:55.413281Z","iopub.execute_input":"2026-01-26T23:28:55.413654Z","iopub.status.idle":"2026-01-26T23:28:55.426509Z","shell.execute_reply.started":"2026-01-26T23:28:55.413622Z","shell.execute_reply":"2026-01-26T23:28:55.425010Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\n\ninputs = tf.keras.Input(shape=(96, 96, 3))\nx = inputs\nfor layer in model.layers:   # reuse your trained layers\n    x = layer(x)\n\nmodel_gc = tf.keras.Model(inputs, x)  # clean graph model for Grad-CAM\n\nprint(\"Built model_gc\")\nprint(\"Input:\", model_gc.input)\nprint(\"Output:\", model_gc.output)\nprint(\"Has conv2d_2?\", any(l.name == \"conv2d_2\" for l in model_gc.layers))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T23:34:46.816864Z","iopub.execute_input":"2026-01-26T23:34:46.817230Z","iopub.status.idle":"2026-01-26T23:34:46.831762Z","shell.execute_reply.started":"2026-01-26T23:34:46.817202Z","shell.execute_reply":"2026-01-26T23:34:46.830790Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport numpy as np\n\ninputs = tf.keras.Input(shape=(96, 96, 3))\nx = inputs\nnew_layers = []\n\nfor layer in model.layers:\n    # recreate the layer from config (fresh instance)\n    new_layer = layer.__class__.from_config(layer.get_config())\n    x = new_layer(x)\n    new_layers.append(new_layer)\n\nmodel_clean = tf.keras.Model(inputs, x)\n\n_ = model_clean(tf.zeros((1, 96, 96, 3)))\n\nfor old_layer, new_layer in zip(model.layers, new_layers):\n    if old_layer.get_weights():  # only layers that have weights\n        new_layer.set_weights(old_layer.get_weights())\n\nprint(\"Clean model built.\")\nprint(\"Top-level layers:\", [l.name for l in model_clean.layers])\nprint(\"Has conv2d_2?\", any(l.name == \"conv2d_2\" for l in model_clean.layers))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T23:42:12.181562Z","iopub.execute_input":"2026-01-26T23:42:12.181981Z","iopub.status.idle":"2026-01-26T23:42:12.284981Z","shell.execute_reply.started":"2026-01-26T23:42:12.181947Z","shell.execute_reply":"2026-01-26T23:42:12.283984Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nlast_conv_layer_name = \"conv2d_2\"\n\ndef make_gradcam_heatmap(img_array, model_clean, last_conv_layer_name):\n    img_tensor = tf.convert_to_tensor(img_array, dtype=tf.float32)\n\n    conv_layer = model_clean.get_layer(last_conv_layer_name)\n    grad_model = tf.keras.Model(model_clean.input, [conv_layer.output, model_clean.output])\n\n    with tf.GradientTape() as tape:\n        conv_outputs, preds = grad_model(img_tensor, training=False)\n        class_channel = preds[:, 0]\n\n    grads = tape.gradient(class_channel, conv_outputs)\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n\n    conv_outputs = conv_outputs[0]\n    heatmap = conv_outputs @ pooled_grads[..., tf.newaxis]\n    heatmap = tf.squeeze(heatmap)\n\n    heatmap = tf.maximum(heatmap, 0) / (tf.reduce_max(heatmap) + 1e-8)\n    return heatmap.numpy()\n\ndef batch_img_to_pil(x):\n    x = np.clip(x * 255.0, 0, 255).astype(\"uint8\")\n    return tf.keras.utils.array_to_img(x)\n\ndef show_gradcam(img, heatmap, alpha=0.6):\n    heatmap = tf.image.resize(heatmap[..., np.newaxis], (img.size[1], img.size[0])).numpy().squeeze()\n    plt.figure(figsize=(5, 5))\n    plt.imshow(img)\n    plt.imshow(heatmap, alpha=alpha)\n    plt.axis(\"off\")\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T23:42:26.292738Z","iopub.execute_input":"2026-01-26T23:42:26.293078Z","iopub.status.idle":"2026-01-26T23:42:26.302389Z","shell.execute_reply.started":"2026-01-26T23:42:26.293050Z","shell.execute_reply":"2026-01-26T23:42:26.301534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"x_batch, y_batch = next(val_gen)\nprobs = model_clean.predict(x_batch, verbose=0).ravel()\n\nhi = int(np.argmax(probs))\nlo = int(np.argmin(probs))\n\nprint(\"Highest prob:\", float(probs[hi]), \" True label:\", int(y_batch[hi]))\nprint(\"Lowest prob: \", float(probs[lo]), \" True label:\", int(y_batch[lo]))\n\nfor idx, tag in [(hi, \"HIGH\"), (lo, \"LOW\")]:\n    img = batch_img_to_pil(x_batch[idx])\n    img_array = np.expand_dims(x_batch[idx], axis=0)\n\n    heatmap = make_gradcam_heatmap(img_array, model_clean, last_conv_layer_name)\n\n    print(f\"{tag} example prob={probs[idx]:.4f}, true={int(y_batch[idx])}\")\n    show_gradcam(img, heatmap, alpha=0.6)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-26T23:42:39.522275Z","iopub.execute_input":"2026-01-26T23:42:39.522900Z","iopub.status.idle":"2026-01-26T23:42:40.303537Z","shell.execute_reply.started":"2026-01-26T23:42:39.522837Z","shell.execute_reply":"2026-01-26T23:42:40.302679Z"}},"outputs":[],"execution_count":null}]}