{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":10338,"databundleVersionId":862042,"sourceType":"competition"},{"sourceId":12476632,"sourceType":"datasetVersion","datasetId":7852885}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip -q install optuna","metadata":{"_uuid":"dc5e86da-4c48-4700-858a-df657abd5538","_cell_guid":"ce3997ec-150c-459b-815b-46c5e82b4fc0","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:06.313921Z","iopub.execute_input":"2025-07-24T17:25:06.314254Z","iopub.status.idle":"2025-07-24T17:25:09.578294Z","shell.execute_reply.started":"2025-07-24T17:25:06.314222Z","shell.execute_reply":"2025-07-24T17:25:09.576986Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport gc\nimport cv2\nimport json\nimport pickle\nimport random\nimport optuna\nimport logging\nimport warnings\nimport pandas as pd\nimport numpy as np\nimport pydicom as dcm\nfrom tqdm import tqdm\nfrom enum import Enum\nimport seaborn as sns\nimport multiprocessing as mp\nfrom datetime import datetime\nimport matplotlib.pyplot as plt\nfrom collections import Counter\nwarnings.filterwarnings('ignore')\nfrom dataclasses import dataclass, asdict, field\nfrom sklearn.model_selection import train_test_split\nfrom typing import Dict, List, Optional, Callable, Any, Tuple\nfrom sklearn.metrics import f1_score, precision_score, recall_score, accuracy_score, roc_auc_score, confusion_matrix","metadata":{"_uuid":"d3b12eb7-efb9-4ce5-83a2-9f1d73984a21","_cell_guid":"6e2d9392-e405-4fb4-9029-9b6aff240b38","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:09.580326Z","iopub.execute_input":"2025-07-24T17:25:09.580651Z","iopub.status.idle":"2025-07-24T17:25:11.917112Z","shell.execute_reply.started":"2025-07-24T17:25:09.580615Z","shell.execute_reply":"2025-07-24T17:25:11.916517Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"try:\n    mp.set_start_method('spawn', force=True)\nexcept RuntimeError:\n    print(\"start method has already been set; ignore\")\n    pass\n\nfrom absl import logging as absl_logging\nabsl_logging.set_verbosity(absl_logging.ERROR)  # hide backend INFO & WARNING","metadata":{"_uuid":"63a5cd8c-9b2f-4845-a431-a3b1d9e6d5cb","_cell_guid":"8081af4b-d5f7-4570-b2ce-22e8a8ae485a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:11.917814Z","iopub.execute_input":"2025-07-24T17:25:11.918142Z","iopub.status.idle":"2025-07-24T17:25:11.941443Z","shell.execute_reply.started":"2025-07-24T17:25:11.918122Z","shell.execute_reply":"2025-07-24T17:25:11.940725Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set up logging\nos.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'        # only ERROR+FATAL from C++\nos.environ['TF_CUDNN_USE_AUTOTUNE'] = '0'       # turn off cuDNN auto‑tune (optional)\nos.environ['TF_DISABLE_XLA_SLOW_OP_ALARM'] = '1'\nos.environ['ABSL_CPP_MIN_LOG_LEVEL'] = '2'\nlogger = logging.getLogger(__name__)","metadata":{"_uuid":"d317a7d5-45d8-4346-8ca2-8879e9021c9d","_cell_guid":"ada959ed-69cb-4f1d-a7c4-a9edaccabbaa","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-07-24T17:25:11.943023Z","iopub.execute_input":"2025-07-24T17:25:11.943244Z","iopub.status.idle":"2025-07-24T17:25:11.954347Z","shell.execute_reply.started":"2025-07-24T17:25:11.943225Z","shell.execute_reply":"2025-07-24T17:25:11.953599Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Set seeds for reproducibility\nSEED = 23\nos.environ['PYTHONHASHSEED'] = str(SEED)\nrandom.seed(SEED)\nnp.random.seed(SEED)\n\nimport tensorflow as tf\ntf.random.set_seed(SEED)\ntf.get_logger().setLevel(logging.ERROR)","metadata":{"_uuid":"39a0e3c4-de69-4dff-9d8a-3475d6580ef4","_cell_guid":"57df4c0c-a883-4903-b4da-fe0c1a647877","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:11.955134Z","iopub.execute_input":"2025-07-24T17:25:11.955364Z","iopub.status.idle":"2025-07-24T17:25:25.14972Z","shell.execute_reply.started":"2025-07-24T17:25:11.955341Z","shell.execute_reply":"2025-07-24T17:25:25.148979Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Configure TensorFlow for memory optimization\ngpus = tf.config.experimental.list_physical_devices('GPU')\nif gpus:\n    try:\n        # Enable memory growth for GPU\n        for gpu in gpus:\n            tf.config.experimental.set_memory_growth(gpu, True)\n        print(f\"GPU memory growth enabled for {len(gpus)} GPU(s)\")\n    except RuntimeError as e:\n        print(e)","metadata":{"_uuid":"9beac944-d52c-4809-8653-35d7e7c318f6","_cell_guid":"cdbdfe8b-30f5-4087-bedf-c4467b84d1e1","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-07-24T17:25:25.150533Z","iopub.execute_input":"2025-07-24T17:25:25.151126Z","iopub.status.idle":"2025-07-24T17:25:25.978746Z","shell.execute_reply.started":"2025-07-24T17:25:25.1511Z","shell.execute_reply":"2025-07-24T17:25:25.977746Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow import keras\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.utils import to_categorical","metadata":{"_uuid":"05aa2892-f3a8-45c4-9695-be3c1fa8ca42","_cell_guid":"a91c7d3d-2d5f-4f33-acf7-7f228739af85","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:25.979774Z","iopub.execute_input":"2025-07-24T17:25:25.980097Z","iopub.status.idle":"2025-07-24T17:25:26.076308Z","shell.execute_reply.started":"2025-07-24T17:25:25.980064Z","shell.execute_reply":"2025-07-24T17:25:26.07552Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def clear_memory():\n    \"\"\"Comprehensive memory clearing function\"\"\"\n    # Clear TensorFlow session\n    tf.keras.backend.clear_session()\n    \n    # Force garbage collection\n    gc.collect()\n    \n    # Clear CUDA cache if available\n    if tf.config.list_physical_devices('GPU'):\n        try:\n            tf.keras.utils.clear_session()\n        except:\n            pass","metadata":{"_uuid":"4c99f705-29fd-4d82-b028-caa2bfae473a","_cell_guid":"fedb1d13-8a7c-4304-85df-04719a203877","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:26.077174Z","iopub.execute_input":"2025-07-24T17:25:26.077409Z","iopub.status.idle":"2025-07-24T17:25:26.082131Z","shell.execute_reply.started":"2025-07-24T17:25:26.077392Z","shell.execute_reply":"2025-07-24T17:25:26.081341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class RSNADataPreprocessor:\n    \"\"\"\n    Data preprocessing for RSNA Pneumonia Detection.\n    Handles loading/resizing/normalizing images via a streaming tf.data pipeline.\n    \"\"\"\n    def __init__(\n        self,\n        root_path: str,\n        df: pd.DataFrame,\n        image_size: Tuple[int, int] = (224, 224),\n        target_column: str = 'Target',\n        patient_id_column: str = 'patientId'\n    ):\n        self.root_path = root_path\n        self.df = df.copy()\n        self.image_size = image_size\n        self.target_column = target_column\n        self.patient_id_column = patient_id_column\n\n    def _load_raw_image(self, patient_id: bytes) -> np.ndarray:\n        \"\"\"\n        Read a single DICOM file (bytes→str) and return a uint8 H×W array.\n        Normalizes in float32 then scales back to uint8.\n        \"\"\"\n        if hasattr(patient_id, \"numpy\"):\n            patient_id = patient_id.numpy()\n        pid = patient_id.decode('utf-8')\n        \n        dicom_path = os.path.join(\n            self.root_path, 'stage_2_train_images', f\"{pid}\"\n        )\n        try:\n            ds = dcm.dcmread(dicom_path)\n            img = ds.pixel_array.astype(np.float32)\n            mn, mx = img.min(), img.max()\n            img = (img - mn) / max(mx - mn, 1e-6)  # Min Max scaler to 0-1 range\n            return img   \n        except Exception as e:\n            # fallback to black image\n            print('Something is seriously wrong with the DICOM file!!!')\n            print('\\n'*5, e)\n            return np.zeros(self.image_size, dtype=np.float32)\n\n    def preprocess_image(self, raw_img: np.ndarray) -> np.ndarray:\n        \"\"\"\n        Resize to self.image_size, convert to RGB if necessary, normalize to [0,1].\n        \"\"\"\n        resized = cv2.resize(raw_img, self.image_size, interpolation=cv2.INTER_AREA)\n        \n        # Handle channel conversions\n        if resized.ndim == 2:\n            resized = cv2.cvtColor(resized, cv2.COLOR_GRAY2RGB)\n        elif resized.shape[-1] == 1:\n            resized = cv2.cvtColor(resized, cv2.COLOR_GRAY2RGB)\n        elif resized.shape[-1] == 4:\n            resized = cv2.cvtColor(resized, cv2.COLOR_RGBA2RGB)\n        \n        # Normalize to [0,1]\n        return resized\n\n    def _load_and_preprocess(self, pid, label):\n        \"\"\"\n        TF wrapper: loads raw + preprocess_image → ([H,W,3], label)\n        \"\"\"\n        raw = tf.py_function(\n            func=self._load_raw_image, inp=[pid], Tout=tf.float32\n        )\n        raw.set_shape(self.image_size)\n        \n        img = tf.py_function(\n            func=lambda x: self.preprocess_image(x.numpy()), inp=[raw], Tout=tf.float32\n        )\n        img.set_shape((*self.image_size, 3))\n        return img, tf.cast(label, tf.int32)\n\n\n    def prepare_dataset(\n        self,\n        batch_size: int = 32,\n        validation_split: float = 0.15,\n        test_split: float = 0.15,\n        random_state: int = 42\n        ) -> Dict[str, tf.data.Dataset]:\n        \"\"\"\n        Builds train/val/test tf.data pipelines with stratified splits,\n        and prints class distribution per split.\n        \"\"\"\n        # 1) Extract IDs and labels\n        ids = self.df[self.patient_id_column].astype(str).tolist()\n        labels = self.df[self.target_column].tolist()\n        total = len(labels)\n    \n        # 2) Split off (val+test) from train\n        train_ids, rest_ids, train_labels, rest_labels = train_test_split(\n            ids, labels,\n            test_size=(validation_split + test_split),\n            stratify=labels,\n            random_state=random_state\n        )\n        # 3) Split rest → val / test\n        val_ids, test_ids, val_labels, test_labels = train_test_split(\n            rest_ids, rest_labels,\n            test_size=(test_split / (validation_split + test_split)),\n            stratify=rest_labels,\n            random_state=random_state\n        )\n    \n        # 4) Print class distributions\n        print(\"Total samples:\", total)\n        print(\"Train split:\", len(train_labels), \"->\", Counter(train_labels))\n        print(\"Validation split:\", len(val_labels), \"->\", Counter(val_labels))\n        print(\"Test split:\", len(test_labels), \"->\", Counter(test_labels))\n    \n        # 5) Build tf.data.Dataset for each\n        def make_ds(id_list, lbl_list, do_shuffle):\n            paths = [f\"{pid}.dcm\".encode() for pid in id_list]\n            ds = tf.data.Dataset.from_tensor_slices((paths, lbl_list))\n            if do_shuffle:\n                ds = ds.shuffle(buffer_size=len(id_list), seed=random_state)\n            ds = ds.map(self._load_and_preprocess, num_parallel_calls=tf.data.AUTOTUNE)\n            ds = ds.batch(batch_size)\n            return ds.prefetch(tf.data.AUTOTUNE)\n    \n        result = {\n            \"train_dataset\": make_ds(train_ids, train_labels, do_shuffle=True),\n            \"val_dataset\":   make_ds(val_ids,   val_labels,   do_shuffle=False),\n            \"test_dataset\":  make_ds(test_ids,  test_labels,  do_shuffle=False),\n            \"input_shape\":   (*self.image_size, 3),\n            \"num_classes\":   int(len(np.unique(labels))),\n        }\n    \n        # Cleanup\n        del ids, labels\n        del train_ids, train_labels, rest_ids, rest_labels\n        del val_ids, val_labels, test_ids, test_labels\n        gc.collect()\n    \n        return result","metadata":{"_uuid":"e5a9b891-42a7-43f6-8bbc-ba576764b630","_cell_guid":"9e0d4eb1-3e9e-4820-a5d0-d1abeefb7408","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:26.083158Z","iopub.execute_input":"2025-07-24T17:25:26.083388Z","iopub.status.idle":"2025-07-24T17:25:26.103421Z","shell.execute_reply.started":"2025-07-24T17:25:26.083358Z","shell.execute_reply":"2025-07-24T17:25:26.102653Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"PATH = \"/kaggle/input/rsna-pneumonia-detection-challenge\"","metadata":{"_uuid":"021b617a-f5d2-457c-955d-98f239fb36d9","_cell_guid":"2dcf4c23-ffb8-421c-81e9-a033b67a7caa","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:26.105746Z","iopub.execute_input":"2025-07-24T17:25:26.105955Z","iopub.status.idle":"2025-07-24T17:25:26.125583Z","shell.execute_reply.started":"2025-07-24T17:25:26.105938Z","shell.execute_reply":"2025-07-24T17:25:26.124769Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import sys\ndataset_path = '/kaggle/input/rsna-pneumonia-dataset'\nsys.path.append(dataset_path)","metadata":{"_uuid":"a156e57c-4079-4a18-843d-3665d82a945e","_cell_guid":"ff8e9210-5d96-4b9c-8a5e-0ca5162c39ba","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:26.126341Z","iopub.execute_input":"2025-07-24T17:25:26.126606Z","iopub.status.idle":"2025-07-24T17:25:26.144174Z","shell.execute_reply.started":"2025-07-24T17:25:26.126582Z","shell.execute_reply":"2025-07-24T17:25:26.143312Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(f'{dataset_path}/train_class_df.csv')\nprint('Shape of the dataset:', df.shape)","metadata":{"_uuid":"96147fdf-cdb1-449d-a77e-1d5aec9368f4","_cell_guid":"8467549c-cff4-49be-ae5e-b33c55694da6","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:26.145214Z","iopub.execute_input":"2025-07-24T17:25:26.145542Z","iopub.status.idle":"2025-07-24T17:25:26.204225Z","shell.execute_reply.started":"2025-07-24T17:25:26.145518Z","shell.execute_reply":"2025-07-24T17:25:26.203307Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# show sample of the dataset\ndf.sample()","metadata":{"_uuid":"82d9d219-c79e-4d92-8927-926ed2265bff","_cell_guid":"d686b58c-6170-4fc6-b0fe-cbea5581fb4e","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:26.205114Z","iopub.execute_input":"2025-07-24T17:25:26.205366Z","iopub.status.idle":"2025-07-24T17:25:26.231239Z","shell.execute_reply.started":"2025-07-24T17:25:26.205336Z","shell.execute_reply":"2025-07-24T17:25:26.230638Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# view the class distribution of the dataset\ndf.Target.value_counts()","metadata":{"_uuid":"a35d7aa7-7c91-4017-ac86-318d2c12b3d1","_cell_guid":"e17da7c3-870d-493c-af3e-6aa06170db11","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:26.232093Z","iopub.execute_input":"2025-07-24T17:25:26.232363Z","iopub.status.idle":"2025-07-24T17:25:26.244002Z","shell.execute_reply.started":"2025-07-24T17:25:26.23234Z","shell.execute_reply":"2025-07-24T17:25:26.243441Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"preprocessor = RSNADataPreprocessor(\n    root_path=PATH,\n    df=df\n)","metadata":{"_uuid":"c61df31f-2509-4652-b47f-b10ad1d95dae","_cell_guid":"18a002de-a7af-4f87-a842-40ac81dfb7ef","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:26.244696Z","iopub.execute_input":"2025-07-24T17:25:26.244958Z","iopub.status.idle":"2025-07-24T17:25:26.258828Z","shell.execute_reply.started":"2025-07-24T17:25:26.244933Z","shell.execute_reply":"2025-07-24T17:25:26.258274Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Prepare dataset\ndata_dict = preprocessor.prepare_dataset(\n    batch_size=64,\n    validation_split=0.15,\n    test_split=0.15,\n    random_state=SEED\n)\n\ntrain_dataset = data_dict['train_dataset']\nval_dataset = data_dict['val_dataset']\ntest_dataset = data_dict['test_dataset']","metadata":{"_uuid":"7a7d2e56-7172-4633-b24e-6dd482a9539a","_cell_guid":"08b2e6a5-37fa-41d4-9885-f4fe44df3d41","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:26.259589Z","iopub.execute_input":"2025-07-24T17:25:26.25983Z","iopub.status.idle":"2025-07-24T17:25:28.432486Z","shell.execute_reply.started":"2025-07-24T17:25:26.259806Z","shell.execute_reply":"2025-07-24T17:25:28.431824Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Custom F1 Score metric for Keras\ndef f1_score_metric(y_true, y_pred):\n    \"\"\"Custom F1 score metric for binary classification in Keras\"\"\"\n    y_pred = tf.cast(y_pred > 0.5, tf.float32)\n    y_true = tf.cast(y_true, tf.float32)\n    \n    # Calculate precision and recall\n    tp = tf.reduce_sum(y_true * y_pred)\n    fp = tf.reduce_sum((1 - y_true) * y_pred)\n    fn = tf.reduce_sum(y_true * (1 - y_pred))\n    \n    precision = tp / (tp + fp + tf.keras.backend.epsilon())\n    recall = tp / (tp + fn + tf.keras.backend.epsilon())\n    \n    f1 = 2 * precision * recall / (precision + recall + tf.keras.backend.epsilon())\n    return f1","metadata":{"_uuid":"fcccd554-75e8-48d6-9917-31fc1a858193","_cell_guid":"f61e993c-49b8-40f6-9bce-1b047374870a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:28.43329Z","iopub.execute_input":"2025-07-24T17:25:28.433525Z","iopub.status.idle":"2025-07-24T17:25:28.438667Z","shell.execute_reply.started":"2025-07-24T17:25:28.433507Z","shell.execute_reply":"2025-07-24T17:25:28.43799Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Hyperparameter Module","metadata":{"_uuid":"2d629645-f066-4e76-87b0-2fea97afb93b","_cell_guid":"12cc8e60-316f-44bd-840f-6b1fcdc5fde1","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"class OptimizerType:\n    ADAM = \"adam\"\n    SGD = \"sgd\"\n\nclass ArchitectureType:\n    VGG16 = \"vgg16\"\n    RESNET50 = \"resnet50\"\n    INCEPTIONV3 = \"inceptionv3\"\n    MOBILENETV2 = \"mobilenetv2\"\n\nclass SamplerType:\n    RANDOM = \"Random_Search\"\n    BAYESIAN = \"Bayesian_Optimization\"\n    GRID = \"Grid_Search\"\n\nclass HyperparameterType:\n    LEARNING_RATE = \"learning_rate\"\n    BATCH_SIZE = \"batch_size\"\n    DROPOUT_RATE = \"dropout_rate\"\n    EPOCHS = \"epochs\"\n    OPTIMIZER = \"optimizer\"\n    ALL = \"all\"","metadata":{"_uuid":"c00aa729-2b79-4ab4-81db-a8a563cf73f6","_cell_guid":"30148c91-ae3f-40f2-86e5-f606c13267bd","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:28.439318Z","iopub.execute_input":"2025-07-24T17:25:28.440173Z","iopub.status.idle":"2025-07-24T17:25:28.476368Z","shell.execute_reply.started":"2025-07-24T17:25:28.44015Z","shell.execute_reply":"2025-07-24T17:25:28.475688Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@dataclass\nclass HyperparameterSpace:\n    \"\"\"Configuration class for hyperparameter search spaces\"\"\"\n    learning_rate: List[float] = field(default_factory=lambda: [1e-5, 1e-4, 1e-3, 1e-2, 1e-1])\n    batch_size: List[int] = field(default_factory=lambda: [16, 32, 64, 128, 256])\n    dropout_rate: List[float] = field(default_factory=lambda: [0.1, 0.2, 0.3, 0.4, 0.5])\n    epochs: List[int] = field(default_factory=lambda: [10, 20, 30, 40, 50])\n    optimizers: List[str] = field(default_factory=lambda: [OptimizerType.ADAM, OptimizerType.SGD])\n\n    \n    \n    def __post_init__(self):\n        if self.optimizers is None:\n            self.optimizers = [OptimizerType.ADAM, OptimizerType.SGD]\n        if self.batch_size is None:\n            self.batch_size = [16, 32, 64, 128, 256]\n        if self.epochs is None:\n            self.epochs = list(range(10, 51, 10))  \n\n@dataclass\nclass DefaultHyperparameters:\n    \"\"\"Default hyperparameter values\"\"\"\n    learning_rate: float = 0.001\n    batch_size: int = 64\n    optimizer: str = OptimizerType.ADAM\n    epochs: int = 20  \n    dropout_rate: float = 0.2\n\n@dataclass\nclass ExperimentResult:\n    \"\"\"Data class to store experiment results\"\"\"\n    trial_number: int\n    hyperparameters: Dict[str, Any]\n    train_metrics: Dict[str, float]\n    val_metrics: Dict[str, float]\n    test_metrics: Dict[str, float]\n    history: Dict[str, List[float]]\n    min_train_loss: float\n    min_val_loss: float\n    max_train_f1: float\n    max_val_f1: float\n    max_train_accuracy: float  \n    max_val_accuracy: float   \n    training_time: float\n    final_epoch: int\n    # store test confusion matrix\n    test_confusion_matrix: List[List[int]]","metadata":{"_uuid":"495c1a83-0d31-40ba-afca-419ab9d7d03d","_cell_guid":"fa2d5b87-039e-4292-8bc8-1d5762edefdf","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:28.477283Z","iopub.execute_input":"2025-07-24T17:25:28.477684Z","iopub.status.idle":"2025-07-24T17:25:28.498302Z","shell.execute_reply.started":"2025-07-24T17:25:28.477659Z","shell.execute_reply":"2025-07-24T17:25:28.497631Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def model_builder(\n    input_shape: Tuple[int, int, int],\n    params: Dict[str, Any],\n    architecture: str = 'vgg16'\n) -> keras.Model:\n    \"\"\"\n    Build a binary-classification model with a pretrained backbone.\n    \"\"\"\n    arch = architecture.lower()\n    backbones = {\n        'vgg16': keras.applications.VGG16,\n        'resnet50': keras.applications.ResNet50,\n        'inceptionv3': keras.applications.InceptionV3,\n        'mobilenetv2': keras.applications.MobileNetV2,\n    }\n    \n    if arch not in backbones:\n        raise ValueError(f\"Unsupported architecture: {arch}\")\n    \n    BaseModel = backbones[arch]\n    \n    # Create base model with memory optimization\n    base = BaseModel(\n        weights='imagenet',\n        include_top=False,\n        input_shape=input_shape\n    )\n    base.trainable = False\n    \n    inputs = keras.Input(shape=input_shape)\n    x = base(inputs, training=False)\n    x = layers.GlobalAveragePooling2D()(x)\n    \n    dr = params.get('dropout_rate', 0.2)\n    if not 0 <= dr <= 1: \n        raise ValueError(\"`dropout_rate` must be between 0 and 1.\")\n        \n    x = layers.Dropout(dr)(x)\n    outputs = layers.Dense(1, activation='sigmoid')(x)\n    \n    model = models.Model(inputs, outputs, name=f\"{arch}_binary\")\n    return model","metadata":{"_uuid":"2aaeefd6-738a-455c-959a-38e1e512dd7f","_cell_guid":"28ccc840-2dcc-4b9c-b043-1951ba7bf069","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:28.499086Z","iopub.execute_input":"2025-07-24T17:25:28.499339Z","iopub.status.idle":"2025-07-24T17:25:28.521194Z","shell.execute_reply.started":"2025-07-24T17:25:28.499317Z","shell.execute_reply":"2025-07-24T17:25:28.520385Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def create_batched_dataset(dataset, batch_size):\n    \"\"\"Create batched dataset from unbatched dataset with memory optimization\"\"\"\n    return dataset.batch(batch_size).prefetch(tf.data.AUTOTUNE)  # Reduced prefetch buffer or use AUTOTUNE","metadata":{"_uuid":"d9271a96-eed7-4b47-a1be-7479b8b745d0","_cell_guid":"65be914c-89fb-4f13-9090-bdaccfa9c1f2","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:28.522004Z","iopub.execute_input":"2025-07-24T17:25:28.522265Z","iopub.status.idle":"2025-07-24T17:25:28.541246Z","shell.execute_reply.started":"2025-07-24T17:25:28.522239Z","shell.execute_reply":"2025-07-24T17:25:28.540502Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CNNHyperparameterOptimizer:\n    \"\"\"Memory-optimized CNN hyperparameter optimizer using Optuna\"\"\"\n    \n    def __init__(self, train_dataset, val_dataset, test_dataset, \n                 hyperparameter_space: HyperparameterSpace,\n                 architecture: str = ArchitectureType.VGG16,\n                 input_shape: Tuple[int, int, int] = (224, 224, 3),\n                 num_classes: int = 2,\n                 results_dir: str = \"hyperparameter_results\",\n                 default_hyperparameters: DefaultHyperparameters = None):\n        \n        self.train_dataset = train_dataset.unbatch()\n        self.val_dataset = val_dataset.unbatch()\n        self.test_dataset = test_dataset.unbatch()\n        self.hyperparameter_space = hyperparameter_space\n        self.architecture = architecture\n        self.input_shape = input_shape\n        self.num_classes = num_classes\n        self.results_dir = results_dir\n        self.default_hyperparameters = default_hyperparameters or DefaultHyperparameters()\n        \n        # Create results directory\n        os.makedirs(self.results_dir, exist_ok=True)\n        \n        # Store experiment results count instead of all results to save memory\n        self.experiment_count = 0\n        \n    def create_cnn_model(self, trial, optimize_param: str = HyperparameterType.ALL):\n        \"\"\"Create CNN model with hyperparameters from trial\"\"\"\n        \n        # Start with default values\n        hyperparams = {\n            'learning_rate': self.default_hyperparameters.learning_rate,\n            'batch_size': self.default_hyperparameters.batch_size,\n            'dropout_rate': self.default_hyperparameters.dropout_rate,\n            'optimizer': self.default_hyperparameters.optimizer,\n            'epochs': self.default_hyperparameters.epochs\n        }\n        \n        # Override with trial suggestions based on what we're optimizing\n        if optimize_param == HyperparameterType.ALL:\n            hyperparams['learning_rate'] = trial.suggest_categorical(\n                'learning_rate', \n                self.hyperparameter_space.learning_rate\n            )\n            hyperparams['batch_size'] = trial.suggest_categorical(\n                'batch_size', \n                self.hyperparameter_space.batch_size\n            )\n            hyperparams['dropout_rate'] = trial.suggest_categorical(\n                'dropout_rate',\n                self.hyperparameter_space.dropout_rate\n            )\n            hyperparams['optimizer'] = trial.suggest_categorical(\n                'optimizer', \n                self.hyperparameter_space.optimizers\n            )\n            hyperparams['epochs'] = trial.suggest_categorical(\n                'epochs', \n                self.hyperparameter_space.epochs\n            )\n        elif optimize_param == HyperparameterType.LEARNING_RATE:\n            hyperparams['learning_rate'] = trial.suggest_categorical(\n                'learning_rate', \n                self.hyperparameter_space.learning_rate\n            )\n        elif optimize_param == HyperparameterType.BATCH_SIZE:\n            hyperparams['batch_size'] = trial.suggest_categorical(\n                'batch_size', \n                self.hyperparameter_space.batch_size\n            )\n        elif optimize_param == HyperparameterType.DROPOUT_RATE:\n            hyperparams['dropout_rate'] = trial.suggest_categorical(\n                'dropout_rate',\n                self.hyperparameter_space.dropout_rate\n            )\n        elif optimize_param == HyperparameterType.OPTIMIZER:\n            hyperparams['optimizer'] = trial.suggest_categorical(\n                'optimizer', \n                self.hyperparameter_space.optimizers\n            )\n        elif optimize_param == HyperparameterType.EPOCHS:\n            hyperparams['epochs'] = trial.suggest_categorical(\n                'epochs', \n                self.hyperparameter_space.epochs\n            )\n        \n        # Create model parameters\n        model_params = {\n            'dropout_rate': hyperparams['dropout_rate']\n        }\n        \n        # Create model using model_builder\n        model = model_builder(self.input_shape, model_params, self.architecture)\n        \n        # Configure optimizer\n        if hyperparams['optimizer'] == OptimizerType.ADAM:\n            optimizer = keras.optimizers.Adam(learning_rate=hyperparams['learning_rate'])\n        elif hyperparams['optimizer'] == OptimizerType.SGD:\n            optimizer = keras.optimizers.SGD(learning_rate=hyperparams['learning_rate'], momentum=0.9)\n        \n        # Compile model with F1 score metric\n        model.compile(\n            optimizer=optimizer,\n            loss='binary_crossentropy',\n            metrics=[f1_score_metric, 'accuracy']  # Added custom F1 score metric\n        )\n        \n        return model, hyperparams\n    \n    def evaluate_model_on_dataset(self, model, dataset):\n        \"\"\"Evaluate model on dataset and return true labels and predictions\"\"\"\n        \n        y_true_list = []\n        y_pred_list = []\n        \n        for batch_x, batch_y in dataset:\n            # Get predictions\n            predictions = model.predict(batch_x, verbose=0)\n            \n            # Store true labels and predictions\n            y_true_list.append(batch_y.numpy())\n            y_pred_list.append(predictions)\n        \n        # Concatenate all batches\n        y_true = np.concatenate(y_true_list, axis=0)\n        y_pred = np.concatenate(y_pred_list, axis=0)\n        \n        return y_true, y_pred\n    \n    # Modified calculate_metrics method to include confusion matrix\n    def calculate_metrics(self, y_true, y_pred):\n        \"\"\"Calculate comprehensive metrics including AUC and confusion matrix\"\"\"\n        \n        # Convert predictions to proper format\n        if len(y_pred.shape) > 1 and y_pred.shape[1] == 1:\n            y_pred_classes = (y_pred > 0.5).astype(int).flatten()\n            y_pred_probs = y_pred.flatten()\n        else:\n            y_pred_classes = (y_pred > 0.5).astype(int)\n            y_pred_probs = y_pred\n        \n        if len(y_true.shape) > 1:\n            y_true = y_true.flatten()\n        \n        y_true = y_true.astype(int)\n        \n        try:\n            auc_score = float(roc_auc_score(y_true, y_pred_probs))\n        except ValueError:\n            auc_score = 0.5\n        \n        # Calculate confusion matrix\n        cm = confusion_matrix(y_true, y_pred_classes)\n        \n        metrics = {\n            'accuracy': float(accuracy_score(y_true, y_pred_classes)),\n            'precision': float(precision_score(y_true, y_pred_classes, average='binary', zero_division=0)),\n            'recall': float(recall_score(y_true, y_pred_classes, average='binary', zero_division=0)),\n            'f1_score': float(f1_score(y_true, y_pred_classes, average='binary', zero_division=0)),\n            'auc': auc_score,\n            'confusion_matrix': cm.tolist()  # Convert to list for JSON serialization\n        }\n        \n        # Clean up temporary variables\n        del y_pred_classes, y_pred_probs\n        gc.collect()\n        \n        return metrics\n    \n    # Modified objective method to use F1 score for optimization and save test confusion matrix\n    def objective(self, trial, optimize_param: str = HyperparameterType.ALL):\n        \"\"\"Memory-optimized objective function for Optuna optimization\"\"\"\n\n        start_time = datetime.now()\n        try:\n            # Create model and get hyperparameters\n            model, hyperparams = self.create_cnn_model(trial, optimize_param)\n            \n            # Create batched datasets with the chosen batch size\n            train_batched = create_batched_dataset(self.train_dataset, hyperparams['batch_size'])\n            val_batched = create_batched_dataset(self.val_dataset, hyperparams['batch_size'])\n            test_batched = create_batched_dataset(self.test_dataset, hyperparams['batch_size'])\n            \n            # Train model\n            history = model.fit(\n                train_batched,\n                validation_data=val_batched,\n                epochs=hyperparams['epochs'],\n                verbose=0\n            )\n            \n            # Get training time\n            training_time = (datetime.now() - start_time).total_seconds()\n            \n            # Calculate metrics on all datasets\n            train_y_true, train_y_pred = self.evaluate_model_on_dataset(model, train_batched)\n            val_y_true, val_y_pred = self.evaluate_model_on_dataset(model, val_batched)\n            test_y_true, test_y_pred = self.evaluate_model_on_dataset(model, test_batched)\n            \n            train_metrics = self.calculate_metrics(train_y_true, train_y_pred)\n            val_metrics = self.calculate_metrics(val_y_true, val_y_pred)\n            test_metrics = self.calculate_metrics(test_y_true, test_y_pred)\n            \n            # Extract history statistics - include both F1 and accuracy\n            min_train_loss = float(min(history.history['loss']))\n            min_val_loss = float(min(history.history['val_loss']))\n            max_train_f1 = float(max(history.history['f1_score_metric']))\n            max_val_f1 = float(max(history.history['val_f1_score_metric']))\n            max_train_accuracy = float(max(history.history['accuracy']))  # Added max train accuracy\n            max_val_accuracy = float(max(history.history['val_accuracy']))  # Added max val accuracy\n            final_epoch = len(history.history['loss'])\n            \n            # Convert history to JSON serializable format\n            history_dict = {\n                'loss': [float(v) for v in history.history['loss']],\n                'val_loss': [float(v) for v in history.history['val_loss']],\n                'f1_score_metric': [float(v) for v in history.history['f1_score_metric']],\n                'val_f1_score_metric': [float(v) for v in history.history['val_f1_score_metric']],\n                'accuracy': [float(v) for v in history.history['accuracy']],\n                'val_accuracy': [float(v) for v in history.history['val_accuracy']]\n            }\n            \n            # Store experiment result\n            hyperparameters_with_config = hyperparams.copy()\n            hyperparameters_with_config['architecture'] = self.architecture\n            hyperparameters_with_config['freeze_base'] = True\n            hyperparameters_with_config['optimized_parameter'] = optimize_param\n            \n            experiment_result = ExperimentResult(\n                trial_number=trial.number,\n                hyperparameters=hyperparameters_with_config,\n                train_metrics=train_metrics,\n                val_metrics=val_metrics,\n                test_metrics=test_metrics,\n                history=history_dict,\n                min_train_loss=min_train_loss,\n                min_val_loss=min_val_loss,\n                max_train_f1=max_train_f1,\n                max_val_f1=max_val_f1,\n                max_train_accuracy=max_train_accuracy,  # Added max train accuracy\n                max_val_accuracy=max_val_accuracy,      # Added max val accuracy\n                training_time=training_time,\n                final_epoch=final_epoch,\n                # Only store test confusion matrix\n                test_confusion_matrix=test_metrics['confusion_matrix']\n            )\n            \n            # Save confusion matrix plot (only for test set)\n            self.save_confusion_matrix_plot(experiment_result)\n            \n            # Save experiment result immediately\n            self.save_experiment_result(experiment_result)\n            self.experiment_count += 1\n            \n            # Get validation f1_score for optimization\n            val_score = val_metrics['f1_score']\n            \n            # Comprehensive cleanup\n            del (model, train_batched, val_batched, test_batched, history,\n                 train_y_true, train_y_pred, val_y_true, val_y_pred, \n                 test_y_true, test_y_pred, experiment_result, history_dict)\n            \n            # Clear memory\n            clear_memory()\n            \n            return val_score\n            \n        except Exception as e:\n            print('*'*60, '\\n'*5, f\"Trial {trial.number} failed with error: {str(e)}\", '*'*60, '\\n'*5)\n            clear_memory()\n            return 0.0\n    \n    # Modified method to save only test confusion matrix plot\n    def save_confusion_matrix_plot(self, result: ExperimentResult):\n        \"\"\"Save confusion matrix plot for test set only\"\"\"\n        \n        # Create confusion matrix directory\n        cm_dir = os.path.join(self.current_results_dir, self.sampler_type, 'confusion_matrices')\n        os.makedirs(cm_dir, exist_ok=True)\n        \n        plt.figure(figsize=(8, 6))\n        sns.heatmap(result.test_confusion_matrix, annot=True, fmt='d', cmap='Blues', \n                   xticklabels=['No Pneumonia', 'Pneumonia'],\n                   yticklabels=['No Pneumonia', 'Pneumonia'])\n        plt.title(f'Test Set Confusion Matrix\\nTrial {result.trial_number}')\n        plt.xlabel('Predicted')\n        plt.ylabel('Actual')\n        \n        # Create filename\n        filename = f'confusion_matrix_trial_{result.trial_number:03d}_test.png'\n        filepath = os.path.join(cm_dir, filename)\n        \n        plt.savefig(filepath, dpi=300, bbox_inches='tight')\n        plt.close()  # Close figure to save memory\n    \n    # Modified save_experiment_result method to print both F1 and accuracy\n    def save_experiment_result(self, result: ExperimentResult):\n        \"\"\"Save individual experiment result with memory optimization\"\"\"\n        \n        # Convert result to dictionary\n        result_dict = asdict(result)\n        \n        # Add minimal experiment setup information\n        result_dict['experiment_setup'] = {\n            'architecture': self.architecture,\n            'freeze_base': True,\n            'input_shape': self.input_shape,\n            'num_classes': self.num_classes,\n            'optimized_parameter': result.hyperparameters.get('optimized_parameter', 'all')\n        }\n        \n        # Create descriptive filename\n        timestamp = datetime.now().strftime(\"%Y%m%d_%H%M%S\")\n        arch = self.architecture\n        opt = result.hyperparameters.get('optimizer', 'unknown')\n        batch_size = result.hyperparameters.get('batch_size', 'unknown')\n        lr = result.hyperparameters.get('learning_rate', 0)\n        dropout = result.hyperparameters.get('dropout_rate', 0)\n        epochs = result.hyperparameters.get('epochs', 0)\n        optimized_param = result.hyperparameters.get('optimized_parameter', 'all')\n        \n        lr_str = f\"{lr:.0e}\" if lr > 0 else \"0\"\n        \n        filename = (f\"experiment_trial_{result.trial_number:03d}_{arch}_{optimized_param}_{opt}_\"\n                   f\"bs{batch_size}_lr{lr_str}_drop{dropout:.2f}_ep{epochs}_{timestamp}.json\")\n        \n        # Create sampler-specific directory\n        sampler_dir = os.path.join(self.current_results_dir, self.sampler_type)\n        os.makedirs(sampler_dir, exist_ok=True)\n        \n        filepath = os.path.join(sampler_dir, filename)\n        \n        # Save as JSON\n        with open(filepath, 'w') as f:\n            json.dump(result_dict, f, indent=2, default=str)\n        \n        # Print concise trial information with both F1 and accuracy\n        print(f\"Trial {result.trial_number:3d}: \"\n              f\"Val F1={result.val_metrics['f1_score']:.4f}, \"\n              f\"Val Acc={result.val_metrics['accuracy']:.4f}, \"\n              f\"Test F1={result.test_metrics['f1_score']:.4f}, \"\n              f\"Test Acc={result.test_metrics['accuracy']:.4f}, \"\n              f\"Time={result.training_time:.1f}s\")\n\n    def optimize(self, n_trials: int = 50, \n                 sampler_type: str = SamplerType.BAYESIAN,\n                 optimize_param: str = HyperparameterType.ALL):\n        \"\"\"Run hyperparameter optimization\"\"\"\n\n        self.sampler_type = sampler_type\n        self.optimize_param = optimize_param\n        \n        # Create sampler-specific results directory\n        param_suffix = f\"_{optimize_param}\" if optimize_param != HyperparameterType.ALL else \"\"\n        self.current_results_dir = f\"{self.results_dir}{param_suffix}\"\n        os.makedirs(self.current_results_dir, exist_ok=True)\n        \n        # Choose sampler\n        if sampler_type == SamplerType.RANDOM:\n            sampler = optuna.samplers.RandomSampler(seed=SEED)\n        elif sampler_type == SamplerType.GRID:\n            # build the search_space dict for GridSampler\n            if optimize_param == HyperparameterType.ALL:\n                search_space = {\n                    'learning_rate': self.hyperparameter_space.learning_rate,\n                    'batch_size':    self.hyperparameter_space.batch_size,\n                    'dropout_rate':  self.hyperparameter_space.dropout_rate,\n                    'optimizer':     self.hyperparameter_space.optimizers,\n                    'epochs':        self.hyperparameter_space.epochs,\n                }\n            else:\n                # only grid over the single parameter\n                search_space = {\n                    optimize_param: getattr(self.hyperparameter_space, optimize_param)\n                }\n        \n            sampler = optuna.samplers.GridSampler(search_space)\n\n        else:\n            # Use TPESampler with MedianPruner for Bayesian optimization\n            sampler = optuna.samplers.TPESampler(seed=SEED)\n        \n        # Create pruner for Bayesian optimization\n        pruner = None\n        if sampler_type == SamplerType.BAYESIAN:\n            pruner = optuna.pruners.MedianPruner(\n                n_startup_trials=3, # start pruning after 3 trials\n                n_warmup_steps=5,  # allow 5 epochs before pruning\n                interval_steps=2   # check pruning every 2 epochs\n            )\n        \n        # Create study\n        study = optuna.create_study(\n            direction='maximize',\n            sampler=sampler,\n            pruner=pruner,\n            study_name=f\"CNN_Hyperparameter_Exploration_{optimize_param}_{datetime.now().strftime('%Y%m%d_%H%M%S')}\"\n        )\n        \n        # Run optimization\n        print(f\"Starting hyperparameter exploration with {n_trials} trials...\")\n        print(f\"Architecture: {self.architecture}\")\n        print(f\"Sampler: {sampler_type}\")\n        print(f\"Optimizing parameter: {optimize_param}\")\n        if pruner:\n            print(f\"Pruner: {pruner.__class__.__name__}\")\n        print(f\"Target metric: Validation Accuracy\")\n        print(f\"Results will be saved to: {self.current_results_dir}\")\n        \n        # Print default values being used\n        print(\"\\nDefault hyperparameter values:\")\n        print(f\"  Learning rate: {self.default_hyperparameters.learning_rate}\")\n        print(f\"  Batch size: {self.default_hyperparameters.batch_size}\")\n        print(f\"  Dropout rate: {self.default_hyperparameters.dropout_rate}\")\n        print(f\"  Optimizer: {self.default_hyperparameters.optimizer}\")\n        print(f\"  Epochs: {self.default_hyperparameters.epochs}\")\n        \n        if optimize_param != HyperparameterType.ALL:\n            print(f\"\\nOnly '{optimize_param}' will be optimized, others will use default values.\")\n        \n        print(\"-\" * 100)\n        \n        # Create objective function with optimize_param\n        def objective_with_param(trial):\n            return self.objective(trial, optimize_param)\n        \n        study.optimize(objective_with_param, n_trials=n_trials)\n        \n        # Save complete results\n        self.save_complete_results(study, optimize_param)\n        \n        return study\n    \n    # Modified save_complete_results method to show both F1 and accuracy in summary\n    def save_complete_results(self, study, optimize_param):\n        \"\"\"Save complete optimization results with memory optimization\"\"\"\n        \n        # Create sampler-specific directory\n        sampler_dir = os.path.join(self.current_results_dir, self.sampler_type)\n        os.makedirs(sampler_dir, exist_ok=True)\n        \n        # Save study object\n        study_filename = f\"optuna_study_{optimize_param}.pkl\"\n        study_filepath = os.path.join(sampler_dir, study_filename)\n        \n        with open(study_filepath, 'wb') as f:\n            pickle.dump(study, f)\n        \n        # Create summary with only essential data\n        all_trials_data = []\n        for trial in study.trials:\n            if trial.value is not None:\n                trial_data = {\n                    'trial_number': trial.number,\n                    'value': trial.value,\n                    'params': trial.params,\n                    'state': trial.state.name\n                }\n                all_trials_data.append(trial_data)\n        \n        # Save summary results\n        summary_results = {\n            'exploration_info': {\n                'n_trials': len(study.trials),\n                'n_complete_trials': len([t for t in study.trials if t.state.name == 'COMPLETE']),\n                'n_pruned_trials': len([t for t in study.trials if t.state.name == 'PRUNED']),\n                'optimization_direction': study.direction.name,\n                'sampler': study.sampler.__class__.__name__,\n                'target_metric': 'validation_f1_score',\n                'optimized_parameter': optimize_param,\n                'architecture': self.architecture,\n                'experiment_count': self.experiment_count\n            },\n            'best_trial': {\n                'number': study.best_trial.number,\n                'value': study.best_trial.value,\n                'params': study.best_trial.params\n            } if study.best_trial else None,\n            'all_trials': all_trials_data\n        }\n        \n        summary_filename = f\"hyperparameter_exploration_summary_{self.architecture}_{optimize_param}_{self.sampler_type}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json\"\n        summary_filepath = os.path.join(sampler_dir, summary_filename)\n        \n        with open(summary_filepath, 'w') as f:\n            json.dump(summary_results, f, indent=2)\n        \n        print(\"\\n\" + \"=\" * 80)\n        print(\"HYPERPARAMETER EXPLORATION COMPLETED!\")\n        print(\"=\" * 80)\n        print(f\"Total trials: {len(study.trials)}\")\n        print(f\"Completed trials: {len([t for t in study.trials if t.state.name == 'COMPLETE'])}\")\n        if study.best_trial:\n            print(f\"Best trial: {study.best_trial.number}\")\n            print(f\"Best validation F1 score: {study.best_trial.value:.4f}\")\n            print(\"Best hyperparameters:\")\n            for key, value in study.best_trial.params.items():\n                print(f\"  {key}: {value}\")\n        print(f\"\\nResults saved to: {sampler_dir}\")\n        print(f\"Total experiments processed: {self.experiment_count}\")\n        \n        return summary_results","metadata":{"_uuid":"1d9e61e4-595b-4675-8640-0a317e6d48d3","_cell_guid":"cfe76b47-be8f-4989-ab91-9cb8e1052361","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:28.542229Z","iopub.execute_input":"2025-07-24T17:25:28.542528Z","iopub.status.idle":"2025-07-24T17:25:28.585224Z","shell.execute_reply.started":"2025-07-24T17:25:28.542478Z","shell.execute_reply":"2025-07-24T17:25:28.584382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class MultiFactorOptimizer(CNNHyperparameterOptimizer):\n    \"\"\"Extended optimizer for multi-factor hyperparameter analysis\"\"\"\n    \n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        \n    def create_cnn_model_multifactor(self, trial, optimize_factors: list):\n        \"\"\"Create CNN model with hyperparameters from trial for multiple factors\"\"\"\n        \n        # Start with default values\n        hyperparams = {\n            'learning_rate': self.default_hyperparameters.learning_rate,\n            'batch_size': self.default_hyperparameters.batch_size,\n            'dropout_rate': self.default_hyperparameters.dropout_rate,\n            'optimizer': self.default_hyperparameters.optimizer,\n            'epochs': self.default_hyperparameters.epochs\n        }\n        \n        # Map factor names to hyperparameter space attributes\n        factor_mapping = {\n            'LEARNING_RATE': ('learning_rate', self.hyperparameter_space.learning_rate),\n            'BATCH_SIZE': ('batch_size', self.hyperparameter_space.batch_size),\n            'DROPOUT_RATE': ('dropout_rate', self.hyperparameter_space.dropout_rate),\n            'OPTIMIZER': ('optimizer', self.hyperparameter_space.optimizers),\n            'EPOCHS': ('epochs', self.hyperparameter_space.epochs)\n        }\n        \n        # Override with trial suggestions for specified factors\n        for factor in optimize_factors:\n            if factor in factor_mapping:\n                param_name, param_space = factor_mapping[factor]\n                hyperparams[param_name] = trial.suggest_categorical(param_name, param_space)\n        \n        # Create model parameters\n        model_params = {'dropout_rate': hyperparams['dropout_rate']}\n        \n        # Create model using model_builder\n        model = model_builder(self.input_shape, model_params, self.architecture)\n        \n        # Configure optimizer\n        if hyperparams['optimizer'] == OptimizerType.ADAM:\n            optimizer = keras.optimizers.Adam(learning_rate=hyperparams['learning_rate'])\n        elif hyperparams['optimizer'] == OptimizerType.SGD:\n            optimizer = keras.optimizers.SGD(learning_rate=hyperparams['learning_rate'], momentum=0.9)\n        \n        # Compile model\n        model.compile(\n            optimizer=optimizer,\n            loss='binary_crossentropy',\n            metrics=[f1_score_metric, 'accuracy']\n        )\n        \n        return model, hyperparams\n    \n    def objective_multifactor(self, trial, optimize_factors: list):\n        \"\"\"Objective function for multi-factor optimization\"\"\"\n        \n        start_time = datetime.now()\n        try:\n            # Create model and get hyperparameters\n            model, hyperparams = self.create_cnn_model_multifactor(trial, optimize_factors)\n            \n            # Create batched datasets\n            train_batched = create_batched_dataset(self.train_dataset, hyperparams['batch_size'])\n            val_batched = create_batched_dataset(self.val_dataset, hyperparams['batch_size'])\n            test_batched = create_batched_dataset(self.test_dataset, hyperparams['batch_size'])\n            \n            # Train model\n            history = model.fit(\n                train_batched,\n                validation_data=val_batched,\n                epochs=hyperparams['epochs'],\n                verbose=0\n            )\n            \n            # Calculate metrics\n            training_time = (datetime.now() - start_time).total_seconds()\n            train_y_true, train_y_pred = self.evaluate_model_on_dataset(model, train_batched)\n            val_y_true, val_y_pred = self.evaluate_model_on_dataset(model, val_batched)\n            test_y_true, test_y_pred = self.evaluate_model_on_dataset(model, test_batched)\n            \n            train_metrics = self.calculate_metrics(train_y_true, train_y_pred)\n            val_metrics = self.calculate_metrics(val_y_true, val_y_pred)\n            test_metrics = self.calculate_metrics(test_y_true, test_y_pred)\n            \n            # Extract history statistics\n            min_train_loss = float(min(history.history['loss']))\n            min_val_loss = float(min(history.history['val_loss']))\n            max_train_f1 = float(max(history.history['f1_score_metric']))\n            max_val_f1 = float(max(history.history['val_f1_score_metric']))\n            max_train_accuracy = float(max(history.history['accuracy']))\n            max_val_accuracy = float(max(history.history['val_accuracy']))\n            final_epoch = len(history.history['loss'])\n            \n            # Convert history to JSON serializable format\n            history_dict = {\n                'loss': [float(v) for v in history.history['loss']],\n                'val_loss': [float(v) for v in history.history['val_loss']],\n                'f1_score_metric': [float(v) for v in history.history['f1_score_metric']],\n                'val_f1_score_metric': [float(v) for v in history.history['val_f1_score_metric']],\n                'accuracy': [float(v) for v in history.history['accuracy']],\n                'val_accuracy': [float(v) for v in history.history['val_accuracy']]\n            }\n            \n            # Store experiment result\n            hyperparameters_with_config = hyperparams.copy()\n            hyperparameters_with_config['architecture'] = self.architecture\n            hyperparameters_with_config['freeze_base'] = True\n            hyperparameters_with_config['optimized_factors'] = optimize_factors\n            \n            experiment_result = ExperimentResult(\n                trial_number=trial.number,\n                hyperparameters=hyperparameters_with_config,\n                train_metrics=train_metrics,\n                val_metrics=val_metrics,\n                test_metrics=test_metrics,\n                history=history_dict,\n                min_train_loss=min_train_loss,\n                min_val_loss=min_val_loss,\n                max_train_f1=max_train_f1,\n                max_val_f1=max_val_f1,\n                max_train_accuracy=max_train_accuracy,\n                max_val_accuracy=max_val_accuracy,\n                training_time=training_time,\n                final_epoch=final_epoch,\n                test_confusion_matrix=test_metrics['confusion_matrix']\n            )\n            \n            # Save results\n            self.save_confusion_matrix_plot(experiment_result)\n            self.save_experiment_result(experiment_result)\n            self.experiment_count += 1\n            \n            # Cleanup\n            del (model, train_batched, val_batched, test_batched, history,\n                 train_y_true, train_y_pred, val_y_true, val_y_pred, \n                 test_y_true, test_y_pred, experiment_result, history_dict)\n            clear_memory()\n            \n            return val_metrics['f1_score']\n            \n        except Exception as e:\n            print(f\"Trial {trial.number} failed: {str(e)}\")\n            clear_memory()\n            return 0.0\n    \n    def optimize_multifactor(self, optimize_factors: list, n_trials: int = 50, \n                           sampler_type: str = SamplerType.BAYESIAN):\n        \"\"\"Run multi-factor hyperparameter optimization\"\"\"\n        \n        self.sampler_type = sampler_type\n        self.optimize_param = \"_\".join(optimize_factors)\n        \n        # Validate factors\n        valid_factors = {'LEARNING_RATE', 'BATCH_SIZE', 'DROPOUT_RATE', 'OPTIMIZER', 'EPOCHS'}\n        if not all(factor in valid_factors for factor in optimize_factors):\n            raise ValueError(f\"Invalid factors. Valid options: {valid_factors}\")\n        \n        if len(optimize_factors) < 2:\n            raise ValueError(\"Multi-factor analysis requires at least 2 factors\")\n        \n        # Create results directory\n        factor_suffix = \"_\".join(optimize_factors)\n        self.current_results_dir = f\"{self.results_dir}/multifactor_{factor_suffix}_{sampler_type.lower()}\"\n        os.makedirs(self.current_results_dir, exist_ok=True)\n        \n        # Choose sampler\n        if sampler_type == SamplerType.RANDOM:\n            sampler = optuna.samplers.RandomSampler(seed=SEED)\n        elif sampler_type == SamplerType.GRID:\n            # Build search space for GridSampler\n            factor_mapping = {\n                'LEARNING_RATE': ('learning_rate', self.hyperparameter_space.learning_rate),\n                'BATCH_SIZE': ('batch_size', self.hyperparameter_space.batch_size),\n                'DROPOUT_RATE': ('dropout_rate', self.hyperparameter_space.dropout_rate),\n                'OPTIMIZER': ('optimizer', self.hyperparameter_space.optimizers),\n                'EPOCHS': ('epochs', self.hyperparameter_space.epochs)\n            }\n            \n            search_space = {}\n            for factor in optimize_factors:\n                param_name, param_space = factor_mapping[factor]\n                search_space[param_name] = param_space\n            \n            sampler = optuna.samplers.GridSampler(search_space)\n        else:\n            sampler = optuna.samplers.TPESampler(seed=SEED)\n        \n        # Create pruner for Bayesian optimization\n        pruner = None\n        if sampler_type == SamplerType.BAYESIAN:\n            pruner = optuna.pruners.MedianPruner(\n                n_startup_trials=3,\n                n_warmup_steps=5,\n                interval_steps=2\n            )\n        \n        # Create study\n        study = optuna.create_study(\n            direction='maximize',\n            sampler=sampler,\n            pruner=pruner,\n            study_name=f\"CNN_MultiFactor_{factor_suffix}_{datetime.now().strftime('%Y%m%d_%H%M%S')}\"\n        )\n        \n        # Print configuration\n        print(f\"Starting {len(optimize_factors)}-factor analysis: {' × '.join(optimize_factors)}\")\n        print(f\"Architecture: {self.architecture}\")\n        print(f\"Sampler: {sampler_type}\")\n        print(f\"Trials: {n_trials}\")\n        print(f\"Results: {self.current_results_dir}\")\n        print(\"-\" * 100)\n        \n        # Run optimization\n        def objective_with_factors(trial):\n            return self.objective_multifactor(trial, optimize_factors)\n        \n        study.optimize(objective_with_factors, n_trials=n_trials)\n        \n        # Save results\n        self.save_complete_results(study, factor_suffix)\n        \n        return study","metadata":{"_uuid":"597831e6-5caa-4222-9d40-1f19dd04a8d0","_cell_guid":"d802cdcf-0474-4944-ba47-8f72d6e5351a","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-07-24T17:31:01.977295Z","iopub.execute_input":"2025-07-24T17:31:01.977626Z","iopub.status.idle":"2025-07-24T17:31:01.999182Z","shell.execute_reply.started":"2025-07-24T17:31:01.977603Z","shell.execute_reply":"2025-07-24T17:31:01.998583Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore')","metadata":{"_uuid":"98a61b95-da30-43e3-8979-0a573039e8b8","_cell_guid":"10cfbf5d-5595-4fd6-83c6-51b233499f8a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:31:04.335008Z","iopub.execute_input":"2025-07-24T17:31:04.335273Z","iopub.status.idle":"2025-07-24T17:31:04.339225Z","shell.execute_reply.started":"2025-07-24T17:31:04.335253Z","shell.execute_reply":"2025-07-24T17:31:04.338427Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Usage of the CNN Hyperparameter Optimizer","metadata":{"_uuid":"f0179d74-5ca0-4b63-9879-e16ab7a23513","_cell_guid":"135e767f-92c4-4ab8-8a61-671afa2160ae","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Define hyperparameter space for exploration\nhyperparameter_space = HyperparameterSpace(\n    learning_rate=[1e-5, 1e-4, 1e-3, 1e-2, 1e-1],\n    batch_size=[16, 32, 64, 128, 256],\n    dropout_rate=[0.1, 0.2, 0.3, 0.4, 0.5],\n    epochs=[10, 20, 30, 40, 50],\n    optimizers=[OptimizerType.ADAM, OptimizerType.SGD]\n)\n\n# Define custom default hyperparameters \ndefault_hyperparams = DefaultHyperparameters(\n    learning_rate=0.001,\n    batch_size=64,\n    optimizer=OptimizerType.ADAM,\n    epochs=20,\n    dropout_rate=0.2\n)","metadata":{"_uuid":"dc941744-ef2e-46e5-97dd-af2d4980914e","_cell_guid":"2c9138d3-3159-4ff1-9382-236fc5f98250","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:25:28.634437Z","iopub.execute_input":"2025-07-24T17:25:28.634664Z","iopub.status.idle":"2025-07-24T17:25:28.649729Z","shell.execute_reply.started":"2025-07-24T17:25:28.634648Z","shell.execute_reply":"2025-07-24T17:25:28.648999Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Two-factor interactions\nTWO_FACTOR_CONFIGS = {\n    \"LR_BS\": [\"LEARNING_RATE\", \"BATCH_SIZE\"],\n    \"BS_OPT\": [\"BATCH_SIZE\", \"OPTIMIZER\"], \n    \"LR_DR\": [\"LEARNING_RATE\", \"DROPOUT_RATE\"],\n    \"LR_OPT\": [\"LEARNING_RATE\", \"OPTIMIZER\"],\n    \"EP_DR\": [\"EPOCHS\", \"DROPOUT_RATE\"],\n    \"BS_EP\": [\"BATCH_SIZE\", \"EPOCHS\"]\n}\n\n# Three-factor interactions\nTHREE_FACTOR_CONFIGS = {\n    \"LR_BS_DR\": [\"LEARNING_RATE\", \"BATCH_SIZE\", \"DROPOUT_RATE\"],\n    \"LR_EP_OPT\": [\"LEARNING_RATE\", \"EPOCHS\", \"OPTIMIZER\"],\n    \"DR_EP_OPT\": [\"DROPOUT_RATE\", \"EPOCHS\", \"OPTIMIZER\"]\n}\n\n# Four-factor interactions\nFOUR_FACTOR_CONFIGS = {\n    \"LR_BS_DR_EP\": [\"LEARNING_RATE\", \"BATCH_SIZE\", \"DROPOUT_RATE\", \"EPOCHS\"],\n    \"LR_BS_EP_OPT\": [\"LEARNING_RATE\", \"BATCH_SIZE\", \"EPOCHS\", \"OPTIMIZER\"]\n}\n\n# Five-factor interaction\nFIVE_FACTOR_CONFIGS = {\n    \"ALL_FACTORS\": [\"LEARNING_RATE\", \"BATCH_SIZE\", \"DROPOUT_RATE\", \"EPOCHS\", \"OPTIMIZER\"]\n}","metadata":{"_uuid":"f1c467da-f0e0-4be1-9836-1369d04bd15b","_cell_guid":"44bea417-050f-45d5-8646-ec8f16660cbc","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:26:28.465218Z","iopub.execute_input":"2025-07-24T17:26:28.465541Z","iopub.status.idle":"2025-07-24T17:26:28.471089Z","shell.execute_reply.started":"2025-07-24T17:26:28.465516Z","shell.execute_reply":"2025-07-24T17:26:28.470322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MULTIFACTOR_SETTINGS = {\n 'analysis_configs': {'five_factor': {'ALL_FACTORS': 30},\n                      'four_factor': {'LR_BS_DR_EP': 30,\n                                      'LR_BS_EP_OPT': 30},\n                      'three_factor': {'DR_EP_OPT': 30,\n                                       'LR_BS_DR': 30,\n                                       'LR_EP_OPT': 30},\n                      'two_factor': {'BS_EP': 5*5,\n                                     'BS_OPT': 5*2,\n                                     'EP_DR': 5*5,\n                                     'LR_BS': 5*5,\n                                     'LR_DR': 5*5,\n                                     'LR_OPT': 5*2}\n                     },\n 'input_shape': (224, 224, 3),\n 'num_classes': 2,\n 'sampler_types': ['GRID', 'BAYESIAN', 'RANDOM']}","metadata":{"_uuid":"5dbfff0d-6387-4b68-bd9c-3265a37658cc","_cell_guid":"766eea06-9e8b-4547-ace9-4fe6cb852e10","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-07-24T17:26:31.142433Z","iopub.execute_input":"2025-07-24T17:26:31.14276Z","iopub.status.idle":"2025-07-24T17:26:31.148047Z","shell.execute_reply.started":"2025-07-24T17:26:31.142741Z","shell.execute_reply":"2025-07-24T17:26:31.147242Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def run_specific_analysis(factor_config_name, sampler_type=\"GRID\", n_trials=None):\n    \"\"\"Run specific multi-factor analysis with automatic trial count from settings\"\"\"\n    \n    # Get factors and trials from config\n    optimize_factors = None\n    config_trials = None\n    \n    # Search through all analysis types in MULTIFACTOR_SETTINGS\n    for analysis_type, configs in MULTIFACTOR_SETTINGS[\"analysis_configs\"].items():\n        if factor_config_name in configs:\n            config_trials = configs[factor_config_name]\n            \n            # Get factor mapping based on analysis type\n            if analysis_type == \"two_factor\":\n                optimize_factors = TWO_FACTOR_CONFIGS[factor_config_name]\n            elif analysis_type == \"three_factor\":  \n                optimize_factors = THREE_FACTOR_CONFIGS[factor_config_name]\n            elif analysis_type == \"four_factor\":\n                optimize_factors = FOUR_FACTOR_CONFIGS[factor_config_name]\n            elif analysis_type == \"five_factor\":\n                optimize_factors = FIVE_FACTOR_CONFIGS[factor_config_name]\n            break\n    \n    # Fallback to manual lookup if not in MULTIFACTOR_SETTINGS\n    if optimize_factors is None:\n        if factor_config_name in TWO_FACTOR_CONFIGS:\n            optimize_factors = TWO_FACTOR_CONFIGS[factor_config_name]\n        elif factor_config_name in THREE_FACTOR_CONFIGS:\n            optimize_factors = THREE_FACTOR_CONFIGS[factor_config_name]\n        elif factor_config_name in FOUR_FACTOR_CONFIGS:\n            optimize_factors = FOUR_FACTOR_CONFIGS[factor_config_name]\n        elif factor_config_name in FIVE_FACTOR_CONFIGS:\n            optimize_factors = FIVE_FACTOR_CONFIGS[factor_config_name]\n        else:\n            raise ValueError(f\"Unknown config: {factor_config_name}\")\n    \n    # Use trials from settings, fallback to parameter\n    final_trials = config_trials if config_trials is not None else n_trials\n    if final_trials is None:\n        raise ValueError(f\"No trial count found for {factor_config_name} in MULTIFACTOR_SETTINGS and no n_trials provided\")\n    \n    # Initialize optimizer\n    optimizer = MultiFactorOptimizer(\n        train_dataset=train_dataset,\n        val_dataset=val_dataset,\n        test_dataset=test_dataset, \n        hyperparameter_space=hyperparameter_space,\n        architecture=getattr(ArchitectureType, MULTIFACTOR_SETTINGS[\"architecture\"]),\n        input_shape=MULTIFACTOR_SETTINGS[\"input_shape\"],\n        num_classes=MULTIFACTOR_SETTINGS[\"num_classes\"],\n        results_dir=f\"multifactor_analysis_{factor_config_name}\",\n        default_hyperparameters=default_hyperparams\n    )\n    \n    print(f\"Running {factor_config_name} with {final_trials} trials (from {'settings' if config_trials else 'parameter'})\")\n    \n    # Run analysis\n    return optimizer.optimize_multifactor(\n        optimize_factors=optimize_factors,\n        n_trials=final_trials,\n        sampler_type=getattr(SamplerType, sampler_type)\n    )","metadata":{"_uuid":"c69888a5-e731-416c-a919-6925a9dd4646","_cell_guid":"c75c126c-80fb-4089-9af7-86a562eb26e8","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-07-24T17:31:09.890432Z","iopub.execute_input":"2025-07-24T17:31:09.891189Z","iopub.status.idle":"2025-07-24T17:31:09.898641Z","shell.execute_reply.started":"2025-07-24T17:31:09.891163Z","shell.execute_reply":"2025-07-24T17:31:09.89791Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"MULTIFACTOR_SETTINGS[\"architecture\"] = \"VGG16\"\n[\n    # \"BS_EP\",\n    # \"BS_OPT\",\n    # \"EP_DR\",\n    # \"LR_BS\",\n    # \"LR_DR\",\n    # \"LR_OPT\",\n     # \"LR_BS_DR_EP\",\n    # \"LR_BS_EP_OPT\",\n    \"DR_EP_OPT\",\n    # \"LR_BS_DR\",\n    # \"LR_EP_OPT\",\n    # \"ALL_FACTORS\"\n]","metadata":{"_uuid":"6a2308e3-289b-4021-b7b5-1ae6d6621425","_cell_guid":"5e511981-b57c-4d78-b61e-1a28e29cfb9b","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-07-24T17:31:10.332986Z","iopub.execute_input":"2025-07-24T17:31:10.333256Z","iopub.status.idle":"2025-07-24T17:31:10.338444Z","shell.execute_reply.started":"2025-07-24T17:31:10.333236Z","shell.execute_reply":"2025-07-24T17:31:10.337904Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Run specific 3-factor analysis:\nstudy = run_specific_analysis(\"DR_EP_OPT\", \"RANDOM\")","metadata":{"_uuid":"959b2284-b786-4603-a710-be8594af4ff2","_cell_guid":"6cc18ef5-3336-4987-b191-e17021c2f294","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-07-24T17:31:12.909326Z","iopub.execute_input":"2025-07-24T17:31:12.909638Z","iopub.status.idle":"2025-07-24T17:49:20.041667Z","shell.execute_reply.started":"2025-07-24T17:31:12.909616Z","shell.execute_reply":"2025-07-24T17:49:20.040902Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"_uuid":"4eccbfb0-365c-43fd-b3a3-65fd5bd75b4a","_cell_guid":"36457f38-4fc0-4fbd-89f7-d52a5b533d8b","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"_uuid":"d689d488-5a12-490c-841b-68e0d55ae1e3","_cell_guid":"e4b054c1-06e9-426a-b337-1b7046444c10","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null}]}