{"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 warnings\nwarnings.filterwarnings(\"ignore\")\n\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport pydicom\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, callbacks\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import classification_report, confusion_matrix, roc_auc_score\n\n# ═══════════════════════════════════════════════════════════════════════════════\n# CONFIGURATION OPTIMISÉE\n# ═══════════════════════════════════════════════════════════════════════════════\nPATH = '/kaggle/input/rsna-miccai-brain-tumor-radiogenomic-classification/'\nIMG_SIZE = 128\nNUM_SLICES = 16\nBATCH_SIZE = 8          # Réduit légèrement pour mieux généraliser\nEPOCHS = 40\nMODALITIES = ['FLAIR', 'T1wCE'] # Utilisation de 2 modalités clés\n\n# ═══════════════════════════════════════════════════════════════════════════════\n# 1. CHARGEMENT NETTOYÉ & SANS DOUBLE NORMALISATION\n# ═══════════════════════════════════════════════════════════════════════════════\ntrain_labels = pd.read_csv(PATH + 'train_labels.csv')\n\ndef load_dicom_slice(filepath):\n    \"\"\"Charge et normalise une coupe DICOM de 0.0 à 1.0\"\"\"\n    try:\n        dicom = pydicom.dcmread(filepath)\n        img = dicom.pixel_array.astype(np.float32)\n        \n        # Supprimer le bruit de fond (pixels noirs/crâne)\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        return img\n    except:\n        return None\n\ndef load_patient_volume(patient_id):\n    \"\"\"Charge les coupes en sélectionnant celles avec du signal (tumeur/cerveau)\"\"\"\n    combined_modalities = []\n    \n    for mod in MODALITIES:\n        patient_path = os.path.join(PATH, 'train', str(patient_id).zfill(5), mod)\n        if not os.path.exists(patient_path):\n            return None\n        \n        files = sorted([f for f in os.listdir(patient_path) if f.endswith('.dcm')])\n        if not files:\n            return None\n        \n        # Sélection intelligente : cibler le centre (40% à 60% du volume)\n        n = len(files)\n        start = int(n * 0.3)\n        end = int(n * 0.7)\n        selected_files = files[start:end] if (end - start) >= NUM_SLICES else files\n        \n        # Échantillonnage uniforme\n        indices = np.linspace(0, len(selected_files) - 1, NUM_SLICES, dtype=int)\n        \n        slices = []\n        for idx in indices:\n            img = load_dicom_slice(os.path.join(patient_path, selected_files[idx]))\n            if img is not None:\n                slices.append(cv2.resize(img, (IMG_SIZE, IMG_SIZE)))\n            else:\n                slices.append(np.zeros((IMG_SIZE, IMG_SIZE)))\n                \n        combined_modalities.append(np.array(slices))\n    \n    # Empiler les modalités sur le canal final: Shape (NUM_SLICES, IMG_SIZE, IMG_SIZE, 2)\n    volume = np.stack(combined_modalities, axis=-1)\n    return volume\n\nprint(\"⏳ Chargement des données...\")\nX, y = [], []\nfor idx, row in train_labels.iterrows():\n    vol = load_patient_volume(row['BraTS21ID'])\n    if vol is not None:\n        X.append(vol)\n        y.append(row['MGMT_value'])\n\n# ⚠️ CORRECTION CRITIQUE : Pas de \"/ 255.0\" ici ! Les images sont déjà normalisées.\nX = np.array(X, dtype=np.float32) \ny = np.array(y, dtype=np.float32)\n\nprint(f\"✅ Forme des données: {X.shape} | Positifs: {int(sum(y))}, Négatifs: {len(y) - int(sum(y))}\")\n\n# Split Stratifié\nX_train, X_temp, y_train, y_temp = train_test_split(X, y, test_size=0.3, random_state=42, stratify=y)\nX_val, X_test, y_val, y_test = train_test_split(X_temp, y_temp, test_size=0.5, random_state=42, stratify=y_temp)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:09:10.307512Z","iopub.execute_input":"2026-07-11T12:09:10.307709Z","iopub.status.idle":"2026-07-11T12:12:41.950035Z","shell.execute_reply.started":"2026-07-11T12:09:10.307692Z","shell.execute_reply":"2026-07-11T12:12:41.949036Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ═══════════════════════════════════════════════════════════════════════════════\n# 2. ARCHITECTURE 3D SANS COUCHES D'AUGMENTATION PROBLÉMATIQUES\n# ═══════════════════════════════════════════════════════════════════════════════\ndef build_robust_3d_cnn(input_shape):\n    inputs = layers.Input(shape=input_shape)\n    \n    # Bloc Conv 1\n    x = layers.Conv3D(32, (3, 3, 3), padding='same', kernel_initializer='he_normal')(inputs)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation('relu')(x)\n    x = layers.MaxPooling3D((1, 2, 2))(x)\n    \n    # Bloc Conv 2\n    x = layers.Conv3D(64, (3, 3, 3), padding='same', kernel_initializer='he_normal')(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation('relu')(x)\n    x = layers.MaxPooling3D((2, 2, 2))(x)\n    x = layers.Dropout(0.3)(x)\n\n    # Bloc Conv 3\n    x = layers.Conv3D(128, (3, 3, 3), padding='same', kernel_initializer='he_normal')(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation('relu')(x)\n    x = layers.MaxPooling3D((2, 2, 2))(x)\n    x = layers.Dropout(0.4)(x)\n\n    # Dense / Classification\n    x = layers.GlobalAveragePooling3D()(x)\n    x = layers.Dense(64, activation='relu', kernel_regularizer=tf.keras.regularizers.l2(0.01))(x)\n    x = layers.Dropout(0.5)(x)\n    outputs = layers.Dense(1, activation='sigmoid')(x)\n    \n    return models.Model(inputs, outputs)\n\n# ⚠️ On ré-instancie proprement le modèle\nmodel = build_robust_3d_cnn((NUM_SLICES, IMG_SIZE, IMG_SIZE, len(MODALITIES)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:18:08.961713Z","iopub.execute_input":"2026-07-11T12:18:08.962465Z","iopub.status.idle":"2026-07-11T12:18:09.039838Z","shell.execute_reply.started":"2026-07-11T12:18:08.962438Z","shell.execute_reply":"2026-07-11T12:18:09.039289Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ═══════════════════════════════════════════════════════════════════════════════\n# 3. COMPILATION ET ENTRAÎNEMENT\n# ═══════════════════════════════════════════════════════════════════════════════\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n    loss='binary_crossentropy',\n    metrics=['accuracy', tf.keras.metrics.AUC(name='auc')]\n)\n\ncb_list = [\n    callbacks.EarlyStopping(\n        monitor='val_auc', \n        patience=12, \n        mode='max', \n        restore_best_weights=True,\n        verbose=1\n    ),\n    callbacks.ReduceLROnPlateau(\n        monitor='val_loss', \n        factor=0.5, \n        patience=4, \n        min_lr=1e-6, \n        verbose=1\n    ),\n    callbacks.ModelCheckpoint(\n        'best_brain_model.keras', \n        monitor='val_auc', \n        mode='max', \n        save_best_only=True,\n        verbose=1\n    )\n]\n\nprint(\"\\n🚀 Lancement de l'entraînement...\")\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=cb_list,\n    verbose=1\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:18:27.908231Z","iopub.execute_input":"2026-07-11T12:18:27.908493Z","iopub.status.idle":"2026-07-11T12:21:29.898627Z","shell.execute_reply.started":"2026-07-11T12:18:27.908477Z","shell.execute_reply":"2026-07-11T12:21:29.897873Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ═══════════════════════════════════════════════════════════════════════════════\n# 4. ÉVALUATION\n# ═══════════════════════════════════════════════════════════════════════════════\ny_test_prob = model.predict(X_test).flatten()\ny_test_pred = (y_test_prob > 0.5).astype(int)\n\nprint(f\"\\n🎯 Test AUC: {roc_auc_score(y_test, y_test_prob):.4f}\")\nprint(f\"📊 Test Accuracy: {np.mean(y_test_pred == y_test):.4f}\")\nprint(\"\\nMatrice de confusion:\")\nprint(confusion_matrix(y_test, y_test_pred))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T12:22:09.022703Z","iopub.execute_input":"2026-07-11T12:22:09.023203Z","iopub.status.idle":"2026-07-11T12:22:17.261227Z","shell.execute_reply.started":"2026-07-11T12:22:09.023175Z","shell.execute_reply":"2026-07-11T12:22:17.260457Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T10:11:54.821815Z","iopub.status.idle":"2026-07-11T10:11:54.822123Z","shell.execute_reply.started":"2026-07-11T10:11:54.821989Z","shell.execute_reply":"2026-07-11T10:11:54.822004Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T10:11:54.823214Z","iopub.status.idle":"2026-07-11T10:11:54.823491Z","shell.execute_reply.started":"2026-07-11T10:11:54.823371Z","shell.execute_reply":"2026-07-11T10:11:54.823383Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T10:11:54.825184Z","iopub.status.idle":"2026-07-11T10:11:54.825422Z","shell.execute_reply.started":"2026-07-11T10:11:54.825305Z","shell.execute_reply":"2026-07-11T10:11:54.825315Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T10:11:54.827026Z","iopub.status.idle":"2026-07-11T10:11:54.827253Z","shell.execute_reply.started":"2026-07-11T10:11:54.827143Z","shell.execute_reply":"2026-07-11T10:11:54.827153Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T10:11:54.828705Z","iopub.status.idle":"2026-07-11T10:11:54.829073Z","shell.execute_reply.started":"2026-07-11T10:11:54.828870Z","shell.execute_reply":"2026-07-11T10:11:54.828889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-07-11T10:11:54.830339Z","iopub.status.idle":"2026-07-11T10:11:54.830570Z","shell.execute_reply.started":"2026-07-11T10:11:54.830461Z","shell.execute_reply":"2026-07-11T10:11:54.830470Z"}},"outputs":[],"execution_count":null}]}