{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ===========================\n# 1. IMPORTS ET CONFIGURATION\n# ===========================\nimport os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom pathlib import Path\nimport cv2\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as transforms\nfrom sklearn.model_selection import train_test_split\nimport warnings\nwarnings.filterwarnings('ignore')\n\n# Configuration\nCONFIG = {\n    'batch_size': 8,\n    'lr': 1e-4,\n    'epochs': 50,\n    'img_size': 512,\n    'train_split': 0.8,\n    'device': torch.device('cuda' if torch.cuda.is_available() else 'cpu'),\n    'seed': 42\n}\n\n# Fixer le seed pour la reproductibilité\ndef set_seed(seed):\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\n\nset_seed(CONFIG['seed'])\nprint(f\"Utilisation du device: {CONFIG['device']}\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-02-01T20:27:22.658818Z","iopub.execute_input":"2026-02-01T20:27:22.659053Z","iopub.status.idle":"2026-02-01T20:27:37.544263Z","shell.execute_reply.started":"2026-02-01T20:27:22.659032Z","shell.execute_reply":"2026-02-01T20:27:37.543597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 2. EXPLORATION DES DONNÉES\n# ===========================\n\ndef explorer_structure_donnees(chemin_base):\n    \"\"\"Explorer la structure complète des données\"\"\"\n    \n    # Chemins des dossiers\n    chemins = {\n        'train_images': '/kaggle/input/vesuvius-challenge-surface-detection/train_images/',\n        'train_labels': '/kaggle/input/vesuvius-challenge-surface-detection/train_labels/',\n        'test_images': '/kaggle/input/vesuvius-challenge-surface-detection/test_images/',\n        'train_csv': '/kaggle/input/vesuvius-challenge-surface-detection/train.csv',\n        'test_csv': '/kaggle/input/vesuvius-challenge-surface-detection/test.csv'\n    }\n    \n    # Explorer chaque dossier\n    print(\"📁 STRUCTURE DES DONNÉES:\")\n    print(\"=\"*50)\n    \n    for nom, chemin in chemins.items():\n        if 'csv' in nom:\n            # Lire les fichiers CSV\n            df = pd.read_csv(chemin)\n            print(f\"\\n{nom}: {len(df)} lignes\")\n            print(f\"Colonnes: {list(df.columns)}\")\n            print(df.head(2))\n        else:\n            # Compter les images\n            fichiers = list(Path(chemin).glob('*.tif'))\n            print(f\"\\n{nom}: {len(fichiers)} fichiers .tif\")\n            if fichiers:\n                print(f\"Exemple: {fichiers[0].name}\")\n    \n    return chemins\n\n# Charger les métadonnées\ntrain_df = pd.read_csv('/kaggle/input/vesuvius-challenge-surface-detection/train.csv')\ntest_df = pd.read_csv('/kaggle/input/vesuvius-challenge-surface-detection/test.csv')\n\nprint(\"\\n📊 APERÇU DES MÉTADONNÉES:\")\nprint(f\"Images d'entraînement: {len(train_df)}\")\nprint(f\"Images de test: {len(test_df)}\")\nprint(f\"\\nColonnes train: {train_df.columns.tolist()}\")\nprint(f\"Colonnes test: {test_df.columns.tolist()}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T20:27:37.54599Z","iopub.execute_input":"2026-02-01T20:27:37.54639Z","iopub.status.idle":"2026-02-01T20:27:37.576672Z","shell.execute_reply.started":"2026-02-01T20:27:37.54637Z","shell.execute_reply":"2026-02-01T20:27:37.575483Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 3. VISUALISATION DES DONNÉES\n# ===========================\n\"\"\"\nVisualisation des scans CT et de leurs masques de segmentation\nCela nous aide à comprendre la nature des données et la complexité du problème\n\"\"\"\n\ndef visualiser_echantillons(n_echantillons=3):\n    \"\"\"Visualiser des échantillons d'images et leurs masques\"\"\"\n    \n    # Sélectionner des échantillons aléatoires\n    echantillons = train_df.sample(n_echantillons)\n    \n    fig, axes = plt.subplots(n_echantillons, 3, figsize=(15, 5*n_echantillons))\n    \n    for idx, (_, row) in enumerate(echantillons.iterrows()):\n        # Charger l'image\n        img_path = f\"/kaggle/input/vesuvius-challenge-surface-detection/train_images/{row['id']}.tif\"\n        mask_path = f\"/kaggle/input/vesuvius-challenge-surface-detection/train_labels/{row['id']}.tif\"\n        \n        # Lire les images\n        image = Image.open(img_path)\n        mask = Image.open(mask_path)\n        \n        # Convertir en tableaux numpy\n        img_array = np.array(image)\n        mask_array = np.array(mask)\n        \n        # Afficher\n        axes[idx, 0].imshow(img_array, cmap='gray')\n        axes[idx, 0].set_title(f'Image: {row[\"id\"]}')\n        axes[idx, 0].axis('off')\n        \n        axes[idx, 1].imshow(mask_array, cmap='gray')\n        axes[idx, 1].set_title(f'Masque: {row[\"id\"]}')\n        axes[idx, 1].axis('off')\n        \n        # Superposition\n        axes[idx, 2].imshow(img_array, cmap='gray')\n        axes[idx, 2].imshow(mask_array, cmap='Reds', alpha=0.5)\n        axes[idx, 2].set_title(f'Superposition: {row[\"id\"]}')\n        axes[idx, 2].axis('off')\n    \n    plt.tight_layout()\n    plt.savefig('aperçu_données.png', dpi=300, bbox_inches='tight')\n    plt.show()\n\n# Visualiser quelques échantillons\nvisualiser_echantillons(n_echantillons=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T20:27:37.577127Z","iopub.status.idle":"2026-02-01T20:27:37.577376Z","shell.execute_reply.started":"2026-02-01T20:27:37.57726Z","shell.execute_reply":"2026-02-01T20:27:37.577273Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 4. DATASET PERSONNALISÉ\n# ===========================\n\"\"\"\nDataset PyTorch personnalisé pour gérer les images médicales TIFF\navec leurs masques de segmentation\n\"\"\"\n\nclass VesuviusDataset(Dataset):\n    \"\"\"Dataset pour les scans Vesuvius avec masques\"\"\"\n    \n    def __init__(self, df, transform=None, is_train=True):\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n        self.is_train = is_train\n        \n        # Chemins de base\n        self.train_img_path = '/kaggle/input/vesuvius-challenge-surface-detection/train_images/'\n        self.train_mask_path = '/kaggle/input/vesuvius-challenge-surface-detection/train_labels/'\n        self.test_img_path = '/kaggle/input/vesuvius-challenge-surface-detection/test_images/'\n    \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_id = row['id']\n        \n        if self.is_train:\n            # Mode entraînement : charger image + masque\n            img_path = f\"{self.train_img_path}{img_id}.tif\"\n            mask_path = f\"{self.train_mask_path}{img_id}.tif\"\n            \n            # Charger l'image\n            image = Image.open(img_path)\n            image = np.array(image, dtype=np.float32)\n            \n            # Normaliser l'image\n            image = (image - image.min()) / (image.max() - image.min() + 1e-8)\n            \n            # Charger le masque\n            mask = Image.open(mask_path)\n            mask = np.array(mask, dtype=np.float32)\n            mask = (mask > 0).astype(np.float32)  # Binariser\n            \n            # Redimensionner si nécessaire\n            if image.shape != (CONFIG['img_size'], CONFIG['img_size']):\n                image = cv2.resize(image, (CONFIG['img_size'], CONFIG['img_size']))\n                mask = cv2.resize(mask, (CONFIG['img_size'], CONFIG['img_size']))\n            \n            # Appliquer les augmentations\n            if self.transform:\n                augmented = self.transform(image=image, mask=mask)\n                image = augmented['image']\n                mask = augmented['mask']\n            \n            return {\n                'image': torch.tensor(image).unsqueeze(0),\n                'mask': torch.tensor(mask).unsqueeze(0),\n                'id': img_id\n            }\n        \n        else:\n            # Mode test : charger seulement l'image\n            img_path = f\"{self.test_img_path}{img_id}.tif\"\n            image = Image.open(img_path)\n            image = np.array(image, dtype=np.float32)\n            image = (image - image.min()) / (image.max() - image.min() + 1e-8)\n            \n            if image.shape != (CONFIG['img_size'], CONFIG['img_size']):\n                image = cv2.resize(image, (CONFIG['img_size'], CONFIG['img_size']))\n            \n            return {\n                'image': torch.tensor(image).unsqueeze(0),\n                'id': img_id\n            }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T20:27:37.57852Z","iopub.status.idle":"2026-02-01T20:27:37.5788Z","shell.execute_reply.started":"2026-02-01T20:27:37.57866Z","shell.execute_reply":"2026-02-01T20:27:37.578677Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 5. AUGMENTATIONS AVEC ALBUMENTATIONS\n# ===========================\n\"\"\"\nAugmentations spécifiques pour les images médicales\nAlbumentations est idéal pour la segmentation\n\"\"\"\nimport albumentations as A\n\ndef get_transforms(is_train=True):\n    \"\"\"Obtenir les transformations pour train/val\"\"\"\n    \n    if is_train:\n        # Augmentations pour l'entraînement\n        transform = A.Compose([\n            A.HorizontalFlip(p=0.5),\n            A.VerticalFlip(p=0.5),\n            A.RandomRotate90(p=0.5),\n            A.ShiftScaleRotate(\n                shift_limit=0.1,\n                scale_limit=0.1,\n                rotate_limit=15,\n                p=0.5\n            ),\n            A.RandomBrightnessContrast(\n                brightness_limit=0.1,\n                contrast_limit=0.1,\n                p=0.3\n            ),\n            A.GaussNoise(var_limit=(0.0, 0.05), p=0.3),\n        ])\n    else:\n        # Pas d'augmentations pour la validation\n        transform = A.Compose([\n            # Seulement des transformations nécessaires\n        ])\n    \n    return transform","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T20:27:37.579721Z","iopub.status.idle":"2026-02-01T20:27:37.579984Z","shell.execute_reply.started":"2026-02-01T20:27:37.579865Z","shell.execute_reply":"2026-02-01T20:27:37.579877Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =====================================================================\n#  U-Net SIMPLE & COHÉRENT (plus d’erreurs de dimension)\n# =====================================================================\nclass UNetVesuvius(nn.Module):\n    def __init__(self, in_ch=1, out_ch=1):\n        super().__init__()\n\n        # --------- encodeur ---------\n        self.enc1 = self.conv_block(in_ch, 64)\n        self.enc2 = self.conv_block(64, 128)\n        self.enc3 = self.conv_block(128, 256)\n        self.enc4 = self.conv_block(256, 512)\n        self.enc5 = self.conv_block(512, 1024)\n\n        # --------- décodeur ---------\n        self.up4 = nn.ConvTranspose2d(1024, 512, 2, stride=2)   # 1024 → 512\n        self.dec4 = self.conv_block(1024, 512)                  # skip 512\n\n        self.up3 = nn.ConvTranspose2d(512, 256, 2, stride=2)    # 512 → 256\n        self.dec3 = self.conv_block(512, 256)                   # skip 256\n\n        self.up2 = nn.ConvTranspose2d(256, 128, 2, stride=2)    # 256 → 128\n        self.dec2 = self.conv_block(256, 128)                   # skip 128\n\n        self.up1 = nn.ConvTranspose2d(128, 64, 2, stride=2)     # 128 → 64\n        self.dec1 = self.conv_block(128, 64)                    # skip 64\n\n        self.out = nn.Conv2d(64, out_ch, 1)\n\n    # bloc « double convolution » + BN + ReLU\n    @staticmethod\n    def conv_block(in_c, out_c):\n        return nn.Sequential(\n            nn.Conv2d(in_c, out_c, 3, padding=1, bias=False),\n            nn.BatchNorm2d(out_c),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_c, out_c, 3, padding=1, bias=False),\n            nn.BatchNorm2d(out_c),\n            nn.ReLU(inplace=True)\n        )\n\n    def forward(self, x):\n        # ---------------- encodeur ----------------\n        x1 = self.enc1(x)                       # 64\n        x2 = self.enc2(F.max_pool2d(x1, 2))     # 128\n        x3 = self.enc3(F.max_pool2d(x2, 2))     # 256\n        x4 = self.enc4(F.max_pool2d(x3, 2))     # 512\n        x5 = self.enc5(F.max_pool2d(x4, 2))     # 1024\n\n        # ---------------- décodeur ----------------\n        u4 = self.up4(x5)                       # 512\n        u4 = torch.cat([u4, x4], dim=1)         # 512+512 = 1024\n        d4 = self.dec4(u4)                      # 512\n\n        u3 = self.up3(d4)                       # 256\n        u3 = torch.cat([u3, x3], dim=1)         # 256+256 = 512\n        d3 = self.dec3(u3)                      # 256\n\n        u2 = self.up2(d3)                       # 128\n        u2 = torch.cat([u2, x2], dim=1)         # 128+128 = 256\n        d2 = self.dec2(u2)                      # 128\n\n        u1 = self.up1(d2)                       # 64\n        u1 = torch.cat([u1, x1], dim=1)         # 64+64 = 128\n        d1 = self.dec1(u1)                      # 64\n\n        return self.out(d1)                     # 1","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T20:27:37.581083Z","iopub.status.idle":"2026-02-01T20:27:37.581395Z","shell.execute_reply.started":"2026-02-01T20:27:37.581222Z","shell.execute_reply":"2026-02-01T20:27:37.581241Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# FONCTION D’ENTRAÎNEMENT CORRIGÉE\n# ===========================\ndef entrainement_simple(model, train_loader, val_loader, n_epochs=30):\n    optimizer = torch.optim.Adam(model.parameters(), lr=CONFIG['lr'])\n    criterion = nn.BCEWithLogitsLoss()          # simple et stable\n    best_dice = 0.0\n    history = {'train_loss': [], 'val_dice': []}\n\n    for epoch in range(1, n_epochs+1):\n        print(f\"\\n📅 Epoch {epoch}/{n_epochs}\")\n        # ---- train ----\n        model.train()\n        running_loss = 0.\n        for batch in train_loader:\n            images, masks = batch['image'].to(CONFIG['device']), batch['mask'].to(CONFIG['device'])\n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, masks)\n            loss.backward()\n            optimizer.step()\n            running_loss += loss.item()\n        avg_train_loss = running_loss / len(train_loader)\n\n        # ---- val ----\n        model.eval()\n        val_dices = []\n        with torch.no_grad():\n            for batch in val_loader:\n                images, masks = batch['image'].to(CONFIG['device']), batch['mask'].to(CONFIG['device'])\n                preds = torch.sigmoid(model(images)) > 0.5\n                for p, m in zip(preds, masks):\n                    val_dices.append(calculate_dice(p.float(), m))\n        avg_val_dice = np.mean(val_dices)\n\n        # log\n        history['train_loss'].append(avg_train_loss)\n        history['val_dice'].append(avg_val_dice)\n        print(f\"  train loss: {avg_train_loss:.4f}  |  val dice: {avg_val_dice:.4f}\")\n\n        # save best\n        if avg_val_dice > best_dice:\n            best_dice = avg_val_dice\n            torch.save(model.state_dict(), \"best_model.pth\")\n            print(f\"  ✅ best model saved (dice {best_dice:.4f})\")\n    return history, best_dice","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T20:27:37.582908Z","iopub.status.idle":"2026-02-01T20:27:37.583202Z","shell.execute_reply.started":"2026-02-01T20:27:37.58304Z","shell.execute_reply":"2026-02-01T20:27:37.583059Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 8. FONCTIONS UTILITAIRES\n# ===========================\ndef calculate_dice(pred, target, threshold=0.5):\n    \"\"\"Calculer le Dice Score\"\"\"\n    pred = (pred > threshold).float()\n    \n    intersection = (pred * target).sum()\n    union = pred.sum() + target.sum()\n    \n    if union == 0:\n        return torch.tensor(1.0, device=pred.device)\n    \n    return (2. * intersection) / union\n\ndef visualiser_predictions_avance(model, dataset, n_samples=3):\n    \"\"\"Visualiser les prédictions avec comparaison détaillée\"\"\"\n    \n    model.eval()\n    indices = np.random.choice(len(dataset), n_samples, replace=False)\n    \n    fig, axes = plt.subplots(n_samples, 4, figsize=(20, 5*n_samples))\n    \n    with torch.no_grad():\n        for i, idx in enumerate(indices):\n            data = dataset[idx]\n            image = data['image'].unsqueeze(0).to(CONFIG['device'])\n            true_mask = data['mask'].squeeze(0)\n            \n            # Prédiction\n            pred = model(image)\n            pred_mask = torch.sigmoid(pred).squeeze(0).squeeze(0).cpu()\n            pred_binary = (pred_mask > 0.5).float()\n            \n            # Convertir pour l'affichage\n            img_display = data['image'].squeeze(0)\n            \n            # Afficher l'image originale\n            axes[i, 0].imshow(img_display, cmap='gray')\n            axes[i, 0].set_title(f'Image Originale\\n{data[\"id\"]}')\n            axes[i, 0].axis('off')\n            \n            # Afficher le vrai masque\n            axes[i, 1].imshow(true_mask, cmap='gray')\n            axes[i, 1].set_title('Masque Réel')\n            axes[i, 1].axis('off')\n            \n            # Afficher la prédiction\n            axes[i, 2].imshow(pred_binary, cmap='gray')\n            dice = calculate_dice(pred_binary, true_mask)\n            axes[i, 2].set_title(f'Prédiction\\nDice: {dice:.3f}')\n            axes[i, 2].axis('off')\n            \n            # Superposition\n            axes[i, 3].imshow(img_display, cmap='gray')\n            axes[i, 3].imshow(pred_binary, cmap='Reds', alpha=0.5)\n            axes[i, 3].set_title('Superposition')\n            axes[i, 3].axis('off')\n    \n    plt.tight_layout()\n    plt.savefig('predictions_avancees.png', dpi=300, bbox_inches='tight')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T20:27:37.584359Z","iopub.status.idle":"2026-02-01T20:27:37.584932Z","shell.execute_reply.started":"2026-02-01T20:27:37.58475Z","shell.execute_reply":"2026-02-01T20:27:37.58477Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# 9. PRÉPARATION DES DONNÉES\n# ===========================\n\"\"\"\nPréparation finale des données avec split train/val\n\"\"\"\n\n# Créer les datasets\ntrain_transform = get_transforms(is_train=True)\nval_transform = get_transforms(is_train=False)\n\n# Split train/val\ntrain_df_split, val_df_split = train_test_split(\n    train_df, test_size=0.2, random_state=CONFIG['seed'], stratify=None\n)\n\nprint(f\"📊 Split des données:\")\nprint(f\"   - Entraînement: {len(train_df_split)} échantillons\")\nprint(f\"   - Validation: {len(val_df_split)} échantillons\")\n\n# Créer les datasets\ntrain_dataset = VesuviusDataset(train_df_split, transform=train_transform, is_train=True)\nval_dataset = VesuviusDataset(val_df_split, transform=val_transform, is_train=True)\n\n# Créer les dataloaders\ntrain_loader = DataLoader(\n    train_dataset, \n    batch_size=CONFIG['batch_size'], \n    shuffle=True, \n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset, \n    batch_size=CONFIG['batch_size'], \n    shuffle=False, \n    num_workers=2,\n    pin_memory=True\n)\n\nprint(f\"✅ DataLoaders créés!\")\nprint(f\"   - Batches d'entraînement: {len(train_loader)}\")\nprint(f\"   - Batches de validation: {len(val_loader)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T20:27:37.586647Z","iopub.status.idle":"2026-02-01T20:27:37.586924Z","shell.execute_reply.started":"2026-02-01T20:27:37.58681Z","shell.execute_reply":"2026-02-01T20:27:37.586827Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# LANCEMENT RAPIDE AVEC AFFICHAGE DES ITÉRATIONS\n# ===========================\nmodel = UNetVesuvius(in_ch=1, out_ch=1).to(CONFIG['device'])\nprint(f\"📊 Modèle créé – {sum(p.numel() for p in model.parameters()):,} paramètres\")\n\n# ===========================\n# FONCTION D'ENTRAÎNEMENT AVEC AFFICHAGE DES ITÉRATIONS\n# ===========================\ndef entrainement_avec_iterations(model, train_loader, val_loader, n_epochs=30):\n    optimizer = torch.optim.Adam(model.parameters(), lr=CONFIG['lr'])\n    criterion = nn.BCEWithLogitsLoss()\n    best_dice = 0.0\n    history = {'train_loss': [], 'val_dice': []}\n\n    for epoch in range(1, n_epochs+1):\n        print(f\"\\n📅 Epoch {epoch}/{n_epochs}\")\n        \n        # Phase d'entraînement avec affichage des iterations\n        model.train()\n        running_loss = 0.0\n        n_batches = len(train_loader)\n        \n        for batch_idx, batch in enumerate(train_loader):\n            images = batch['image'].to(CONFIG['device'])\n            masks = batch['mask'].to(CONFIG['device'])\n            \n            optimizer.zero_grad()\n            outputs = model(images)\n            loss = criterion(outputs, masks)\n            loss.backward()\n            optimizer.step()\n            \n            running_loss += loss.item()\n            \n            # Affichage chaque 10 iterations\n            if batch_idx % 10 == 0 or batch_idx == n_batches - 1:\n                print(f\"  🔄 Batch {batch_idx+1}/{n_batches} - Loss: {loss.item():.6f}\")\n            \n            # Barre de progression simple\n            if batch_idx % 5 == 0:\n                progress = (batch_idx + 1) / n_batches * 100\n                bar_length = int(progress / 5)\n                print(f\"  [{'█' * bar_length}{'░' * (20 - bar_length)}] {progress:.1f}%\", end='\\r')\n        \n        avg_train_loss = running_loss / n_batches\n        print(f\"\\n  ✅ Train Loss moyen: {avg_train_loss:.6f}\")\n        \n        # Phase de validation\n        model.eval()\n        val_dices = []\n        \n        with torch.no_grad():\n            for batch_idx, batch in enumerate(val_loader):\n                images = batch['image'].to(CONFIG['device'])\n                masks = batch['mask'].to(CONFIG['device'])\n                \n                outputs = model(images)\n                pred_binary = (torch.sigmoid(outputs) > 0.5).float()\n                \n                for i in range(images.size(0)):\n                    dice = calculate_dice(pred_binary[i], masks[i])\n                    val_dices.append(dice.item())\n                \n                # Affichage progression validation\n                if batch_idx % 5 == 0:\n                    val_progress = (batch_idx + 1) / len(val_loader) * 100\n                    print(f\"  🔍 Validation: {val_progress:.1f}%\", end='\\r')\n        \n        avg_val_dice = np.mean(val_dices)\n        history['train_loss'].append(avg_train_loss)\n        history['val_dice'].append(avg_val_dice)\n        \n        print(f\"\\n  📊 Résultats epoch {epoch}:\")\n        print(f\"     Train Loss: {avg_train_loss:.6f}  |  Val Dice: {avg_val_dice:.6f}\")\n        \n        # Sauvegarder le meilleur modèle\n        if avg_val_dice > best_dice:\n            best_dice = avg_val_dice\n            torch.save(model.state_dict(), 'best_model_with_iterations.pth')\n            print(f\"  🏆 Nouveau meilleur modèle! Dice: {best_dice:.6f}\")\n        \n        # Petit résumé visuel par epoch\n        if epoch % 5 == 0:\n            print(f\"\\n  📈 Progression: {epoch}/{n_epochs} epochs complétées\")\n    \n    return history, best_dice\n\n# ===========================\n# LANCEMENT AVEC AFFICHAGE DÉTAILLÉ\n# ===========================\nprint(\"🚀 Démarrage de l'entraînement avec suivi des itérations...\")\nprint(\"=\" * 60)\n\nhistory, best_dice = entrainement_avec_iterations(model, train_loader, val_loader, CONFIG['epochs'])\n\nprint(\"\\n\" + \"=\" * 60)\nprint(f\"🏆 Entraînement terminé! Meilleur Dice: {best_dice:.6f}\")\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-01T20:27:37.587665Z","iopub.status.idle":"2026-02-01T20:27:37.587943Z","shell.execute_reply.started":"2026-02-01T20:27:37.587832Z","shell.execute_reply":"2026-02-01T20:27:37.587846Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ===========================\n# VISUALISATION SIMPLE (adaptée à notre history)\n# ===========================\ndef visualiser_resultats(history):\n    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 4))\n\n    # --- Loss ---\n    ax1.plot(history['train_loss'], 'b-', linewidth=2, label='Train')\n    ax1.set_title('Training Loss', fontsize=14)\n    ax1.set_xlabel('Epoch'); ax1.set_ylabel('Loss')\n    ax1.grid(alpha=0.3)\n\n    # --- Dice ---\n    ax2.plot(history['val_dice'], 'g-', linewidth=2, label='Val Dice')\n    ax2.set_title('Validation Dice Score', fontsize=14)\n    ax2.set_xlabel('Epoch'); ax2.set_ylabel('Dice Score')\n    ax2.grid(alpha=0.3)\n\n    plt.tight_layout()\n    plt.savefig('training_curves.png', dpi=300, bbox_inches='tight')\n    plt.show()\n\n# ---- exécution ----\nvisualiser_resultats(history)\nprint(\"✅ Courbes générées.\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---------  AJOUTER CECI  ---------\nfrom tqdm import tqdm\n# ----------------------------------\n# 1️⃣  CHARGER LE MEILLEUR MODÈLE\nmodel.load_state_dict(torch.load(\"best_model_with_iterations.pth\", map_location=CONFIG['device']))\nmodel.eval()\nprint(\"✅ Meilleur modèle restauré\")\n\n# 2️⃣  CRÉER LE DATASET DE TEST (sans masques)\ntest_df = pd.read_csv(\"/kaggle/input/vesuvius-challenge-surface-detection/test.csv\")\ntest_dataset = VesuviusDataset(test_df, is_train=False)\ntest_loader  = DataLoader(test_dataset, batch_size=1, shuffle=False, num_workers=1)\n\n# 3️⃣  FONCTION DE POST-TRAITEMENT (binarisation + redimensionnement)\ndef soumettre_predictions(model, loader, out_dir=\"submission\"):\n    os.makedirs(out_dir, exist_ok=True)\n    with torch.no_grad():\n        for batch in tqdm(loader, desc=\"Prédiction\"):\n            image = batch['image'].to(CONFIG['device'])\n            fname = batch['id'][0]          # nom de l'image sans extension\n            h, w  = image.shape[-2:]        # taille originale éventuelle\n\n            # inference\n            prob = torch.sigmoid(model(image)).squeeze(0).squeeze(0)  # H x W\n            mask = (prob > 0.5).float().cpu().numpy().astype(np.uint8) * 255\n\n            # redimensionner si nécessaire\n            if mask.shape != (h, w):\n                mask = cv2.resize(mask, (w, h), interpolation=cv2.INTER_NEAREST)\n\n            Image.fromarray(mask).save(os.path.join(out_dir, f\"{fname}_mask.png\"))\n    print(f\"📦 Masques sauvés dans {out_dir}/\")\n\n# 4️⃣  GÉNÉRER LES MASQUES\nsoumettre_predictions(model, test_loader)\n\n# 5️⃣  CRÉER LE CSV DE SOUMISSION\nsub_csv = test_df.copy()\nsub_csv['path'] = sub_csv['id'].apply(lambda x: f\"{x}_mask.png\")\nsub_csv.to_csv(\"submission.csv\", index=False)\nprint(\"✅ submission.csv créé\")\n\n# 6️⃣  VÉRIFICATION RAPIDE\nprint(sub_csv.head())\nprint(f\"Nombre de masques : {len(sub_csv)}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}