{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":10338,"databundleVersionId":862042}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"\n# Kermany Dataset Classification with EfficientNetB2 using split 80/10/10\n\nThis notebook keeps the same general style as the RSNA-based classification code, but adapts it to:\n\n`https://www.kaggle.com/competitions/rsna-pneumonia-detection-challenge`\n\n\n\nOutputs:\n- accuracy\n- precision\n- f1-score\n- recall\n- AUC\n  \n","metadata":{}},{"cell_type":"code","source":"import os\nimport random\nimport warnings\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport pydicom  # Nécessaire pour lire les fichiers .dcm de RSNA\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import (\n    classification_report, confusion_matrix,\n    accuracy_score, precision_score, recall_score,\n    f1_score, roc_auc_score\n)\nfrom sklearn.utils.class_weight import compute_class_weight\n\nimport tensorflow as tf\nfrom tensorflow.keras import layers, Model\nfrom tensorflow.keras.applications import EfficientNetB2\nfrom tensorflow.keras.applications.efficientnet import preprocess_input\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\n\nwarnings.filterwarnings(\"ignore\")\n\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\nprint(\"TensorFlow version:\", tf.__version__)\n\nIMG_SIZE = 224\nBATCH_SIZE = 8\nEPOCHS = 5\nTRAIN_SIZE = 0.8\nVAL_SIZE   = 0.1\nTEST_SIZE  = 0.1\nAUTOTUNE = tf.data.AUTOTUNE\n\n# --- 1) RSNA Dataset Paths ---\nDATA_ROOT = \"/kaggle/input/competitions/rsna-pneumonia-detection-challenge\"\nWORKDIR = \"/kaggle/working/rsna_efficientnetb2\"\nos.makedirs(WORKDIR, exist_ok=True)\n\nprint(\"DATA_ROOT:\", DATA_ROOT)\nprint(\"WORKDIR:\", WORKDIR)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-05-06T17:33:29.141486Z","iopub.execute_input":"2026-05-06T17:33:29.142357Z","iopub.status.idle":"2026-05-06T17:33:29.151445Z","shell.execute_reply.started":"2026-05-06T17:33:29.142324Z","shell.execute_reply":"2026-05-06T17:33:29.150515Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 2) Build dataframe from RSNA CSV ---\nlabels_csv = os.path.join(DATA_ROOT, \"stage_2_train_labels.csv\")\nif not os.path.exists(labels_csv):\n    raise FileNotFoundError(\"RSNA dataset not found. Please attach it to the notebook.\")\n\nraw_df = pd.read_csv(labels_csv)\n\n# Un patient peut avoir plusieurs boîtes englobantes, nous supprimons les doublons \n# car nous faisons uniquement de la classification binaire (0 ou 1)\nraw_df = raw_df.drop_duplicates(subset=[\"patientId\"]).reset_index(drop=True)\n\nrows = []\nimages_dir = os.path.join(DATA_ROOT, \"stage_2_train_images\")\n\nfor _, row in raw_df.iterrows():\n    pid = row[\"patientId\"]\n    target = int(row[\"Target\"])\n    fpath = os.path.join(images_dir, f\"{pid}.dcm\")\n    \n    if os.path.exists(fpath):\n        rows.append({\n            \"image_id\": f\"{pid}.dcm\",\n            \"image_path\": fpath,\n            \"label_name\": \"Pneumonia\" if target == 1 else \"Normal\",\n            \"label\": target,\n            \"original_split\": \"train\"\n        })\n\ndf = pd.DataFrame(rows)\n\nCLASS_NAMES = [\"Normal\", \"Pneumonia\"]\nNUM_CLASSES = len(CLASS_NAMES)\n\nprint(\"\\nShape:\", df.shape)\ndisplay(df.head())\n\nprint(\"\\nClass distribution:\")\ndisplay(df[\"label_name\"].value_counts())\n\nif df.empty:\n    raise ValueError(\"No images found after dataset discovery.\")\n\n# Stratification et splits identiques à l'original (80/10/10)\ntrain_df, temp_df = train_test_split(\n    df,\n    test_size=(VAL_SIZE + TEST_SIZE),\n    stratify=df[\"label\"],\n    random_state=SEED\n)\n\nval_df, test_df = train_test_split(\n    temp_df,\n    test_size=TEST_SIZE / (VAL_SIZE + TEST_SIZE),  # 0.5 ici\n    stratify=temp_df[\"label\"],\n    random_state=SEED\n)\n\ntrain_df = train_df.reset_index(drop=True)\nval_df   = val_df.reset_index(drop=True)\ntest_df  = test_df.reset_index(drop=True)\n\nprint(\"Train:\", train_df.shape)\nprint(\"Val  :\", val_df.shape)\nprint(\"Test :\", test_df.shape)\n\nclasses = np.array(sorted(train_df[\"label\"].unique()))\nweights = compute_class_weight(\n    class_weight=\"balanced\",\n    classes=classes,\n    y=train_df[\"label\"].values\n)\nclass_weight = {int(c): float(w) for c, w in zip(classes, weights)}\nprint(\"\\nClass weights:\", class_weight)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T17:33:29.220012Z","iopub.execute_input":"2026-05-06T17:33:29.220817Z","iopub.status.idle":"2026-05-06T17:33:47.633260Z","shell.execute_reply.started":"2026-05-06T17:33:29.220743Z","shell.execute_reply":"2026-05-06T17:33:47.632624Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 4) TensorFlow datasets ---\ndef read_dicom_tf(path_bytes):\n    # Fonction Python native pour lire le DICOM et le formater pour TF\n    path_str = path_bytes.decode('utf-8')\n    ds = pydicom.dcmread(path_str)\n    img = ds.pixel_array.astype(np.float32)\n    \n    # Normalisation 0-255 pour standardisation\n    img = (img - np.min(img)) / (np.max(img) - np.min(img) + 1e-8) * 255.0\n    \n    # Conversion de niveaux de gris vers RGB pour EfficientNet\n    if len(img.shape) == 2:\n        img = np.stack((img,)*3, axis=-1)\n        \n    return img\n\ndef decode_image(path):\n    # Enveloppe la fonction python native dans tf.numpy_function\n    img = tf.numpy_function(read_dicom_tf, [path], tf.float32)\n    img.set_shape((None, None, 3)) # Obligatoire pour utiliser tf.image.resize\n    img = tf.image.resize(img, (IMG_SIZE, IMG_SIZE))\n    img = preprocess_input(img)\n    return img\n\ndef process_path(path, label):\n    img = decode_image(path)\n    if NUM_CLASSES == 2:\n        label = tf.cast(label, tf.float32)\n        label = tf.expand_dims(label, axis=-1)\n    else:\n        label = tf.one_hot(tf.cast(label, tf.int32), NUM_CLASSES)\n    return img, label\n\ndef make_dataset(df, training=False):\n    ds = tf.data.Dataset.from_tensor_slices((\n        df[\"image_path\"].values,\n        df[\"label\"].values\n    ))\n    ds = ds.map(process_path, num_parallel_calls=AUTOTUNE)\n    if training:\n        ds = ds.shuffle(buffer_size=min(len(df), 4096), seed=SEED)\n    ds = ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n    return ds\n    \ntrain_ds = make_dataset(train_df, training=True)\nval_ds   = make_dataset(val_df, training=False)\ntest_ds  = make_dataset(test_df, training=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T17:33:47.634453Z","iopub.execute_input":"2026-05-06T17:33:47.634759Z","iopub.status.idle":"2026-05-06T17:33:47.768446Z","shell.execute_reply.started":"2026-05-06T17:33:47.634737Z","shell.execute_reply":"2026-05-06T17:33:47.767623Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 5) EfficientNetB2 model ---\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)\n\nif NUM_CLASSES == 2:\n    outputs = layers.Dense(1, activation=\"sigmoid\")(x)\n    loss_fn = \"binary_crossentropy\"\nelse:\n    outputs = layers.Dense(NUM_CLASSES, activation=\"softmax\")(x)\n    loss_fn = \"categorical_crossentropy\"\n\nmodel = Model(inputs, outputs)\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),\n    loss=loss_fn,\n    metrics=[\"accuracy\"]\n)\n\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T17:33:47.769585Z","iopub.execute_input":"2026-05-06T17:33:47.769853Z","iopub.status.idle":"2026-05-06T17:33:49.848235Z","shell.execute_reply.started":"2026-05-06T17:33:47.769831Z","shell.execute_reply":"2026-05-06T17:33:49.847598Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 6) Training ---\ncallbacks = [\n    ModelCheckpoint(\n        os.path.join(WORKDIR, \"best_rsna_efficientnetb2.keras\"),\n        monitor=\"val_accuracy\",\n        save_best_only=True,\n        mode=\"max\",\n        verbose=1\n    ),\n    EarlyStopping(\n        monitor=\"val_accuracy\",\n        patience=3,\n        mode=\"max\",\n        restore_best_weights=True,\n        verbose=1\n    ),\n    ReduceLROnPlateau(\n        monitor=\"val_loss\",\n        factor=0.5,\n        patience=2,\n        verbose=1\n    )\n]\n\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS,\n    class_weight=class_weight,\n    callbacks=callbacks,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T17:33:49.849512Z","iopub.execute_input":"2026-05-06T17:33:49.849834Z","iopub.status.idle":"2026-05-06T17:49:22.693013Z","shell.execute_reply.started":"2026-05-06T17:33:49.849812Z","shell.execute_reply":"2026-05-06T17:49:22.692310Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 7) Evaluation ---\ny_prob = model.predict(test_ds, verbose=1)\ny_true = test_df[\"label\"].values\n\nif NUM_CLASSES == 2:\n    y_prob = y_prob.ravel()\n    y_pred = (y_prob >= 0.5).astype(int)\n\n    acc    = accuracy_score(y_true, y_pred)\n    prec   = precision_score(y_true, y_pred, zero_division=0)\n    recall = recall_score(y_true, y_pred, zero_division=0)\n    f1     = f1_score(y_true, y_pred, zero_division=0)\n    auc    = roc_auc_score(y_true, y_prob)\n\nelse:\n    y_pred = np.argmax(y_prob, axis=1)\n\n    acc    = accuracy_score(y_true, y_pred)\n    prec   = precision_score(y_true, y_pred, average=\"macro\", zero_division=0)\n    recall = recall_score(y_true, y_pred, average=\"macro\", zero_division=0)\n    f1     = f1_score(y_true, y_pred, average=\"macro\", zero_division=0)\n    auc    = roc_auc_score(y_true, y_prob, multi_class=\"ovr\", average=\"macro\")\n\nprint(f\"Accuracy  : {acc:.4f}\")\nprint(f\"Precision : {prec:.4f}\")\nprint(f\"Recall    : {recall:.4f}\")\nprint(f\"F1-score  : {f1:.4f}\")\nprint(f\"AUC       : {auc:.4f}\")\n\nmetrics_df = pd.DataFrame({\n    \"metric\": [\"accuracy\", \"precision\", \"recall\", \"f1_score\", \"auc\"],\n    \"value\": [acc, prec, recall, f1, auc]\n})\ndisplay(metrics_df)\n\n\nprint(\"Classification report:\")\nprint(classification_report(y_true, y_pred, target_names=CLASS_NAMES, zero_division=0))\n\nprint(\"Confusion matrix:\")\nprint(confusion_matrix(y_true, y_pred))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T17:49:22.694531Z","iopub.execute_input":"2026-05-06T17:49:22.694911Z","iopub.status.idle":"2026-05-06T17:50:15.910118Z","shell.execute_reply.started":"2026-05-06T17:49:22.694888Z","shell.execute_reply":"2026-05-06T17:50:15.909474Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# --- 8) Learning curves ---\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()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-05-06T17:50:15.911232Z","iopub.execute_input":"2026-05-06T17:50:15.911520Z","iopub.status.idle":"2026-05-06T17:50:16.150256Z","shell.execute_reply.started":"2026-05-06T17:50:15.911499Z","shell.execute_reply":"2026-05-06T17:50:16.149644Z"}},"outputs":[],"execution_count":null}]}