{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":29653,"databundleVersionId":2420395,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport torch\nimport torch.nn as nn\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib\nimport pydicom as dicom\nimport cv2\nimport ast\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:11.847048Z","iopub.execute_input":"2026-07-11T11:58:11.847991Z","iopub.status.idle":"2026-07-11T11:58:11.852275Z","shell.execute_reply.started":"2026-07-11T11:58:11.847961Z","shell.execute_reply":"2026-07-11T11:58:11.851358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"path = '/kaggle/input/rsna-miccai-brain-tumor-radiogenomic-classification/'\nos.listdir(path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:11.853841Z","iopub.execute_input":"2026-07-11T11:58:11.854146Z","iopub.status.idle":"2026-07-11T11:58:11.869134Z","shell.execute_reply.started":"2026-07-11T11:58:11.854128Z","shell.execute_reply":"2026-07-11T11:58:11.868398Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"path = '/kaggle/input/rsna-miccai-brain-tumor-radiogenomic-classification/'\nos.listdir(path)\ntrain_data = pd.read_csv(path+'train_labels.csv')\nsamp_subm = pd.read_csv(path+'sample_submission.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:11.870720Z","iopub.execute_input":"2026-07-11T11:58:11.871438Z","iopub.status.idle":"2026-07-11T11:58:11.881047Z","shell.execute_reply.started":"2026-07-11T11:58:11.871412Z","shell.execute_reply":"2026-07-11T11:58:11.880117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print('Samples train:', len(train_data))\nprint('Samples test:', len(samp_subm))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:11.881952Z","iopub.execute_input":"2026-07-11T11:58:11.882287Z","iopub.status.idle":"2026-07-11T11:58:11.886548Z","shell.execute_reply.started":"2026-07-11T11:58:11.882270Z","shell.execute_reply":"2026-07-11T11:58:11.885677Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:11.888089Z","iopub.execute_input":"2026-07-11T11:58:11.888427Z","iopub.status.idle":"2026-07-11T11:58:11.900929Z","shell.execute_reply.started":"2026-07-11T11:58:11.888384Z","shell.execute_reply":"2026-07-11T11:58:11.899928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data[\"MGMT_value\"].value_counts().head(2).plot(kind = 'pie', autopct='%1.1f%%', figsize=(8, 8)).legend()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:11.901840Z","iopub.execute_input":"2026-07-11T11:58:11.902388Z","iopub.status.idle":"2026-07-11T11:58:12.027469Z","shell.execute_reply.started":"2026-07-11T11:58:11.902362Z","shell.execute_reply":"2026-07-11T11:58:12.026663Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_data[\"MGMT_value\"].value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:12.028355Z","iopub.execute_input":"2026-07-11T11:58:12.028636Z","iopub.status.idle":"2026-07-11T11:58:12.036144Z","shell.execute_reply.started":"2026-07-11T11:58:12.028614Z","shell.execute_reply":"2026-07-11T11:58:12.035387Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"samp_subm.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:12.037905Z","iopub.execute_input":"2026-07-11T11:58:12.038491Z","iopub.status.idle":"2026-07-11T11:58:12.053846Z","shell.execute_reply.started":"2026-07-11T11:58:12.038466Z","shell.execute_reply":"2026-07-11T11:58:12.053107Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"folder = str(train_data.loc[0, 'BraTS21ID']).zfill(5)\nfolder","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:12.054602Z","iopub.execute_input":"2026-07-11T11:58:12.054893Z","iopub.status.idle":"2026-07-11T11:58:12.065607Z","shell.execute_reply.started":"2026-07-11T11:58:12.054871Z","shell.execute_reply":"2026-07-11T11:58:12.064690Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"os.listdir(path+'train/'+folder)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:12.066643Z","iopub.execute_input":"2026-07-11T11:58:12.067676Z","iopub.status.idle":"2026-07-11T11:58:12.079094Z","shell.execute_reply.started":"2026-07-11T11:58:12.067651Z","shell.execute_reply":"2026-07-11T11:58:12.078276Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print('Number of FLAIR images:', len(os.listdir(path+'train/'+folder+'/'+'FLAIR')))\nprint('Number of T1w images:', len(os.listdir(path+'train/'+folder+'/'+'T1w')))\nprint('Number of T1wCE images:', len(os.listdir(path+'train/'+folder+'/'+'T1wCE')))\nprint('Number of T2w images:', len(os.listdir(path+'train/'+folder+'/'+'T2w')))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:12.080015Z","iopub.execute_input":"2026-07-11T11:58:12.080187Z","iopub.status.idle":"2026-07-11T11:58:12.091893Z","shell.execute_reply.started":"2026-07-11T11:58:12.080174Z","shell.execute_reply":"2026-07-11T11:58:12.091157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"path_file = ''.join([path, 'train/', folder, '/', 'FLAIR/'])\nimage = os.listdir(path_file)[0]\ndata_file = dicom.dcmread(path_file+image)\nimg = data_file.pixel_array\nprint('Image shape:', img.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:12.093805Z","iopub.execute_input":"2026-07-11T11:58:12.094241Z","iopub.status.idle":"2026-07-11T11:58:12.106390Z","shell.execute_reply.started":"2026-07-11T11:58:12.094210Z","shell.execute_reply":"2026-07-11T11:58:12.105625Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Flair Image\ndef plot_examples(row = 0, cat = 'FLAIR'): \n    folder = str(train_data.loc[row, 'BraTS21ID']).zfill(5)\n    path_file = ''.join([path, 'train/', folder, '/', cat, '/'])\n    images = os.listdir(path_file)\n    \n    fig, axs = plt.subplots(1, 5, figsize=(30, 30))\n    fig.subplots_adjust(hspace = .2, wspace=.2)\n    axs = axs.ravel()\n    \n    for num in range(5):\n        data_file = dicom.dcmread(path_file+images[num])\n        img = data_file.pixel_array\n        axs[num].imshow(img, cmap='gray')\n        axs[num].set_title(cat+' '+images[num])\n        axs[num].set_xticklabels([])\n        axs[num].set_yticklabels([])\n        \nrow = 0\nplot_examples(row = row, cat = 'FLAIR')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:12.107184Z","iopub.execute_input":"2026-07-11T11:58:12.107476Z","iopub.status.idle":"2026-07-11T11:58:12.757138Z","shell.execute_reply.started":"2026-07-11T11:58:12.107452Z","shell.execute_reply":"2026-07-11T11:58:12.756368Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#T1w Images\nplot_examples(row = row, cat = 'T1w')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:12.758009Z","iopub.execute_input":"2026-07-11T11:58:12.758387Z","iopub.status.idle":"2026-07-11T11:58:13.395441Z","shell.execute_reply.started":"2026-07-11T11:58:12.758327Z","shell.execute_reply":"2026-07-11T11:58:13.394461Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#T1wCE Images\nplot_examples(row = row, cat = 'T1wCE')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:13.396434Z","iopub.execute_input":"2026-07-11T11:58:13.397457Z","iopub.status.idle":"2026-07-11T11:58:14.072315Z","shell.execute_reply.started":"2026-07-11T11:58:13.397426Z","shell.execute_reply":"2026-07-11T11:58:14.071408Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#T2w Images\nplot_examples(row = row, cat = 'T2w')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:14.073247Z","iopub.execute_input":"2026-07-11T11:58:14.073559Z","iopub.status.idle":"2026-07-11T11:58:14.730423Z","shell.execute_reply.started":"2026-07-11T11:58:14.073540Z","shell.execute_reply":"2026-07-11T11:58:14.729219Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport pydicom\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models\nfrom sklearn.model_selection import train_test_split\nimport cv2\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import classification_report, confusion_matrix, roc_curve, auc\n\n# Path dataset\npath = '/kaggle/input/rsna-miccai-brain-tumor-radiogenomic-classification/'\ntrain_labels = pd.read_csv(path + 'train_labels.csv')\n\n# Konfigurasi\nIMG_SIZE = 128\nBATCH_SIZE = 16      # réduit car 4 modalités = 4x plus de mémoire par volume\nEPOCHS = 50\nNUM_SLICES = 16\nMODALITIES = ['FLAIR', 'T1w', 'T1wCE', 'T2w']  # on utilise les 4 modalités disponibles\n\ndef load_dicom_image(filepath, img_size=IMG_SIZE):\n    \"\"\"Charge une image DICOM, la normalise en float32 dans [0,1] et la redimensionne.\"\"\"\n    dicom = pydicom.dcmread(filepath)\n    img = dicom.pixel_array.astype(np.float32)\n\n    # Normalisation min-max robuste (evite division par 0 si image constante)\n    img_min, img_max = img.min(), img.max()\n    if img_max > img_min:\n        img = (img - img_min) / (img_max - img_min)\n    else:\n        img = np.zeros_like(img)\n\n    img = cv2.resize(img, (img_size, img_size), interpolation=cv2.INTER_AREA)\n    return img.astype(np.float32)  # shape (H, W), valeurs dans [0,1]\n\n\ndef get_selected_slices(patient_id, modality, num_slices=NUM_SLICES):\n    \"\"\"Retourne la liste des fichiers DICOM sélectionnés pour une modalité donnée.\"\"\"\n    patient_path = os.path.join(path, 'train', str(patient_id).zfill(5), modality)\n    if not os.path.exists(patient_path):\n        return None\n    dicom_files = sorted([f for f in os.listdir(patient_path) if f.endswith('.dcm')])\n    if not dicom_files:\n        return None\n    step = max(1, len(dicom_files) // num_slices)\n    selected_files = dicom_files[::step][:num_slices]\n    return patient_path, selected_files\n\ndef load_patient_data(patient_id, num_slices=NUM_SLICES, modalities=MODALITIES):\n    \"\"\"\n    Charge les 4 modalités IRM d'un patient et les empile comme des canaux.\n    Retourne un volume de forme (num_slices, IMG_SIZE, IMG_SIZE, len(modalities))\n    ou None si une modalité essentielle est manquante.\n    \"\"\"\n    # On se base sur la modalité FLAIR pour choisir les indices de slices\n    # (les autres modalités doivent avoir un nombre de slices comparable)\n    ref = get_selected_slices(patient_id, modalities[0], num_slices)\n    if ref is None:\n        print(f\"[Patient {patient_id}] Modalité de référence '{modalities[0]}' introuvable.\")\n        return None\n    ref_path, ref_files = ref\n    n_ref = len(ref_files)\n\n    all_channels = []  # liste de volumes (num_slices, H, W), un par modalité\n\n    for mod in modalities:\n        result = get_selected_slices(patient_id, mod, num_slices)\n        if result is None:\n            print(f\"[Patient {patient_id}] Modalité manquante : {mod}. Patient ignoré.\")\n            return None\n\n        mod_path, mod_files = result\n\n        # Sécurité : on garde le même nombre de slices que la référence\n        n = min(len(mod_files), n_ref)\n        mod_files = mod_files[:n]\n\n        slices = [load_dicom_image(os.path.join(mod_path, f)) for f in mod_files]\n\n        # Complétion si pas assez de slices (répétition de la dernière)\n        while len(slices) < num_slices:\n            slices.append(slices[-1].copy() if slices else np.zeros((IMG_SIZE, IMG_SIZE), dtype=np.float32))\n\n        vol = np.stack(slices[:num_slices], axis=0)  # (num_slices, H, W)\n        all_channels.append(vol)\n\n    # Empilement des modalités comme canaux : (num_slices, H, W, n_modalities)\n    volume = np.stack(all_channels, axis=-1).astype(np.float32)\n\n    # Vérifications de sécurité (shape / dtype)\n    assert volume.shape == (num_slices, IMG_SIZE, IMG_SIZE, len(modalities)), \\\n        f\"Shape inattendue pour patient {patient_id}: {volume.shape}\"\n    assert volume.dtype == np.float32\n    assert 0.0 <= volume.min() and volume.max() <= 1.0 + 1e-6\n\n    return volume","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:14.731398Z","iopub.execute_input":"2026-07-11T11:58:14.731920Z","iopub.status.idle":"2026-07-11T11:58:14.745604Z","shell.execute_reply.started":"2026-07-11T11:58:14.731900Z","shell.execute_reply":"2026-07-11T11:58:14.744901Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"X = []\ny = []\n\nprint(\"Chargement des 4 modalités IRM pour chaque patient...\")\nfor idx, row in train_labels.iterrows():\n    patient_id = row['BraTS21ID']\n    label = row['MGMT_value']\n\n    patient_data = load_patient_data(patient_id)  # (16,128,128,4)\n    if patient_data is not None:\n        X.append(patient_data)\n        y.append(label)\n\nX = np.array(X, dtype=np.float32)\ny = np.array(y, dtype=np.float32)\n\n# Vérifications finales avant split\nprint(f\"Shape finale de X : {X.shape}\")   # attendu : (N, 16, 128, 128, 4)\nprint(f\"Shape finale de y : {y.shape}\")\nassert X.ndim == 5 and X.shape[1:] == (16, 128, 128, len(MODALITIES)), \"Shape de X incorrecte !\"\nassert X.dtype == np.float32\n\nprint(f\"Total de patients chargés : {len(X)}\")\nprint(f\"Distribution des classes : {int(np.sum(y==1))} positifs, {int(np.sum(y==0))} négatifs\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:58:14.747309Z","iopub.execute_input":"2026-07-11T11:58:14.747662Z","iopub.status.idle":"2026-07-11T11:59:40.029680Z","shell.execute_reply.started":"2026-07-11T11:58:14.747645Z","shell.execute_reply":"2026-07-11T11:59:40.028538Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Split data: training (60%), validation (20%), test (20%)\nX_train, X_temp, y_train, y_temp = train_test_split(\n    X, y, test_size=0.4, random_state=42, stratify=y\n)\nX_val, X_test, y_val, y_test = train_test_split(\n    X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp\n)\n\nprint(\"\\nDistribusi dataset:\")\nprint(f\"Training:   {len(X_train)} sampel\")\nprint(f\"Validation: {len(X_val)} sampel\")\nprint(f\"Test:       {len(X_test)} sampel\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:59:40.030743Z","iopub.execute_input":"2026-07-11T11:59:40.030997Z","iopub.status.idle":"2026-07-11T11:59:40.825852Z","shell.execute_reply.started":"2026-07-11T11:59:40.030980Z","shell.execute_reply":"2026-07-11T11:59:40.824988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Arsitektur model CNN 3D\nfrom tensorflow.keras import regularizers\n\ndef build_3d_cnn(input_shape, num_classes):\n    \"\"\"\n    CNN 3D avec régularisation L2 légère en plus de BatchNorm et Dropout.\n    On garde la même architecture (3 blocs conv) pour rester simple et explicable.\n    \"\"\"\n    l2_reg = regularizers.l2(1e-4)  # régularisation légère, pas trop forte\n\n    model = models.Sequential([\n        layers.Conv3D(32, (3, 3, 3), activation='relu', padding='same',\n                      kernel_regularizer=l2_reg, input_shape=input_shape),\n        layers.BatchNormalization(),\n        layers.MaxPooling3D((2, 2, 2)),\n        layers.Dropout(0.2),\n\n        layers.Conv3D(64, (3, 3, 3), activation='relu', padding='same',\n                      kernel_regularizer=l2_reg),\n        layers.BatchNormalization(),\n        layers.MaxPooling3D((2, 2, 2)),\n        layers.Dropout(0.3),\n\n        layers.Conv3D(64, (3, 3, 3), activation='relu', padding='same',\n                      kernel_regularizer=l2_reg),\n        layers.BatchNormalization(),\n        layers.MaxPooling3D((2, 2, 2)),\n        layers.Dropout(0.4),\n\n        layers.Conv3D(128, (3, 3, 3), activation='relu', padding='same',\n                      kernel_regularizer=l2_reg),\n        layers.BatchNormalization(),\n        layers.MaxPooling3D((2, 2, 2)),\n        layers.Dropout(0.5),\n\n        layers.GlobalAveragePooling3D(),\n        layers.Dense(128, activation='relu', kernel_regularizer=l2_reg),\n        layers.Dropout(0.6),\n        layers.Dense(num_classes, activation='sigmoid')\n    ])\n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:59:40.826713Z","iopub.execute_input":"2026-07-11T11:59:40.827045Z","iopub.status.idle":"2026-07-11T11:59:40.834203Z","shell.execute_reply.started":"2026-07-11T11:59:40.827016Z","shell.execute_reply":"2026-07-11T11:59:40.833381Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"input_shape = (X_train.shape[1], X_train.shape[2], X_train.shape[3], X_train.shape[4])\nprint(f\"\\nInput shape: {input_shape}\")\nmodel = build_3d_cnn(input_shape, num_classes=1)\n\n# Ringkasan model\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:59:40.836949Z","iopub.execute_input":"2026-07-11T11:59:40.837432Z","iopub.status.idle":"2026-07-11T11:59:40.957138Z","shell.execute_reply.started":"2026-07-11T11:59:40.837408Z","shell.execute_reply":"2026-07-11T11:59:40.956533Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Kompilasi model\n# AdamW au lieu d'Adam : ajoute un weight decay découplé, utile avec peu de données\nmodel.compile(\n    optimizer=tf.keras.optimizers.AdamW(learning_rate=5e-4,weight_decay=1e-4),\n    loss='binary_crossentropy',\n    metrics=[\n        'accuracy',\n        tf.keras.metrics.AUC(name='auc'),\n        tf.keras.metrics.Precision(name='precision'),\n        tf.keras.metrics.Recall(name='recall'),\n    ]\n)\n\ncallbacks = [\n    tf.keras.callbacks.EarlyStopping(\n        patience=5, monitor='val_auc', mode='max',\n        restore_best_weights=True, verbose=1\n    ),\n    tf.keras.callbacks.ReduceLROnPlateau(\n        monitor='val_loss', factor=0.2, patience=3, min_lr=1e-6, verbose=1\n    ),\n    tf.keras.callbacks.ModelCheckpoint(\n        filepath='best_model.h5', save_best_only=True,\n        monitor='val_auc', mode='max', verbose=1\n    ),\n    tf.keras.callbacks.CSVLogger('training_log.csv', append=False)\n]\n\n# Calcul automatique des poids de classes (déséquilibre 278 vs 307)\nfrom sklearn.utils.class_weight import compute_class_weight\n\nclass_weights_array = compute_class_weight(\n    class_weight='balanced',\n    classes=np.unique(y_train),\n    y=y_train\n)\nclass_weight_dict = {int(c): w for c, w in zip(np.unique(y_train), class_weights_array)}\nprint(\"Poids de classes calculés :\", class_weight_dict)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:59:40.957971Z","iopub.execute_input":"2026-07-11T11:59:40.958276Z","iopub.status.idle":"2026-07-11T11:59:40.979809Z","shell.execute_reply.started":"2026-07-11T11:59:40.958259Z","shell.execute_reply":"2026-07-11T11:59:40.979135Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Training\nprint(\"\\nMemulai training...\")\nhistory = model.fit(\n    X_train, y_train,\n    validation_data=(X_val, y_val),\n    batch_size=BATCH_SIZE,\n    epochs=EPOCHS,\n    callbacks=callbacks,\n    class_weight=class_weight_dict   # prise en compte du déséquilibre de classes\n)\n\n# Plot history training\ndef plot_history(history):\n    \"\"\"Affiche Loss, Accuracy et AUC (train vs validation).\"\"\"\n    plt.figure(figsize=(18, 5))\n\n    plt.subplot(1, 3, 1)\n    plt.plot(history.history['loss'], label='Training Loss')\n    plt.plot(history.history['val_loss'], label='Validation Loss')\n    plt.title('Loss')\n    plt.xlabel('Epoch'); plt.legend()\n\n    plt.subplot(1, 3, 2)\n    plt.plot(history.history['accuracy'], label='Training Accuracy')\n    plt.plot(history.history['val_accuracy'], label='Validation Accuracy')\n    plt.title('Accuracy')\n    plt.xlabel('Epoch'); plt.legend()\n\n    plt.subplot(1, 3, 3)\n    plt.plot(history.history['auc'], label='Training AUC')\n    plt.plot(history.history['val_auc'], label='Validation AUC')\n    plt.title('AUC')\n    plt.xlabel('Epoch'); plt.legend()\n\n    plt.tight_layout()\n    plt.savefig('training_history.png')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T11:59:40.980644Z","iopub.execute_input":"2026-07-11T11:59:40.980924Z","iopub.status.idle":"2026-07-11T12:00:33.428858Z","shell.execute_reply.started":"2026-07-11T11:59:40.980906Z","shell.execute_reply":"2026-07-11T12:00:33.428112Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"results = model.evaluate(X_val, y_val)\n\nprint(results)\nprint(model.metrics_names)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:00:33.429590Z","iopub.execute_input":"2026-07-11T12:00:33.429803Z","iopub.status.idle":"2026-07-11T12:00:36.056839Z","shell.execute_reply.started":"2026-07-11T12:00:33.429789Z","shell.execute_reply":"2026-07-11T12:00:36.056200Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Prediksi pada test set\ny_pred_prob = model.predict(X_test).flatten()\ny_pred = (y_pred_prob > 0.5).astype(int)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:00:36.057637Z","iopub.execute_input":"2026-07-11T12:00:36.057914Z","iopub.status.idle":"2026-07-11T12:00:38.078577Z","shell.execute_reply.started":"2026-07-11T12:00:36.057891Z","shell.execute_reply":"2026-07-11T12:00:38.077553Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Classification report\nprint(\"\\nClassification Report:\")\nprint(classification_report(y_test, y_pred))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:00:38.079490Z","iopub.execute_input":"2026-07-11T12:00:38.079770Z","iopub.status.idle":"2026-07-11T12:00:38.093563Z","shell.execute_reply.started":"2026-07-11T12:00:38.079744Z","shell.execute_reply":"2026-07-11T12:00:38.092808Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Confusion matrix\nconf_matrix = confusion_matrix(y_test, y_pred)\nprint(\"\\nConfusion Matrix:\")\nprint(conf_matrix)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:00:38.094250Z","iopub.execute_input":"2026-07-11T12:00:38.094709Z","iopub.status.idle":"2026-07-11T12:00:38.101320Z","shell.execute_reply.started":"2026-07-11T12:00:38.094691Z","shell.execute_reply":"2026-07-11T12:00:38.100659Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Plot ROC curve\nfpr, tpr, thresholds = roc_curve(y_test, y_pred_prob)\nroc_auc = auc(fpr, tpr)\n\nplt.figure()\nplt.plot(fpr, tpr, color='darkorange', lw=2, label=f'ROC curve (area = {roc_auc:.2f})')\nplt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')\nplt.xlim([0.0, 1.0])\nplt.ylim([0.0, 1.05])\nplt.xlabel('False Positive Rate')\nplt.ylabel('True Positive Rate')\nplt.title('Receiver Operating Characteristic')\nplt.legend(loc=\"lower right\")\nplt.savefig('roc_curve.png')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:00:38.102308Z","iopub.execute_input":"2026-07-11T12:00:38.102762Z","iopub.status.idle":"2026-07-11T12:00:38.836832Z","shell.execute_reply.started":"2026-07-11T12:00:38.102737Z","shell.execute_reply":"2026-07-11T12:00:38.836084Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_pred_prob = model.predict(X_test).flatten()\n\nprint(\"Minimum :\", y_pred_prob.min())\nprint(\"Maximum :\", y_pred_prob.max())\nprint(\"Moyenne :\", y_pred_prob.mean())\n\nprint(\"\\n20 premières probabilités :\")\nprint(y_pred_prob[:20])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:00:38.837563Z","iopub.execute_input":"2026-07-11T12:00:38.837754Z","iopub.status.idle":"2026-07-11T12:00:39.784541Z","shell.execute_reply.started":"2026-07-11T12:00:38.837738Z","shell.execute_reply":"2026-07-11T12:00:39.783899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Simpan model akhir\nmodel.save('final_model.h5')\nprint(\"\\nModel akhir disimpan sebagai 'final_model.h5'\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:00:39.785178Z","iopub.execute_input":"2026-07-11T12:00:39.785449Z","iopub.status.idle":"2026-07-11T12:00:39.858855Z","shell.execute_reply.started":"2026-07-11T12:00:39.785432Z","shell.execute_reply":"2026-07-11T12:00:39.858165Z"}},"outputs":[],"execution_count":null}]}