{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"},"kaggle":{"accelerator":"none","dataSources":[],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"cc8229f9","cell_type":"markdown","source":"# EfficientNetB2 on RSNA Pneumonia Detection Challenge (binary classification)\n\nThis notebook is adapted for the Kaggle competition:\n\n`rsna-pneumonia-detection-challenge`\n\nThe original challenge is a detection/localization task with DICOM images and bounding boxes in `stage_2_train_labels.csv`.  \nThis notebook builds a simpler binary classification pipeline:\n\n- `Target = 1` → Pneumonia\n- `Target = 0` → No Pneumonia\n\nIt:\n- auto-detects the Kaggle competition folder\n- reads `stage_2_train_labels.csv`\n- loads `.dcm` images from `stage_2_train_images/`\n- deduplicates patients into one label per image\n- creates a train/validation split\n- trains EfficientNetB2\n- reports accuracy, precision, and f1-score\n","metadata":{}},{"id":"1d6692ef","cell_type":"code","source":"\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nimport matplotlib.pyplot as plt\nimport pydicom\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (\n    classification_report,\n    confusion_matrix,\n    accuracy_score,\n    precision_score,\n    f1_score\n)\n\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.applications import EfficientNetB2\nfrom tensorflow.keras.applications.efficientnet import preprocess_input\n\nSEED = 42\nIMG_SIZE = 260\nBATCH_SIZE = 16\nEPOCHS = 5\nVAL_SIZE = 0.2\n\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\nprint(\"TensorFlow version:\", tf.__version__)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"eb5cc512","cell_type":"code","source":"\n# =========================\n# Dataset root detection\n# =========================\n\ndef find_dataset_root():\n    candidates = [\n        \"/kaggle/input/rsna-pneumonia-detection-challenge\",\n        \"/kaggle/input/competitions/rsna-pneumonia-detection-challenge\",\n    ]\n\n    for c in candidates:\n        if os.path.isfile(os.path.join(c, \"stage_2_train_labels.csv\")):\n            return c\n\n    if os.path.isdir(\"/kaggle/input\"):\n        for root, dirs, files in os.walk(\"/kaggle/input\"):\n            if \"stage_2_train_labels.csv\" in files:\n                return root\n\n    return None\n\nDATA_ROOT = find_dataset_root()\nWORKDIR = \"/kaggle/working/efficientnetb2_rsna_pneumonia\"\nos.makedirs(WORKDIR, exist_ok=True)\n\nprint(\"Detected DATA_ROOT:\", DATA_ROOT)\nprint(\"WORKDIR:\", WORKDIR)\n\nif DATA_ROOT is None:\n    print(\"\\nCould not auto-detect the competition data.\")\n    if os.path.isdir(\"/kaggle/input\"):\n        print(\"\\nFolders under /kaggle/input:\")\n        for root, dirs, files in os.walk(\"/kaggle/input\"):\n            depth = root.replace(\"/kaggle/input\", \"\").count(os.sep)\n            if depth <= 5:\n                print(root)\n    raise FileNotFoundError(\"RSNA competition dataset root not found.\")\n\nprint(\"\\nDATA_ROOT contents:\", sorted(os.listdir(DATA_ROOT))[:20])\n\nLABELS_CSV = os.path.join(DATA_ROOT, \"stage_2_train_labels.csv\")\nTRAIN_IMAGES_DIR = os.path.join(DATA_ROOT, \"stage_2_train_images\")\nTEST_IMAGES_DIR = os.path.join(DATA_ROOT, \"stage_2_test_images\")\n\nprint(\"LABELS_CSV exists:\", os.path.isfile(LABELS_CSV))\nprint(\"TRAIN_IMAGES_DIR exists:\", os.path.isdir(TRAIN_IMAGES_DIR))\nprint(\"TEST_IMAGES_DIR exists:\", os.path.isdir(TEST_IMAGES_DIR))\n","metadata":{},"outputs":[],"execution_count":null},{"id":"bd6cf737","cell_type":"code","source":"\n# =========================\n# Load labels and build binary dataframe\n# =========================\n\nlabels_raw = pd.read_csv(LABELS_CSV)\nprint(\"Raw labels shape:\", labels_raw.shape)\ndisplay(labels_raw.head())\n\nlabels_df = (\n    labels_raw.groupby(\"patientId\", as_index=False)[\"Target\"]\n    .max()\n    .rename(columns={\"Target\": \"label\"})\n)\n\nlabels_df[\"label\"] = labels_df[\"label\"].astype(int)\nlabels_df[\"label_name\"] = labels_df[\"label\"].map({0: \"No_Pneumonia\", 1: \"Pneumonia\"})\nlabels_df[\"image_path\"] = labels_df[\"patientId\"].apply(\n    lambda pid: os.path.join(TRAIN_IMAGES_DIR, f\"{pid}.dcm\")\n)\n\nlabels_df = labels_df[labels_df[\"image_path\"].apply(os.path.isfile)].copy()\nlabels_df[\"image_id\"] = labels_df[\"patientId\"] + \".dcm\"\n\nlabels_df = labels_df[[\"image_id\", \"image_path\", \"label_name\", \"label\"]].reset_index(drop=True)\n\nprint(\"Binary dataframe shape:\", labels_df.shape)\ndisplay(labels_df.head())\n\nprint(\"\\nClass distribution:\")\ndisplay(labels_df[\"label_name\"].value_counts())\n\nif labels_df.empty:\n    raise ValueError(\"No valid DICOM files were found.\")\nif labels_df[\"label\"].nunique() < 2:\n    raise ValueError(\"Need both classes to train.\")\n","metadata":{},"outputs":[],"execution_count":null},{"id":"7eeb3126","cell_type":"code","source":"\n# =========================\n# Train / validation split\n# =========================\n\ntrain_df, val_df = train_test_split(\n    labels_df,\n    test_size=VAL_SIZE,\n    random_state=SEED,\n    stratify=labels_df[\"label\"]\n)\n\ntrain_df = train_df.reset_index(drop=True)\nval_df = val_df.reset_index(drop=True)\n\nprint(\"Train shape:\", train_df.shape)\nprint(\"Val shape:\", val_df.shape)\n\nprint(\"\\nTrain distribution:\")\ndisplay(train_df[\"label_name\"].value_counts())\n\nprint(\"\\nVal distribution:\")\ndisplay(val_df[\"label_name\"].value_counts())\n","metadata":{},"outputs":[],"execution_count":null},{"id":"809536b0","cell_type":"code","source":"\n# =========================\n# DICOM loader\n# =========================\n\ndef load_dicom_as_rgb(path):\n    ds = pydicom.dcmread(path.numpy().decode(\"utf-8\"))\n    img = ds.pixel_array.astype(np.float32)\n\n    img = img - np.min(img)\n    max_val = np.max(img)\n    if max_val > 0:\n        img = img / max_val\n    img = (img * 255.0).astype(np.uint8)\n\n    img = np.stack([img, img, img], axis=-1)\n    return img\n\ndef tf_load_dicom(path, label):\n    img = tf.numpy_function(load_dicom_as_rgb, [path], tf.uint8)\n    img.set_shape([None, None, 3])\n    img = tf.image.resize(img, (IMG_SIZE, IMG_SIZE))\n    img = tf.cast(img, tf.float32)\n    img = preprocess_input(img)\n\n    label = tf.cast(label, tf.float32)\n    label = tf.expand_dims(label, axis=-1)\n    return img, label\n\ndef make_dataset(df_, training=False):\n    paths = df_[\"image_path\"].values\n    labels = df_[\"label\"].values.astype(\"float32\")\n\n    ds = tf.data.Dataset.from_tensor_slices((paths, labels))\n    ds = ds.map(tf_load_dicom, num_parallel_calls=tf.data.AUTOTUNE)\n\n    if training:\n        ds = ds.shuffle(min(len(df_), 4096), seed=SEED)\n\n    return ds.batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n\ntrain_ds = make_dataset(train_df, training=True)\nval_ds = make_dataset(val_df, training=False)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"2115ff11","cell_type":"code","source":"\n# =========================\n# Visual sanity check\n# =========================\n\nsample_batch = next(iter(train_ds))\nsample_images, sample_labels = sample_batch\n\nplt.figure(figsize=(12, 8))\nfor i in range(min(9, sample_images.shape[0])):\n    plt.subplot(3, 3, i + 1)\n    img = sample_images[i].numpy()\n    img_disp = img.copy()\n    img_disp = img_disp - img_disp.min()\n    if img_disp.max() > 0:\n        img_disp = img_disp / img_disp.max()\n    plt.imshow(img_disp)\n    plt.title(f\"label={int(sample_labels[i].numpy()[0])}\")\n    plt.axis(\"off\")\nplt.tight_layout()\nplt.show()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"bcdb52c5","cell_type":"code","source":"\n# =========================\n# Model\n# =========================\n\nbase_model = EfficientNetB2(\n    include_top=False,\n    weights=\"imagenet\",\n    input_shape=(IMG_SIZE, IMG_SIZE, 3)\n)\nbase_model.trainable = False\n\ninputs = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\nx = base_model(inputs, training=False)\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.Dropout(0.3)(x)\noutputs = layers.Dense(1, activation=\"sigmoid\")(x)\n\nmodel = models.Model(inputs, outputs)\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(1e-3),\n    loss=\"binary_crossentropy\",\n    metrics=[\n        \"accuracy\",\n        tf.keras.metrics.Precision(name=\"precision\")\n    ]\n)\n\nmodel.summary()\n","metadata":{},"outputs":[],"execution_count":null},{"id":"0225e4a0","cell_type":"code","source":"\n# =========================\n# Training\n# =========================\n\ncallbacks = [\n    tf.keras.callbacks.ModelCheckpoint(\n        filepath=os.path.join(WORKDIR, \"best_efficientnetb2_rsna_pneumonia.keras\"),\n        monitor=\"val_accuracy\",\n        mode=\"max\",\n        save_best_only=True,\n        verbose=1\n    ),\n    tf.keras.callbacks.EarlyStopping(\n        monitor=\"val_accuracy\",\n        mode=\"max\",\n        patience=3,\n        restore_best_weights=True,\n        verbose=1\n    )\n]\n\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS,\n    callbacks=callbacks\n)\n","metadata":{},"outputs":[],"execution_count":null},{"id":"c0aec52a","cell_type":"code","source":"\n# =========================\n# Evaluation\n# =========================\n\nval_probs = model.predict(val_ds, verbose=1).ravel()\ny_true = val_df[\"label\"].values\ny_pred = (val_probs >= 0.5).astype(int)\n\nacc = accuracy_score(y_true, y_pred)\nprec = precision_score(y_true, y_pred, zero_division=0)\nf1 = f1_score(y_true, y_pred, zero_division=0)\n\nprint(\"\\nMetrics:\")\nprint(f\"Accuracy  : {acc:.4f}\")\nprint(f\"Precision : {prec:.4f}\")\nprint(f\"F1-score  : {f1:.4f}\")\n\nmetrics_df = pd.DataFrame({\n    \"metric\": [\"accuracy\", \"precision\", \"f1_score\"],\n    \"value\": [acc, prec, f1]\n})\ndisplay(metrics_df)\n\nprint(\"\\nClassification report:\")\nprint(classification_report(y_true, y_pred, target_names=[\"No_Pneumonia\", \"Pneumonia\"], zero_division=0))\n\nprint(\"\\nConfusion matrix:\")\nprint(confusion_matrix(y_true, y_pred))\n","metadata":{},"outputs":[],"execution_count":null},{"id":"8dfc5ae2","cell_type":"code","source":"\n# =========================\n# Curves\n# =========================\n\nhistory_df = pd.DataFrame(history.history)\ndisplay(history_df.head())\n\nplt.figure(figsize=(8, 5))\nplt.plot(history.history[\"accuracy\"], label=\"train_acc\")\nplt.plot(history.history[\"val_accuracy\"], label=\"val_acc\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Accuracy\")\nplt.legend()\nplt.show()\n\nplt.figure(figsize=(8, 5))\nplt.plot(history.history[\"loss\"], label=\"train_loss\")\nplt.plot(history.history[\"val_loss\"], label=\"val_loss\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"Loss\")\nplt.legend()\nplt.show()\n","metadata":{},"outputs":[],"execution_count":null}]}