{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================================\n# CELL 1: Chuẩn bị môi trường và công cụ (Kaggle GPU)\n# ============================================================\nimport os\nimport glob\nimport random\nimport time\nimport zipfile\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom pathlib import Path\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom sklearn.model_selection import train_test_split\n\nimport cv2\n\n# Kiểm tra GPU\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Device: {device}\")\nif torch.cuda.is_available():\n    print(f\"GPU   : {torch.cuda.get_device_name(0)}\")\n    print(f\"VRAM  : {torch.cuda.get_device_properties(0).total_memory / 1e9:.1f} GB\")\n\n# Đường dẫn\ninput_dir   = \"/kaggle/input/competitions/carvana-image-masking-challenge\"\nworking_dir = \"/kaggle/working\"\n\n# Giải nén dataset (chỉ chạy lần đầu)\ntrain_zip = input_dir + \"/train.zip\"\nmask_zip  = input_dir + \"/train_masks.zip\"\n\nif not os.path.isdir(working_dir + \"/train\"):\n    print(\"Dang giai nen train.zip...\")\n    with zipfile.ZipFile(train_zip, \"r\") as z:\n        z.extractall(working_dir)\n    print(\"Done train/\")\n\n    print(\"Dang giai nen train_masks.zip...\")\n    with zipfile.ZipFile(mask_zip, \"r\") as z:\n        z.extractall(working_dir)\n    print(\"Done train_masks/\")\nelse:\n    print(\"Dataset da giai nen san.\")\n\n# Seed\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\ntorch.cuda.manual_seed_all(SEED)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-06-14T03:36:17.422071Z","iopub.execute_input":"2026-06-14T03:36:17.42291Z","iopub.status.idle":"2026-06-14T03:36:29.790158Z","shell.execute_reply.started":"2026-06-14T03:36:17.422878Z","shell.execute_reply":"2026-06-14T03:36:29.789505Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 2: BT1 & BT2 — Khám phá cấu trúc dataset\n# ============================================================\ntrain_img_dir  = os.path.join(working_dir, \"train\")\ntrain_mask_dir = os.path.join(working_dir, \"train_masks\")\n\nall_images = sorted(glob.glob(os.path.join(train_img_dir, \"*.jpg\")))\nall_masks  = sorted(glob.glob(os.path.join(train_mask_dir, \"*.gif\")))\n\nprint(f\"Số ảnh train      : {len(all_images)}\")\nprint(f\"Số mask tương ứng : {len(all_masks)}\")\n\nsample_img  = Image.open(all_images[0])\nsample_mask = Image.open(all_masks[0])\nprint(f\"\\nKích thước ảnh gốc : {sample_img.size}\")\nprint(f\"Kích thước mask    : {sample_mask.size}\")\nprint(f\"Mode ảnh           : {sample_img.mode}\")\nprint(f\"Mode mask          : {sample_mask.mode}\")\n\nmask_np = np.array(sample_mask)\nprint(f\"Unique values mask : {np.unique(mask_np)}\")\n\nimg_names  = {Path(p).stem for p in all_images}\nmask_names = {Path(p).stem.replace(\"_mask\", \"\") for p in all_masks}\nmismatch = img_names.symmetric_difference(mask_names)\nprint(f\"\\nSố file không match: {len(mismatch)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T03:36:34.9121Z","iopub.execute_input":"2026-06-14T03:36:34.913118Z","iopub.status.idle":"2026-06-14T03:36:35.068973Z","shell.execute_reply.started":"2026-06-14T03:36:34.913086Z","shell.execute_reply":"2026-06-14T03:36:35.068177Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 3: BT3 — Hiển thị ảnh gốc và mask tương ứng\n# ============================================================\nnum_samples = 5\nindices = random.sample(range(len(all_images)), num_samples)\n\nfig, axes = plt.subplots(2, num_samples, figsize=(20, 8))\nfig.suptitle(\"BT3 — Ảnh gốc (trên) và Ground Truth Mask (dưới)\", fontsize=14, fontweight='bold')\n\nfor i, idx in enumerate(indices):\n    img  = Image.open(all_images[idx])\n    mask = Image.open(all_masks[idx])\n    \n    axes[0, i].imshow(img)\n    axes[0, i].set_title(Path(all_images[idx]).name[:20], fontsize=8)\n    axes[0, i].axis(\"off\")\n    \n    axes[1, i].imshow(mask, cmap=\"gray\")\n    axes[1, i].set_title(\"Mask\", fontsize=8)\n    axes[1, i].axis(\"off\")\n\nplt.tight_layout()\nplt.savefig(f\"{working_dir}/bt3_visualization.png\", dpi=150, bbox_inches='tight')\nplt.show()\nprint(\"Saved: bt3_visualization.png\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T03:36:37.910314Z","iopub.execute_input":"2026-06-14T03:36:37.911019Z","iopub.status.idle":"2026-06-14T03:36:42.84305Z","shell.execute_reply.started":"2026-06-14T03:36:37.910993Z","shell.execute_reply":"2026-06-14T03:36:42.842074Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 4: BT4 — Chia tập Train / Validation (80/20)\n# ============================================================\ntrain_images, val_images, train_masks, val_masks = train_test_split(\n    all_images, all_masks,\n    test_size=0.2,\n    random_state=SEED\n)\n\nprint(f\"Training set   : {len(train_images)} ảnh\")\nprint(f\"Validation set : {len(val_images)} ảnh\")\nprint(f\"Tỉ lệ         : {len(train_images)/len(all_images)*100:.0f}% / {len(val_images)/len(all_images)*100:.0f}%\")\n\n# Verify match\nfor img_p, mask_p in zip(train_images[:3], train_masks[:3]):\n    img_stem  = Path(img_p).stem\n    mask_stem = Path(mask_p).stem.replace(\"_mask\", \"\")\n    print(f\"  {img_stem} <-> {mask_stem} : {'OK' if img_stem == mask_stem else 'MISMATCH'}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T03:36:49.586847Z","iopub.execute_input":"2026-06-14T03:36:49.587479Z","iopub.status.idle":"2026-06-14T03:36:49.59611Z","shell.execute_reply.started":"2026-06-14T03:36:49.587451Z","shell.execute_reply":"2026-06-14T03:36:49.595302Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 5: BT5 & BT6 — Dataset Class + DataLoader\n# ============================================================\nIMG_SIZE = 256  # resize cho U-Net\n\nclass CarvanaDataset(Dataset):\n    \"\"\"Dataset class cho Carvana binary segmentation.\"\"\"\n    def __init__(self, image_paths, mask_paths, img_size=IMG_SIZE, augment=False):\n        self.image_paths = image_paths\n        self.mask_paths  = mask_paths\n        self.img_size    = img_size\n        self.augment     = augment\n        \n        # Transform cho ảnh (chuẩn hóa ImageNet)\n        self.img_transform = transforms.Compose([\n            transforms.Resize((img_size, img_size)),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.485, 0.456, 0.406],\n                                 std=[0.229, 0.224, 0.225])\n        ])\n        # Transform cho mask (chỉ resize, KHÔNG normalize)\n        self.mask_transform = transforms.Compose([\n            transforms.Resize((img_size, img_size), interpolation=transforms.InterpolationMode.NEAREST),\n            transforms.ToTensor()\n        ])\n        \n    def __len__(self):\n        return len(self.image_paths)\n    \n    def __getitem__(self, idx):\n        # Đọc ảnh RGB\n        image = Image.open(self.image_paths[idx]).convert(\"RGB\")\n        # Đọc mask grayscale\n        mask  = Image.open(self.mask_paths[idx]).convert(\"L\")\n        \n        # Data augmentation đơn giản\n        if self.augment and random.random() > 0.5:\n            image = transforms.functional.hflip(image)\n            mask  = transforms.functional.hflip(mask)\n        \n        image = self.img_transform(image)\n        mask  = self.mask_transform(mask)\n        \n        # Đảm bảo mask binary [0, 1]\n        mask = (mask > 0.5).float()\n        \n        return image, mask\n\n# Tạo Dataset\ntrain_dataset = CarvanaDataset(train_images, train_masks, augment=True)\nval_dataset   = CarvanaDataset(val_images, val_masks, augment=False)\n\n# Tạo DataLoader — batch_size=8 mặc định\nBATCH_SIZE = 8\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True,  num_workers=2, pin_memory=True)\nval_loader   = DataLoader(val_dataset,   batch_size=BATCH_SIZE, shuffle=False, num_workers=2, pin_memory=True)\n\n# Verify shape\nimages, masks = next(iter(train_loader))\nprint(f\"Batch images shape : {images.shape}\")   # [8, 3, 256, 256]\nprint(f\"Batch masks shape  : {masks.shape}\")     # [8, 1, 256, 256]\nprint(f\"Mask unique values : {torch.unique(masks)}\")\nprint(f\"\\nTrain batches: {len(train_loader)}\")\nprint(f\"Val batches  : {len(val_loader)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T03:36:53.000449Z","iopub.execute_input":"2026-06-14T03:36:53.001255Z","iopub.status.idle":"2026-06-14T03:36:54.491803Z","shell.execute_reply.started":"2026-06-14T03:36:53.001194Z","shell.execute_reply":"2026-06-14T03:36:54.490949Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 6: Kiến trúc U-Net — có Skip Connections\n# ============================================================\nclass DoubleConv(nn.Module):\n    \"\"\"(Conv2d → BN → ReLU) × 2\"\"\"\n    def __init__(self, in_ch, out_ch):\n        super().__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(in_ch, out_ch, 3, padding=1, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_ch, out_ch, 3, padding=1, bias=False),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True)\n        )\n    def forward(self, x):\n        return self.conv(x)\n\n\nclass UNet(nn.Module):\n    \"\"\"\n    U-Net với 4 tầng encoder-decoder + skip connections.\n    Input:  (B, 3, 256, 256)\n    Output: (B, 1, 256, 256)\n    \"\"\"\n    def __init__(self, in_channels=3, out_channels=1, features=[64, 128, 256, 512]):\n        super().__init__()\n        self.encoders = nn.ModuleList()\n        self.decoders = nn.ModuleList()\n        self.pool = nn.MaxPool2d(2, 2)\n        \n        # Encoder path\n        for f in features:\n            self.encoders.append(DoubleConv(in_channels, f))\n            in_channels = f\n        \n        # Bottleneck\n        self.bottleneck = DoubleConv(features[-1], features[-1] * 2)\n        \n        # Decoder path\n        for f in reversed(features):\n            self.decoders.append(nn.ConvTranspose2d(f * 2, f, kernel_size=2, stride=2))\n            self.decoders.append(DoubleConv(f * 2, f))  # f*2 vì concat skip\n        \n        # Final 1×1 conv\n        self.final_conv = nn.Conv2d(features[0], out_channels, kernel_size=1)\n    \n    def forward(self, x):\n        skip_connections = []\n        \n        # Encoder\n        for encoder in self.encoders:\n            x = encoder(x)\n            skip_connections.append(x)\n            x = self.pool(x)\n        \n        # Bottleneck\n        x = self.bottleneck(x)\n        \n        # Decoder (reverse skip connections)\n        skip_connections = skip_connections[::-1]\n        for i in range(0, len(self.decoders), 2):\n            x = self.decoders[i](x)      # ConvTranspose2d (upsample)\n            skip = skip_connections[i // 2]\n            \n            # Handle size mismatch\n            if x.shape != skip.shape:\n                x = nn.functional.interpolate(x, size=skip.shape[2:])\n            \n            x = torch.cat([skip, x], dim=1)  # Skip connection (concat)\n            x = self.decoders[i + 1](x)      # DoubleConv\n        \n        return torch.sigmoid(self.final_conv(x))\n\n\n# Test kiến trúc\nmodel_test = UNet().to(device)\ndummy = torch.randn(1, 3, 256, 256).to(device)\nout = model_test(dummy)\nprint(f\"Input  : {dummy.shape}\")\nprint(f\"Output : {out.shape}\")\n\n# Đếm params\ntotal_params = sum(p.numel() for p in model_test.parameters())\nprint(f\"Total parameters: {total_params:,}\")\ndel model_test, dummy, out\ntorch.cuda.empty_cache()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-13T16:13:49.486785Z","iopub.execute_input":"2026-06-13T16:13:49.487183Z","iopub.status.idle":"2026-06-13T16:13:49.826262Z","shell.execute_reply.started":"2026-06-13T16:13:49.487134Z","shell.execute_reply":"2026-06-13T16:13:49.825472Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 7: Loss Functions + Metrics + Training/Eval functions\n# ============================================================\n\n# --- Loss Functions ---\nclass DiceLoss(nn.Module):\n    def __init__(self, smooth=1.0):\n        super().__init__()\n        self.smooth = smooth\n    \n    def forward(self, pred, target):\n        pred_flat   = pred.view(-1)\n        target_flat = target.view(-1)\n        intersection = (pred_flat * target_flat).sum()\n        dice = (2.0 * intersection + self.smooth) / (pred_flat.sum() + target_flat.sum() + self.smooth)\n        return 1 - dice\n\n\nclass BCEDiceLoss(nn.Module):\n    \"\"\"Kết hợp BCE + Dice cho kết quả tốt hơn.\"\"\"\n    def __init__(self):\n        super().__init__()\n        self.bce  = nn.BCELoss()\n        self.dice = DiceLoss()\n    \n    def forward(self, pred, target):\n        return self.bce(pred, target) + self.dice(pred, target)\n\n\n# --- Metrics ---\ndef compute_iou(pred, target, threshold=0.5):\n    pred_bin = (pred > threshold).float()\n    intersection = (pred_bin * target).sum()\n    union = pred_bin.sum() + target.sum() - intersection\n    if union == 0:\n        return 1.0\n    return (intersection / union).item()\n\n\ndef compute_dice(pred, target, threshold=0.5):\n    pred_bin = (pred > threshold).float()\n    intersection = (pred_bin * target).sum()\n    dice = (2.0 * intersection) / (pred_bin.sum() + target.sum() + 1e-8)\n    return dice.item()\n\n\n# --- Training 1 epoch ---\ndef train_one_epoch(model, loader, criterion, optimizer):\n    model.train()\n    total_loss = 0\n    total_iou  = 0\n    \n    for images, masks in loader:\n        images = images.to(device)\n        masks  = masks.to(device)\n        \n        preds = model(images)\n        loss  = criterion(preds, masks)\n        \n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        \n        total_loss += loss.item()\n        total_iou  += compute_iou(preds.detach(), masks)\n    \n    n = len(loader)\n    return total_loss / n, total_iou / n\n\n\n# --- Validate ---\n@torch.no_grad()\ndef validate(model, loader, criterion):\n    model.eval()\n    total_loss = 0\n    total_iou  = 0\n    total_dice = 0\n    \n    for images, masks in loader:\n        images = images.to(device)\n        masks  = masks.to(device)\n        \n        preds = model(images)\n        loss  = criterion(preds, masks)\n        \n        total_loss += loss.item()\n        total_iou  += compute_iou(preds, masks)\n        total_dice += compute_dice(preds, masks)\n    \n    n = len(loader)\n    return total_loss / n, total_iou / n, total_dice / n\n\n\n# --- Full training loop ---\ndef train_model(model, train_loader, val_loader, criterion, optimizer, \n                num_epochs=10, save_name=\"model\"):\n    history = {\"train_loss\": [], \"val_loss\": [], \"val_iou\": [], \"val_dice\": []}\n    best_iou = 0\n    \n    for epoch in range(num_epochs):\n        t0 = time.time()\n        train_loss, train_iou = train_one_epoch(model, train_loader, criterion, optimizer)\n        val_loss, val_iou, val_dice = validate(model, val_loader, criterion)\n        elapsed = time.time() - t0\n        \n        history[\"train_loss\"].append(train_loss)\n        history[\"val_loss\"].append(val_loss)\n        history[\"val_iou\"].append(val_iou)\n        history[\"val_dice\"].append(val_dice)\n        \n        print(f\"Epoch [{epoch+1:02d}/{num_epochs}] \"\n              f\"Train Loss: {train_loss:.4f} | \"\n              f\"Val Loss: {val_loss:.4f} | \"\n              f\"Val IoU: {val_iou:.4f} | \"\n              f\"Val Dice: {val_dice:.4f} | \"\n              f\"Time: {elapsed:.1f}s\")\n        \n        # Lưu model tốt nhất\n        if val_iou > best_iou:\n            best_iou = val_iou\n            torch.save(model.state_dict(), f\"{working_dir}/{save_name}_best.pth\")\n    \n    print(f\"\\nBest Val IoU: {best_iou:.4f}\")\n    return history\n\nprint(\"Training functions ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-13T16:13:49.82724Z","iopub.execute_input":"2026-06-13T16:13:49.827816Z","iopub.status.idle":"2026-06-13T16:13:49.844269Z","shell.execute_reply.started":"2026-06-13T16:13:49.827789Z","shell.execute_reply":"2026-06-13T16:13:49.843395Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 8: BT7 — Huấn luyện U-Net với BCE Loss\n# ============================================================\nNUM_EPOCHS = 15\n\nmodel_bce = UNet().to(device)\ncriterion_bce = nn.BCELoss()\noptimizer_bce = optim.Adam(model_bce.parameters(), lr=0.001)\n\nprint(\"=\" * 60)\nprint(\"BT7 — Training U-Net với BCE Loss, lr=0.001\")\nprint(\"=\" * 60)\n\nhistory_bce = train_model(\n    model_bce, train_loader, val_loader,\n    criterion_bce, optimizer_bce,\n    num_epochs=NUM_EPOCHS,\n    save_name=\"unet_bce\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-13T16:13:49.84514Z","iopub.execute_input":"2026-06-13T16:13:49.845437Z","iopub.status.idle":"2026-06-13T17:20:45.16015Z","shell.execute_reply.started":"2026-06-13T16:13:49.845399Z","shell.execute_reply":"2026-06-13T17:20:45.159118Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 9: BT8 — Huấn luyện U-Net với Dice Loss\n# ============================================================\nmodel_dice = UNet().to(device)\ncriterion_dice = DiceLoss()\noptimizer_dice = optim.Adam(model_dice.parameters(), lr=0.001)\n\nprint(\"=\" * 60)\nprint(\"BT8 — Training U-Net với Dice Loss, lr=0.001\")\nprint(\"=\" * 60)\n\nhistory_dice = train_model(\n    model_dice, train_loader, val_loader,\n    criterion_dice, optimizer_dice,\n    num_epochs=NUM_EPOCHS,\n    save_name=\"unet_dice\"\n)\n\n# --- So sánh BCE vs Dice ---\nfig, axes = plt.subplots(1, 3, figsize=(18, 5))\nfig.suptitle(\"BT8 — So sánh BCE Loss vs Dice Loss\", fontsize=14, fontweight='bold')\n\naxes[0].plot(history_bce[\"train_loss\"], label=\"BCE - Train\")\naxes[0].plot(history_dice[\"train_loss\"], label=\"Dice - Train\")\naxes[0].set_xlabel(\"Epoch\"); axes[0].set_ylabel(\"Train Loss\")\naxes[0].legend(); axes[0].set_title(\"Training Loss\")\n\naxes[1].plot(history_bce[\"val_loss\"], label=\"BCE - Val\")\naxes[1].plot(history_dice[\"val_loss\"], label=\"Dice - Val\")\naxes[1].set_xlabel(\"Epoch\"); axes[1].set_ylabel(\"Val Loss\")\naxes[1].legend(); axes[1].set_title(\"Validation Loss\")\n\naxes[2].plot(history_bce[\"val_iou\"], label=\"BCE\")\naxes[2].plot(history_dice[\"val_iou\"], label=\"Dice\")\naxes[2].set_xlabel(\"Epoch\"); axes[2].set_ylabel(\"IoU\")\naxes[2].legend(); axes[2].set_title(\"Validation IoU\")\n\nplt.tight_layout()\nplt.savefig(f\"{working_dir}/bt8_bce_vs_dice.png\", dpi=150, bbox_inches='tight')\nplt.show()\n\nprint(f\"\\nKết quả cuối cùng:\")\nprint(f\"  BCE  — Val IoU: {history_bce['val_iou'][-1]:.4f} | Val Dice: {history_bce['val_dice'][-1]:.4f}\")\nprint(f\"  Dice — Val IoU: {history_dice['val_iou'][-1]:.4f} | Val Dice: {history_dice['val_dice'][-1]:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-13T17:20:45.161953Z","iopub.execute_input":"2026-06-13T17:20:45.16231Z","iopub.status.idle":"2026-06-13T18:27:25.296136Z","shell.execute_reply.started":"2026-06-13T17:20:45.162274Z","shell.execute_reply":"2026-06-13T18:27:25.29499Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 10: BT9 — Tính IoU trung bình trên toàn bộ Validation\n# ============================================================\n# Load model tốt nhất\nbest_model = UNet().to(device)\nbest_model.load_state_dict(torch.load(f\"{working_dir}/unet_bce_best.pth\"))\nbest_model.eval()\n\nall_ious  = []\nall_dices = []\n\nwith torch.no_grad():\n    for images, masks in val_loader:\n        images = images.to(device)\n        masks  = masks.to(device)\n        preds  = best_model(images)\n        \n        # Tính IoU từng ảnh trong batch\n        for i in range(preds.size(0)):\n            iou  = compute_iou(preds[i], masks[i])\n            dice = compute_dice(preds[i], masks[i])\n            all_ious.append(iou)\n            all_dices.append(dice)\n\nmean_iou  = np.mean(all_ious)\nmean_dice = np.mean(all_dices)\nstd_iou   = np.std(all_ious)\n\nprint(\"=\" * 50)\nprint(\"BT9 — Evaluation trên toàn bộ Validation Set\")\nprint(\"=\" * 50)\nprint(f\"Số ảnh validation  : {len(all_ious)}\")\nprint(f\"Mean IoU           : {mean_iou:.4f} ± {std_iou:.4f}\")\nprint(f\"Mean Dice Score    : {mean_dice:.4f}\")\nprint(f\"Min IoU            : {min(all_ious):.4f}\")\nprint(f\"Max IoU            : {max(all_ious):.4f}\")\n\n# Histogram IoU\nplt.figure(figsize=(8, 4))\nplt.hist(all_ious, bins=50, edgecolor='black', alpha=0.7, color='steelblue')\nplt.axvline(mean_iou, color='red', linestyle='--', label=f'Mean IoU = {mean_iou:.4f}')\nplt.xlabel(\"IoU\"); plt.ylabel(\"Số ảnh\")\nplt.title(\"BT9 — Phân bố IoU trên Validation Set\")\nplt.legend()\nplt.tight_layout()\nplt.savefig(f\"{working_dir}/bt9_iou_distribution.png\", dpi=150)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-13T18:27:25.297527Z","iopub.execute_input":"2026-06-13T18:27:25.297756Z","iopub.status.idle":"2026-06-13T18:27:48.025067Z","shell.execute_reply.started":"2026-06-13T18:27:25.297725Z","shell.execute_reply":"2026-06-13T18:27:48.024161Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 11: BT10 — Hiển thị: Ảnh gốc | Ground Truth | Predicted\n# ============================================================\nnum_show = 6\nfig, axes = plt.subplots(3, num_show, figsize=(20, 10))\nfig.suptitle(\"BT10 — Ảnh gốc | Ground Truth Mask | Predicted Mask\", fontsize=14, fontweight='bold')\n\nrow_labels = [\"Ảnh gốc\", \"Ground Truth\", \"Predicted\"]\nfor r, label in enumerate(row_labels):\n    axes[r, 0].set_ylabel(label, fontsize=12, fontweight='bold')\n\n# Lấy batch đầu tiên từ val_loader\nimages, masks = next(iter(val_loader))\nimages_gpu = images.to(device)\n\nwith torch.no_grad():\n    preds = best_model(images_gpu)\n\n# Denormalize ảnh để hiển thị\nmean = torch.tensor([0.485, 0.456, 0.406]).view(3,1,1)\nstd  = torch.tensor([0.229, 0.224, 0.225]).view(3,1,1)\n\nfor i in range(num_show):\n    # Ảnh gốc (denormalize)\n    img = images[i].cpu() * std + mean\n    img = img.permute(1, 2, 0).clamp(0, 1).numpy()\n    \n    gt   = masks[i].cpu().squeeze().numpy()\n    pred = (preds[i].cpu().squeeze() > 0.5).float().numpy()\n    \n    axes[0, i].imshow(img)\n    axes[0, i].axis(\"off\")\n    \n    axes[1, i].imshow(gt, cmap=\"gray\")\n    axes[1, i].axis(\"off\")\n    \n    iou = compute_iou(preds[i].cpu(), masks[i])\n    axes[2, i].imshow(pred, cmap=\"gray\")\n    axes[2, i].set_title(f\"IoU: {iou:.3f}\", fontsize=9)\n    axes[2, i].axis(\"off\")\n\nplt.tight_layout()\nplt.savefig(f\"{working_dir}/bt10_predictions.png\", dpi=150, bbox_inches='tight')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-13T18:27:48.026433Z","iopub.execute_input":"2026-06-13T18:27:48.026817Z","iopub.status.idle":"2026-06-13T18:27:50.971661Z","shell.execute_reply.started":"2026-06-13T18:27:48.026785Z","shell.execute_reply":"2026-06-13T18:27:50.970805Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 12: BT11 — So sánh Learning Rate: 0.01 / 0.001 / 0.0001\n# ============================================================\nlearning_rates = [0.01, 0.001, 0.0001]\nlr_histories = {}\n\nfor lr in learning_rates:\n    print(f\"\\n{'='*60}\")\n    print(f\"Training với lr = {lr}\")\n    print(f\"{'='*60}\")\n    \n    model_lr = UNet().to(device)\n    optimizer_lr = optim.Adam(model_lr.parameters(), lr=lr)\n    criterion_lr = nn.BCELoss()\n    \n    history = train_model(\n        model_lr, train_loader, val_loader,\n        criterion_lr, optimizer_lr,\n        num_epochs=10,\n        save_name=f\"unet_lr{lr}\"\n    )\n    lr_histories[lr] = history\n    \n    del model_lr\n    torch.cuda.empty_cache()\n\n# Vẽ biểu đồ so sánh\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\nfig.suptitle(\"BT11 — So sánh Learning Rate\", fontsize=14, fontweight='bold')\n\nfor lr, hist in lr_histories.items():\n    axes[0].plot(hist[\"train_loss\"], label=f\"lr={lr}\")\n    axes[1].plot(hist[\"val_iou\"],    label=f\"lr={lr}\")\n\naxes[0].set_xlabel(\"Epoch\"); axes[0].set_ylabel(\"Train Loss\")\naxes[0].set_title(\"Training Loss\"); axes[0].legend()\n\naxes[1].set_xlabel(\"Epoch\"); axes[1].set_ylabel(\"IoU\")\naxes[1].set_title(\"Validation IoU\"); axes[1].legend()\n\nplt.tight_layout()\nplt.savefig(f\"{working_dir}/bt11_learning_rate.png\", dpi=150, bbox_inches='tight')\nplt.show()\n\n# Bảng tổng kết\nprint(\"\\n\" + \"=\"*50)\nprint(f\"{'LR':>10} | {'Best Val IoU':>12} | {'Final Val IoU':>14}\")\nprint(\"-\"*50)\nfor lr, hist in lr_histories.items():\n    print(f\"{lr:>10} | {max(hist['val_iou']):>12.4f} | {hist['val_iou'][-1]:>14.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-13T18:42:08.313256Z","iopub.execute_input":"2026-06-13T18:42:08.313835Z","iopub.status.idle":"2026-06-13T20:55:18.598655Z","shell.execute_reply.started":"2026-06-13T18:42:08.313798Z","shell.execute_reply":"2026-06-13T20:55:18.597596Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 13: BT12 — Thử nghiệm Batch Size: 4 / 8 / 16\n# ============================================================\nbatch_sizes = [4, 8, 16]\nbs_histories = {}\n\nfor bs in batch_sizes:\n    print(f\"\\n{'='*60}\")\n    print(f\"Training với batch_size = {bs}\")\n    print(f\"{'='*60}\")\n    \n    # Tạo DataLoader mới với batch_size khác\n    train_loader_bs = DataLoader(train_dataset, batch_size=bs, shuffle=True, num_workers=2, pin_memory=True)\n    val_loader_bs   = DataLoader(val_dataset,   batch_size=bs, shuffle=False, num_workers=2, pin_memory=True)\n    \n    model_bs = UNet().to(device)\n    optimizer_bs = optim.Adam(model_bs.parameters(), lr=0.001)\n    criterion_bs = nn.BCELoss()\n    \n    history = train_model(\n        model_bs, train_loader_bs, val_loader_bs,\n        criterion_bs, optimizer_bs,\n        num_epochs=10,\n        save_name=f\"unet_bs{bs}\"\n    )\n    bs_histories[bs] = history\n    \n    del model_bs\n    torch.cuda.empty_cache()\n\n# Vẽ biểu đồ\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\nfig.suptitle(\"BT12 — So sánh Batch Size\", fontsize=14, fontweight='bold')\n\nfor bs, hist in bs_histories.items():\n    axes[0].plot(hist[\"train_loss\"], label=f\"bs={bs}\")\n    axes[1].plot(hist[\"val_iou\"],    label=f\"bs={bs}\")\n\naxes[0].set_xlabel(\"Epoch\"); axes[0].set_ylabel(\"Train Loss\")\naxes[0].set_title(\"Training Loss\"); axes[0].legend()\n\naxes[1].set_xlabel(\"Epoch\"); axes[1].set_ylabel(\"IoU\")\naxes[1].set_title(\"Validation IoU\"); axes[1].legend()\n\nplt.tight_layout()\nplt.savefig(f\"{working_dir}/bt12_batch_size.png\", dpi=150, bbox_inches='tight')\nplt.show()\n\nprint(\"\\n\" + \"=\"*50)\nprint(f\"{'Batch Size':>10} | {'Best Val IoU':>12} | {'Final Val IoU':>14}\")\nprint(\"-\"*50)\nfor bs, hist in bs_histories.items():\n    print(f\"{bs:>10} | {max(hist['val_iou']):>12.4f} | {hist['val_iou'][-1]:>14.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T03:40:21.583201Z","iopub.execute_input":"2026-06-14T03:40:21.583503Z","iopub.status.idle":"2026-06-14T05:51:57.392119Z","shell.execute_reply.started":"2026-06-14T03:40:21.583468Z","shell.execute_reply":"2026-06-14T05:51:57.391063Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 14: BT13 — Phân tích các trường hợp IoU thấp\n# ============================================================\n# Load best model\nbest_model.load_state_dict(torch.load(working_dir + \"/unet_bce_best.pth\"))\nbest_model.eval()\n\n# Thu thập IoU từng ảnh\nper_image_ious = []\nper_image_data = []\n\nwith torch.no_grad():\n    for images, masks in val_loader:\n        images_gpu = images.to(device)\n        preds = best_model(images_gpu)\n        \n        for i in range(preds.size(0)):\n            iou = compute_iou(preds[i].cpu(), masks[i])\n            per_image_ious.append(iou)\n            per_image_data.append((images[i], masks[i], preds[i].cpu()))\n\n# Tìm top-6 ảnh IoU thấp nhất (worst cases)\nsorted_indices = np.argsort(per_image_ious)\nworst_indices = sorted_indices[:6]\n\nfig, axes = plt.subplots(3, 6, figsize=(24, 10))\nfig.suptitle(\"BT13 — 6 ảnh có IoU thấp nhất (Error Analysis)\", fontsize=14, fontweight='bold')\n\nrow_labels = [\"Ảnh gốc\", \"Ground Truth\", \"Predicted\"]\nfor r, label in enumerate(row_labels):\n    axes[r, 0].set_ylabel(label, fontsize=11, fontweight='bold')\n\nmean = torch.tensor([0.485, 0.456, 0.406]).view(3,1,1)\nstd  = torch.tensor([0.229, 0.224, 0.225]).view(3,1,1)\n\nfor col, idx in enumerate(worst_indices):\n    img_t, mask_t, pred_t = per_image_data[idx]\n    \n    img = (img_t * std + mean).permute(1,2,0).clamp(0,1).numpy()\n    gt  = mask_t.squeeze().numpy()\n    pr  = (pred_t.squeeze() > 0.5).float().numpy()\n    \n    axes[0, col].imshow(img);        axes[0, col].axis(\"off\")\n    axes[1, col].imshow(gt, cmap=\"gray\"); axes[1, col].axis(\"off\")\n    axes[2, col].imshow(pr, cmap=\"gray\"); axes[2, col].axis(\"off\")\n    axes[2, col].set_title(f\"IoU: {per_image_ious[idx]:.3f}\", fontsize=10, color='red')\n\nplt.tight_layout()\nplt.savefig(f\"{working_dir}/bt13_error_analysis.png\", dpi=150, bbox_inches='tight')\nplt.show()\n\n# Phân tích thống kê\nious_np = np.array(per_image_ious)\nprint(\"\\nPhân tích lỗi:\")\nprint(f\"  Số ảnh IoU < 0.90 : {(ious_np < 0.90).sum()}\")\nprint(f\"  Số ảnh IoU < 0.80 : {(ious_np < 0.80).sum()}\")\nprint(f\"  Số ảnh IoU < 0.70 : {(ious_np < 0.70).sum()}\")\nprint(f\"  Worst IoU         : {ious_np.min():.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-14T06:14:09.16858Z","iopub.execute_input":"2026-06-14T06:14:09.169166Z","iopub.status.idle":"2026-06-14T06:14:09.433998Z","shell.execute_reply.started":"2026-06-14T06:14:09.169137Z","shell.execute_reply":"2026-06-14T06:14:09.432988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 16: BT15 — So sánh với DeepLabV3 (pretrained ResNet50)\n# ============================================================\nfrom torchvision.models.segmentation import deeplabv3_resnet50\n\n# Load DeepLabV3 pretrained, sửa output class = 1\nmodel_deeplab = deeplabv3_resnet50(weights=\"DEFAULT\")\nmodel_deeplab.classifier[-1] = nn.Conv2d(256, 1, kernel_size=1)  # 1 class output\nmodel_deeplab.aux_classifier[-1] = nn.Conv2d(256, 1, kernel_size=1)\nmodel_deeplab = model_deeplab.to(device)\n\n# Wrapper để extract output đúng\nclass DeepLabWrapper(nn.Module):\n    def __init__(self, model):\n        super().__init__()\n        self.model = model\n    \n    def forward(self, x):\n        out = self.model(x)['out']\n        return torch.sigmoid(out)\n\nmodel_deeplab_wrapped = DeepLabWrapper(model_deeplab)\n\noptimizer_dl = optim.Adam(model_deeplab.parameters(), lr=0.0001)\ncriterion_dl = BCEDiceLoss()\n\nprint(\"=\" * 60)\nprint(\"BT15 — Training DeepLabV3 (pretrained ResNet50)\")\nprint(\"=\" * 60)\n\nhistory_deeplab = train_model(\n    model_deeplab_wrapped, train_loader, val_loader,\n    criterion_dl, optimizer_dl,\n    num_epochs=10,\n    save_name=\"deeplabv3\"\n)\n\n# --- So sánh U-Net vs DeepLabV3 ---\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\nfig.suptitle(\"BT15 — U-Net vs DeepLabV3\", fontsize=14, fontweight='bold')\n\naxes[0].plot(history_bce[\"val_iou\"],     label=\"U-Net (BCE)\")\naxes[0].plot(history_improved[\"val_iou\"], label=\"U-Net (BCE+Dice)\")\naxes[0].plot(history_deeplab[\"val_iou\"], label=\"DeepLabV3\")\naxes[0].set_xlabel(\"Epoch\"); axes[0].set_ylabel(\"IoU\")\naxes[0].set_title(\"Validation IoU\"); axes[0].legend()\n\naxes[1].plot(history_bce[\"val_dice\"],     label=\"U-Net (BCE)\")\naxes[1].plot(history_improved[\"val_dice\"], label=\"U-Net (BCE+Dice)\")\naxes[1].plot(history_deeplab[\"val_dice\"], label=\"DeepLabV3\")\naxes[1].set_xlabel(\"Epoch\"); axes[1].set_ylabel(\"Dice\")\naxes[1].set_title(\"Validation Dice Score\"); axes[1].legend()\n\nplt.tight_layout()\nplt.savefig(f\"{working_dir}/bt15_architecture_comparison.png\", dpi=150, bbox_inches='tight')\nplt.show()\n\n# Bảng tổng kết cuối cùng\nprint(\"\\n\" + \"=\" * 65)\nprint(\"BẢNG TỔNG KẾT TOÀN BỘ THÍ NGHIỆM\")\nprint(\"=\" * 65)\nprint(f\"{'Model':<25} | {'Best IoU':>10} | {'Best Dice':>10}\")\nprint(\"-\" * 65)\nprint(f\"{'U-Net + BCE':<25} | {max(history_bce['val_iou']):>10.4f} | {max(history_bce['val_dice']):>10.4f}\")\nprint(f\"{'U-Net + Dice Loss':<25} | {max(history_dice['val_iou']):>10.4f} | {max(history_dice['val_dice']):>10.4f}\")\nprint(f\"{'U-Net + BCE+Dice':<25} | {max(history_improved['val_iou']):>10.4f} | {max(history_improved['val_dice']):>10.4f}\")\nprint(f\"{'DeepLabV3 (pretrained)':<25} | {max(history_deeplab['val_iou']):>10.4f} | {max(history_deeplab['val_dice']):>10.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# CELL 17: Tổng hợp kết quả + Inference cuối cùng\n# ============================================================\n\n# Load model tốt nhất overall\nfinal_model = UNet().to(device)\nfinal_model.load_state_dict(torch.load(f\"{working_dir}/unet_improved_best.pth\"))\nfinal_model.eval()\n\n# Inference trên 8 ảnh validation → hình đẹp cho báo cáo\nfig, axes = plt.subplots(3, 8, figsize=(28, 10))\nfig.suptitle(\"Kết quả Segmentation — Mô hình tốt nhất (U-Net + BCE+Dice)\", fontsize=14, fontweight='bold')\n\nimages, masks = next(iter(val_loader))\nimages_gpu = images.to(device)\n\nwith torch.no_grad():\n    preds = final_model(images_gpu)\n\nfor i in range(8):\n    img = (images[i].cpu() * std + mean).permute(1,2,0).clamp(0,1).numpy()\n    gt  = masks[i].cpu().squeeze().numpy()\n    pr  = (preds[i].cpu().squeeze() > 0.5).float().numpy()\n    iou = compute_iou(preds[i].cpu(), masks[i])\n    \n    axes[0, i].imshow(img);              axes[0, i].axis(\"off\")\n    axes[1, i].imshow(gt, cmap=\"gray\");  axes[1, i].axis(\"off\")\n    axes[2, i].imshow(pr, cmap=\"gray\");  axes[2, i].axis(\"off\")\n    axes[2, i].set_title(f\"IoU={iou:.3f}\", fontsize=9)\n\naxes[0,0].set_ylabel(\"Input\", fontsize=11, fontweight='bold')\naxes[1,0].set_ylabel(\"Ground Truth\", fontsize=11, fontweight='bold')\naxes[2,0].set_ylabel(\"Predicted\", fontsize=11, fontweight='bold')\n\nplt.tight_layout()\nplt.savefig(f\"{working_dir}/final_results.png\", dpi=150, bbox_inches='tight')\nplt.show()\n\nprint(\"\\nTất cả output đã lưu tại /kaggle/working/\")\nprint(\"Files:\")\nfor f in sorted(glob.glob(f\"{working_dir}/*.png\")) + sorted(glob.glob(f\"{working_dir}/*.pth\")):\n    print(f\"  {Path(f).name}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}