{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":30892,"databundleVersionId":2715462},{"sourceType":"datasetVersion","sourceId":6983304,"datasetId":4002781,"databundleVersionId":7070181}],"dockerImageVersionId":30580,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# !pip install wandb --upgrade","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:02:57.192039Z","iopub.execute_input":"2026-02-23T07:02:57.192401Z","iopub.status.idle":"2026-02-23T07:02:57.199021Z","shell.execute_reply.started":"2026-02-23T07:02:57.192375Z","shell.execute_reply":"2026-02-23T07:02:57.197808Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install segmentation-models-pytorch","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:02:57.20057Z","iopub.execute_input":"2026-02-23T07:02:57.201037Z","iopub.status.idle":"2026-02-23T07:03:06.42298Z","shell.execute_reply.started":"2026-02-23T07:02:57.201006Z","shell.execute_reply":"2026-02-23T07:03:06.422128Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install torchsummary\n!pip install torchgeometry","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T07:03:06.424401Z","iopub.execute_input":"2026-02-23T07:03:06.424797Z","iopub.status.idle":"2026-02-23T07:03:24.524196Z","shell.execute_reply.started":"2026-02-23T07:03:06.424741Z","shell.execute_reply":"2026-02-23T07:03:24.523345Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport cv2\nfrom torchvision.io import read_image\nimport matplotlib.pyplot as plt\nfrom torch.utils.data import Dataset, random_split, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch.transforms import ToTensorV2\n\nfrom torchvision.transforms import ToTensor\nfrom PIL import Image\nimport os\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision \nfrom torchvision import transforms\nfrom torchinfo import summary\nimport timm\nimport segmentation_models_pytorch as smp\nimport wandb","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2026-02-23T07:03:24.527254Z","iopub.execute_input":"2026-02-23T07:03:24.527959Z","iopub.status.idle":"2026-02-23T07:03:24.535013Z","shell.execute_reply.started":"2026-02-23T07:03:24.527928Z","shell.execute_reply":"2026-02-23T07:03:24.534335Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!nvidia-smi -L","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:24.536094Z","iopub.execute_input":"2026-02-23T07:03:24.536987Z","iopub.status.idle":"2026-02-23T07:03:25.627801Z","shell.execute_reply.started":"2026-02-23T07:03:24.536962Z","shell.execute_reply":"2026-02-23T07:03:25.626673Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\ndevice","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:25.629612Z","iopub.execute_input":"2026-02-23T07:03:25.630027Z","iopub.status.idle":"2026-02-23T07:03:25.63964Z","shell.execute_reply.started":"2026-02-23T07:03:25.629971Z","shell.execute_reply":"2026-02-23T07:03:25.638807Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images_path = \"/kaggle/input/bkai-igh-neopolyp/train/train/\"\nimage_path = []\nTRAIN_DIR = '/kaggle/input/bkai-igh-neopolyp/train/train'\nfor root, dirs, files in os.walk(TRAIN_DIR):\n    for file in files:\n        path = os.path.join(root,file)\n        image_path.append(path)\n        \nlen(image_path)","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:25.640857Z","iopub.execute_input":"2026-02-23T07:03:25.641125Z","iopub.status.idle":"2026-02-23T07:03:25.911047Z","shell.execute_reply.started":"2026-02-23T07:03:25.641085Z","shell.execute_reply":"2026-02-23T07:03:25.910248Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import train_test_split\nimport albumentations as A\nfrom albumentations.pytorch.transforms import ToTensorV2\n\nclass PolypDataset(Dataset):\n    def __init__(self, image_paths, mask_paths, resize=(256, 256), transform=None):\n        self.image_paths = image_paths\n        self.mask_paths = mask_paths\n        self.resize = resize\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.image_paths)\n    \n    def read_mask(self, mask_path):\n        image = cv2.imread(mask_path)\n        image = cv2.resize(image, self.resize)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)\n\n        # Lọc màu đỏ (class 1)\n        lower_red1 = np.array([0, 100, 20])\n        upper_red1 = np.array([10, 255, 255])\n        lower_red2 = np.array([160,100,20])\n        upper_red2 = np.array([179,255,255])\n        \n        red_mask = cv2.inRange(image, lower_red1, upper_red1) + cv2.inRange(image, lower_red2, upper_red2)\n        red_mask[red_mask != 0] = 1\n\n        # Lọc màu xanh (class 2)\n        green_mask = cv2.inRange(image, (36, 25, 25), (70, 255, 255))\n        green_mask[green_mask != 0] = 2\n\n        # Gộp mask\n        full_mask = cv2.bitwise_or(red_mask, green_mask)\n        \n        # Output trả về shape (H, W) thay vì (H, W, 1) để hợp với CrossEntropyLoss\n        return full_mask.astype(np.uint8)\n\n    def __getitem__(self, idx):\n        img_path = self.image_paths[idx]\n        mask_path = self.mask_paths[idx]\n        \n        # Đọc ảnh\n        image = cv2.imread(img_path)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        image = cv2.resize(image, self.resize)\n        \n        # Đọc mask\n        mask = self.read_mask(mask_path)\n        \n        # Augmentation\n        if self.transform:\n            transformed = self.transform(image=image, mask=mask)\n            image = transformed['image'].float()\n            mask = transformed['mask'].long() # Đảm bảo mask là số nguyên dài cho CrossEntropy\n            \n        return image, mask","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T07:03:25.912552Z","iopub.execute_input":"2026-02-23T07:03:25.913057Z","iopub.status.idle":"2026-02-23T07:03:25.925513Z","shell.execute_reply.started":"2026-02-23T07:03:25.913021Z","shell.execute_reply":"2026-02-23T07:03:25.924786Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"mask_path = []\nTRAIN_MASK_DIR = '/kaggle/input/bkai-igh-neopolyp/train_gt/train_gt'\nfor root, dirs, files in os.walk(TRAIN_MASK_DIR):\n    for file in files:\n        path = os.path.join(root,file)\n        mask_path.append(path)\n        \nlen(mask_path)","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:25.926837Z","iopub.execute_input":"2026-02-23T07:03:25.927503Z","iopub.status.idle":"2026-02-23T07:03:26.16957Z","shell.execute_reply.started":"2026-02-23T07:03:25.927467Z","shell.execute_reply":"2026-02-23T07:03:26.168808Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import segmentation_models_pytorch as smp\n\nmodel = smp.DeepLabV3Plus(\n    encoder_name=\"efficientnet-b4\",     # Vẫn giữ nguyên encoder mạnh mẽ này\n    encoder_weights=\"imagenet\",         # Bắt đầu học từ bộ trọng số chuẩn\n    in_channels=3,                      # 3 kênh màu RGB\n    classes=3                           # 3 class (Background, Red, Light)\n)","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:26.17266Z","iopub.execute_input":"2026-02-23T07:03:26.173014Z","iopub.status.idle":"2026-02-23T07:03:26.731922Z","shell.execute_reply.started":"2026-02-23T07:03:26.172989Z","shell.execute_reply":"2026-02-23T07:03:26.731195Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\ntrain_transformation = A.Compose([\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.RandomRotate90(p=0.5),\n    \n    A.ShiftScaleRotate(shift_limit=0.0625, scale_limit=0.15, rotate_limit=45, p=0.5),\n    \n    A.OneOf([\n        A.GridDistortion(num_steps=5, distort_limit=0.05, p=1.0),\n        A.ElasticTransform(alpha=1, sigma=50, alpha_affine=50, p=1.0)\n    ], p=0.3),\n    \n    A.OneOf([\n        A.GaussianBlur(blur_limit=(3, 5), p=1.0),\n        A.MotionBlur(blur_limit=5, p=1.0),\n        A.GaussNoise(var_limit=(10.0, 50.0), p=1.0),\n    ], p=0.3),\n    \n    A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n    ToTensorV2(),\n])\nval_transformation = A.Compose([\n    A.Normalize(mean=(0.485, 0.456, 0.406),std=(0.229, 0.224, 0.225)),\n    ToTensorV2(),\n])","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:26.733051Z","iopub.execute_input":"2026-02-23T07:03:26.733382Z","iopub.status.idle":"2026-02-23T07:03:26.743043Z","shell.execute_reply.started":"2026-02-23T07:03:26.733356Z","shell.execute_reply":"2026-02-23T07:03:26.742203Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_DIR = '/kaggle/input/bkai-igh-neopolyp/train/train'\nTRAIN_MASK_DIR = '/kaggle/input/bkai-igh-neopolyp/train_gt/train_gt'\n\n# Lấy danh sách tên file và ghép thành đường dẫn tuyệt đối\nimage_filenames = sorted(os.listdir(TRAIN_DIR))\nall_image_paths = [os.path.join(TRAIN_DIR, f) for f in image_filenames]\nall_mask_paths = [os.path.join(TRAIN_MASK_DIR, f) for f in image_filenames]\n\n# Chia Train/Val (80/20)\ntrain_img_paths, val_img_paths, train_mask_paths, val_mask_paths = train_test_split(\n    all_image_paths, all_mask_paths, test_size=0.2, random_state=42\n)\n\n# Transforms (Giữ nguyên của bạn)\ntrain_transformation = A.Compose([\n    A.HorizontalFlip(p=0.4),\n    A.VerticalFlip(p=0.4),\n    A.RandomGamma(gamma_limit=(70, 130), p=0.2),\n    A.RGBShift(p=0.3, r_shift_limit=10, g_shift_limit=10, b_shift_limit=10),\n    A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n    ToTensorV2(),\n])\n\nval_transformation = A.Compose([\n    A.Normalize(mean=(0.485, 0.456, 0.406), std=(0.229, 0.224, 0.225)),\n    ToTensorV2(),\n])\n\n# Khởi tạo DataLoader\nbatch_size = 4\ntrain_dataset = PolypDataset(train_img_paths, train_mask_paths, transform=train_transformation)\nval_dataset = PolypDataset(val_img_paths, val_mask_paths, transform=val_transformation)\n\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=batch_size, shuffle=False, num_workers=2) # Val không cần shuffle","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:26.744105Z","iopub.execute_input":"2026-02-23T07:03:26.744399Z","iopub.status.idle":"2026-02-23T07:03:26.760876Z","shell.execute_reply.started":"2026-02-23T07:03:26.744375Z","shell.execute_reply":"2026-02-23T07:03:26.759905Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nimport torch\n\n# Định nghĩa hàm để đảo ngược quá trình Normalize (denormalize)\ndef denormalize(tensor):\n    mean = np.array([0.485, 0.456, 0.406])\n    std = np.array([0.229, 0.224, 0.225])\n    \n    # Chuyển tensor từ (C, H, W) sang (H, W, C)\n    image = tensor.permute(1, 2, 0).cpu().numpy()\n    \n    # Denormalize: image = image * std + mean\n    image = std * image + mean\n    image = np.clip(image, 0, 1) # Cắt giá trị về khoảng [0, 1] để pyplot không báo lỗi\n    return image\n\n# Lấy 1 batch từ train_loader\nimages, masks = next(iter(train_loader))\n\n# Chọn 2 ảnh đầu tiên trong batch để demo\nimg1, img2 = images[0], images[1]\nmask1, mask2 = masks[0], masks[1]\n\n# Đưa mask về dạng 2D chuẩn (H, W) để vẽ\nmask1_2d = mask1.squeeze().cpu().numpy()\nmask2_2d = mask2.squeeze().cpu().numpy()\n\nprint(f\"Mask shape: {mask1_2d.shape}\") # Sẽ in ra (256, 256)\nprint(f\"Image shape: {img1.permute(1, 2, 0).shape}\") # (256, 256, 3)\n\n# Vẽ hình (2 cột x 2 hàng)\nfig, axes = plt.subplots(2, 2, figsize=(10, 10))\n\n# Vẽ ảnh gốc ở hàng 1\naxes[0, 0].imshow(denormalize(img1))\n\naxes[0, 1].imshow(denormalize(img2))\n\n# Vẽ segmentation mask tương ứng ở hàng 2\naxes[1, 0].imshow(mask1_2d)\n\naxes[1, 1].imshow(mask2_2d)\n\n# Tắt hiển thị trục tọa độ cho đẹp\nfor ax in axes.flat:\n    ax.axis('off')\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T07:03:26.762199Z","iopub.execute_input":"2026-02-23T07:03:26.762536Z","iopub.status.idle":"2026-02-23T07:03:28.007198Z","shell.execute_reply.started":"2026-02-23T07:03:26.762511Z","shell.execute_reply":"2026-02-23T07:03:28.006141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"learning_rate = 0.0001\n# Thêm weight_decay để giảm overfitting (L2 Regularization)\noptimizer = torch.optim.Adam(model.parameters(), lr=learning_rate, weight_decay=1e-4)\n\nscheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(\n    optimizer, mode='min', factor=0.5, patience=5, min_lr=1e-6\n)","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:28.008474Z","iopub.execute_input":"2026-02-23T07:03:28.008736Z","iopub.status.idle":"2026-02-23T07:03:28.021825Z","shell.execute_reply.started":"2026-02-23T07:03:28.008712Z","shell.execute_reply":"2026-02-23T07:03:28.020772Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"color_dict= {0: (0, 0, 0),\n             1: (255, 0, 0),\n             2: (0, 255, 0)}\ndef mask_to_rgb(mask, color_dict):\n    output = np.zeros((mask.shape[0], mask.shape[1], 3))\n\n    for k in color_dict.keys():\n        output[mask==k] = color_dict[k]\n\n    return np.uint8(output)    ","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:28.023225Z","iopub.execute_input":"2026-02-23T07:03:28.023549Z","iopub.status.idle":"2026-02-23T07:03:28.030804Z","shell.execute_reply.started":"2026-02-23T07:03:28.023524Z","shell.execute_reply":"2026-02-23T07:03:28.02991Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import wandb\nwandb.login(\n    key=\"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\",\n    relogin=True \n)\nwandb.init(\n    project=\"PolypSegment2\"\n)","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:28.032029Z","iopub.execute_input":"2026-02-23T07:03:28.032349Z","iopub.status.idle":"2026-02-23T07:03:30.780534Z","shell.execute_reply.started":"2026-02-23T07:03:28.032312Z","shell.execute_reply":"2026-02-23T07:03:30.779602Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install tqdm\n","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:30.781594Z","iopub.execute_input":"2026-02-23T07:03:30.781871Z","iopub.status.idle":"2026-02-23T07:03:39.673076Z","shell.execute_reply.started":"2026-02-23T07:03:30.781847Z","shell.execute_reply":"2026-02-23T07:03:39.672006Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport torch\n\ndef calculate_iou_per_class(preds, labels, num_classes=3):\n    preds = torch.argmax(preds, dim=1)\n    ious = []\n    \n    for cls in range(num_classes):\n        pred_inds = preds == cls\n        target_inds = labels == cls\n        \n        intersection = (pred_inds & target_inds).long().sum().item()\n        union = pred_inds.long().sum().item() + target_inds.long().sum().item() - intersection\n        \n        if union == 0:\n            ious.append(np.nan)\n        else:\n            ious.append(float(intersection) / float(max(union, 1)))\n            \n    return ious","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T07:03:39.674825Z","iopub.execute_input":"2026-02-23T07:03:39.675198Z","iopub.status.idle":"2026-02-23T07:03:39.682054Z","shell.execute_reply.started":"2026-02-23T07:03:39.675162Z","shell.execute_reply":"2026-02-23T07:03:39.681232Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tqdm import tqdm\nimport math\n\nnum_epochs = 100\ndevice = torch.device('cuda' if torch.cuda.is_available() else \"cpu\")\nmodel.to(device)\n\nfocal_loss = smp.losses.FocalLoss(mode='multiclass', gamma=2.0)\ndice_loss = smp.losses.DiceLoss(mode='multiclass') \nbest_val_loss = float('inf')\n\nepoch_bar = tqdm(total=num_epochs, desc='Total Progress')\n\nfor epoch in range(num_epochs):\n    # --- TRAINING ---\n    model.train()\n    train_loss = 0\n    train_mIoU_total = 0  # Biến lưu IoU tổng\n    \n    train_iou_red, train_count_red = 0.0, 0\n    train_iou_green, train_count_green = 0.0, 0\n    \n    for images, labels in train_loader:\n        images, labels = images.to(device), labels.to(device)\n\n        outputs = model(images)\n        loss = focal_loss(outputs, labels) + dice_loss(outputs, labels)\n        \n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        \n        train_loss += loss.item()\n        \n        # Tính IoU cho batch\n        batch_ious = calculate_iou_per_class(outputs, labels)\n        \n        # 1. Tính IoU tổng của batch (Trung bình của các class hợp lệ)\n        valid_ious = [iou for iou in batch_ious if not np.isnan(iou)]\n        batch_mIoU = np.mean(valid_ious) if len(valid_ious) > 0 else 0.0\n        train_mIoU_total += batch_mIoU\n        \n        # 2. Bóc tách Class 1: Đỏ (Neoplastic)\n        if not np.isnan(batch_ious[1]):\n            train_iou_red += batch_ious[1]\n            train_count_red += 1\n            \n        # 3. Bóc tách Class 2: Xanh (Non-neoplastic)\n        if not np.isnan(batch_ious[2]):\n            train_iou_green += batch_ious[2]\n            train_count_green += 1\n            \n    avg_train_loss = train_loss / len(train_loader)\n    avg_train_mIoU = train_mIoU_total / len(train_loader)  # IoU tổng trung bình\n    avg_train_iou_red = train_iou_red / train_count_red if train_count_red > 0 else 0.0\n    avg_train_iou_green = train_iou_green / train_count_green if train_count_green > 0 else 0.0\n\n    # --- VALIDATION ---\n    model.eval()\n    val_loss = 0\n    val_mIoU_total = 0  # Biến lưu IoU tổng val\n    \n    val_iou_red, val_count_red = 0.0, 0\n    val_iou_green, val_count_green = 0.0, 0\n    \n    with torch.no_grad():\n        for images, labels in val_loader:\n            images, labels = images.to(device), labels.to(device)\n            \n            outputs = model(images)\n            loss = focal_loss(outputs, labels) + dice_loss(outputs, labels)\n            \n            val_loss += loss.item()\n            \n            batch_ious = calculate_iou_per_class(outputs, labels)\n            \n            # Tính IoU tổng\n            valid_ious = [iou for iou in batch_ious if not np.isnan(iou)]\n            batch_mIoU = np.mean(valid_ious) if len(valid_ious) > 0 else 0.0\n            val_mIoU_total += batch_mIoU\n            \n            if not np.isnan(batch_ious[1]):\n                val_iou_red += batch_ious[1]\n                val_count_red += 1\n                \n            if not np.isnan(batch_ious[2]):\n                val_iou_green += batch_ious[2]\n                val_count_green += 1\n                \n    avg_val_loss = val_loss / len(val_loader)\n    avg_val_mIoU = val_mIoU_total / len(val_loader)  # IoU tổng trung bình val\n    avg_val_iou_red = val_iou_red / val_count_red if val_count_red > 0 else 0.0\n    avg_val_iou_green = val_iou_green / val_count_green if val_count_green > 0 else 0.0\n\n    # --- LẤY LEARNING RATE HIỆN TẠI ---\n    current_lr = optimizer.param_groups[0]['lr']\n\n    # In ra terminal\n    print(f\"Epoch [{epoch+1}/{num_epochs}] LR: {current_lr:.6f}\\n\"\n          f\"  Train -> Loss: {avg_train_loss:.4f} | mIoU: {avg_train_mIoU:.4f} | Red: {avg_train_iou_red:.4f} | Light: {avg_train_iou_green:.4f}\\n\"\n          f\"  Val   -> Loss: {avg_val_loss:.4f} | mIoU: {avg_val_mIoU:.4f} | Red: {avg_val_iou_red:.4f} | Light: {avg_val_iou_green:.4f}\")\n    \n    # Save best model\n    if avg_val_loss < best_val_loss:\n        best_val_loss = avg_val_loss\n        checkpoint = { \n            'epoch': epoch,\n            'model': model.state_dict(),\n            'optimizer': optimizer.state_dict(),\n            'val_loss': avg_val_loss,\n            'val_mIoU': avg_val_mIoU,\n            'val_iou_red': avg_val_iou_red,\n            'val_iou_green': avg_val_iou_green\n        }\n        torch.save(checkpoint, 'model_best.pth')\n        print(\"  --> Saved Best Model!\")\n        \n    scheduler.step(avg_val_loss)\n    epoch_bar.update(1)\n    \n    # Log to WandB\n    wandb.log({\n        'Learning_Rate': current_lr,\n        'Train_loss': avg_train_loss,\n        'Train_mIoU': avg_train_mIoU,\n        'Train_IoU_Red': avg_train_iou_red,\n        'Train_IoU_Green': avg_train_iou_green,\n        'Val_loss': avg_val_loss,\n        'Val_mIoU': avg_val_mIoU,\n        'Val_IoU_Red': avg_val_iou_red,\n        'Val_IoU_Green': avg_val_iou_green\n    })\n\nepoch_bar.close()","metadata":{"execution":{"iopub.status.busy":"2026-02-23T07:03:39.683829Z","iopub.execute_input":"2026-02-23T07:03:39.684222Z","iopub.status.idle":"2026-02-23T08:00:38.997731Z","shell.execute_reply.started":"2026-02-23T07:03:39.684188Z","shell.execute_reply":"2026-02-23T08:00:38.99658Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"checkpoint = torch.load('/kaggle/input/unet-model/model.pth')\nmodel.load_state_dict(checkpoint['model'])\ndevice = torch.device('cuda' if torch.cuda.is_available() else \"cpu\")\nmodel.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-23T08:00:38.999282Z","iopub.execute_input":"2026-02-23T08:00:38.999562Z","iopub.status.idle":"2026-02-23T08:00:44.161627Z","shell.execute_reply.started":"2026-02-23T08:00:38.999538Z","shell.execute_reply":"2026-02-23T08:00:44.159086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!mkdir prediction","metadata":{"execution":{"iopub.status.busy":"2026-02-23T08:00:53.575302Z","iopub.execute_input":"2026-02-23T08:00:53.575639Z","iopub.status.idle":"2026-02-23T08:00:54.599227Z","shell.execute_reply.started":"2026-02-23T08:00:53.57561Z","shell.execute_reply":"2026-02-23T08:00:54.597867Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\nfor i in os.listdir(\"/kaggle/input/bkai-igh-neopolyp/test/test\"):\n    img_path = os.path.join(\"/kaggle/input/bkai-igh-neopolyp/test/test\", i)\n    ori_img = cv2.imread(img_path)\n    ori_img = cv2.cvtColor(ori_img, cv2.COLOR_BGR2RGB)\n    ori_w = ori_img.shape[0]\n    ori_h = ori_img.shape[1]\n    img = cv2.resize(ori_img, (256, 256))\n    transformed = val_transformation(image=img)\n    input_img = transformed[\"image\"]\n    input_img = input_img.unsqueeze(0).to(device)\n    with torch.no_grad():\n        output_mask = model.forward(input_img).squeeze(0).cpu().numpy().transpose(1,2,0)\n    mask = cv2.resize(output_mask, (ori_h, ori_w))\n    mask = np.argmax(mask, axis=2)\n    mask_rgb = mask_to_rgb(mask, color_dict)\n    mask_rgb = cv2.cvtColor(mask_rgb, cv2.COLOR_RGB2BGR)\n    cv2.imwrite(\"prediction/{}\".format(i), mask_rgb) ","metadata":{"execution":{"iopub.status.busy":"2026-02-23T08:00:58.630323Z","iopub.execute_input":"2026-02-23T08:00:58.63067Z","iopub.status.idle":"2026-02-23T08:01:28.38041Z","shell.execute_reply.started":"2026-02-23T08:00:58.63064Z","shell.execute_reply":"2026-02-23T08:01:28.379576Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def rle_to_string(runs):\n    return ' '.join(str(x) for x in runs)\n\ndef rle_encode_one_mask(mask):\n    pixels = mask.flatten()\n    pixels[pixels > 225] = 255\n    pixels[pixels <= 225] = 0\n    use_padding = False\n    if pixels[0] or pixels[-1]:\n        use_padding = True\n        pixel_padded = np.zeros([len(pixels) + 2], dtype=pixels.dtype)\n        pixel_padded[1:-1] = pixels\n        pixels = pixel_padded\n    rle = np.where(pixels[1:] != pixels[:-1])[0] + 2\n    if use_padding:\n        rle = rle - 1\n    rle[1::2] = rle[1::2] - rle[:-1:2]\n    \n    return rle_to_string(rle)\n\ndef rle2mask(mask_rle, shape=(3,3)):\n    s = mask_rle.split()\n    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]\n    starts -= 1\n    ends = starts + lengths\n    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)\n    for lo, hi in zip(starts, ends):\n        img[lo:hi] = 1\n    return img.reshape(shape).T\n\ndef mask2string(dir):\n    strings = []\n    ids = []\n    ws, hs = [[] for i in range(2)]\n    for image_id in os.listdir(dir):\n        id = image_id.split('.')[0]\n        path = os.path.join(dir, image_id)\n        print(path)\n        img = cv2.imread(path)[:,:,::-1]\n        h, w = img.shape[0], img.shape[1]\n        for channel in range(2):\n            ws.append(w)\n            hs.append(h)\n            ids.append(f'{id}_{channel}')\n            string = rle_encode_one_mask(img[:,:,channel])\n            strings.append(string)\n    r = {\n        'ids': ids,\n        'strings': strings,\n    }\n    return r\n\n\nMASK_DIR_PATH = '/kaggle/working/prediction'\ndir = MASK_DIR_PATH\nres = mask2string(dir)\ndf = pd.DataFrame(columns=['Id', 'Expected'])\ndf['Id'] = res['ids']\ndf['Expected'] = res['strings']\n\ndf.to_csv(r'output.csv', index=False)","metadata":{"execution":{"iopub.status.busy":"2026-02-23T08:01:38.351947Z","iopub.execute_input":"2026-02-23T08:01:38.352293Z","iopub.status.idle":"2026-02-23T08:01:41.816961Z","shell.execute_reply.started":"2026-02-23T08:01:38.35227Z","shell.execute_reply":"2026-02-23T08:01:41.815269Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"**Infer**","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nimport torch\n\ndef visualize_validation_results(model, val_loader, device, num_samples=3, save_path=\"validation_viz.png\"):\n    # Đưa model về chế độ eval\n    model.eval()\n    \n    # Lấy 1 batch từ tập validation\n    images, true_masks = next(iter(val_loader))\n    images = images.to(device)\n    \n    # Dự đoán (Inference)\n    with torch.no_grad():\n        outputs = model(images)\n        # Lấy class có xác suất cao nhất (B, H, W)\n        preds = torch.argmax(outputs, dim=1).cpu().numpy()\n        \n    # Đưa ảnh và nhãn thật về CPU để vẽ\n    images = images.cpu()\n    true_masks = true_masks.cpu()\n    \n    # Khởi tạo biểu đồ\n    fig, axes = plt.subplots(num_samples, 3, figsize=(12, 4 * num_samples))\n    fig.suptitle('Infer Result', fontsize=16)\n    \n    for i in range(num_samples):\n        # 1. Xử lý Ảnh gốc (Denormalize)\n        img = images[i].permute(1, 2, 0).numpy()\n        mean = np.array([0.485, 0.456, 0.406])\n        std = np.array([0.229, 0.224, 0.225])\n        img = std * img + mean\n        img = np.clip(img, 0, 1) # Cắt gọt tránh lỗi hiển thị\n        \n        # 2. Xử lý Nhãn thật (Loại bỏ chiều channel thừa nếu có)\n        true_mask = true_masks[i].squeeze().numpy()\n        \n        # 3. Lấy Mask dự đoán\n        pred_mask = preds[i]\n        \n        # --- BẮT ĐẦU VẼ ---\n        # Cột 1: Ảnh gốc\n        axes[i, 0].imshow(img)\n        axes[i, 0].set_title(f\"Sample {i+1}\")\n        axes[i, 0].axis('off')\n        \n        # Cột 2: Nhãn thật\n        axes[i, 1].imshow(true_mask, vmin=0, vmax=2)\n        axes[i, 1].set_title(f\"Sample {i+1}: Mask Groundtruth\")\n        axes[i, 1].axis('off')\n        \n        # Cột 3: Nhãn dự đoán\n        axes[i, 2].imshow(pred_mask, vmin=0, vmax=2)\n        axes[i, 2].set_title(f\"Sample {i+1}: Mask Predict\")\n        axes[i, 2].axis('off')\n        \n    plt.tight_layout()\n    \n    # LƯU ẢNH TRƯỚC KHI SHOW\n    if save_path:\n        plt.savefig(save_path, bbox_inches='tight', dpi=300)\n        print(f\"Đã lưu biểu đồ tại: {save_path}\")\n        \n    plt.show()\n\n# Gọi hàm và lưu thành file \"val_result_epoch_250.png\"\nvisualize_validation_results(model, val_loader, device, num_samples=3, save_path=\"val_result_epoch_250.png\")","metadata":{"execution":{"iopub.status.busy":"2026-02-23T08:08:52.676334Z","iopub.execute_input":"2026-02-23T08:08:52.676678Z","iopub.status.idle":"2026-02-23T08:08:56.395932Z","shell.execute_reply.started":"2026-02-23T08:08:52.676649Z","shell.execute_reply":"2026-02-23T08:08:56.394877Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# optimizer.load_state_dict(checkpoint['optimizer'])","metadata":{"execution":{"iopub.status.busy":"2026-02-23T08:00:44.171038Z","iopub.status.idle":"2026-02-23T08:00:44.171509Z","shell.execute_reply.started":"2026-02-23T08:00:44.171268Z","shell.execute_reply":"2026-02-23T08:00:44.171292Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# from collections import OrderedDict\n# new_state_dict = OrderedDict()\n# for k, v in checkpoint['model'].items():\n#     name = k[7:] # remove `module.`\n#     new_state_dict[name] = v\n# # load params\n# model.load_state_dict(new_state_dict)","metadata":{"execution":{"iopub.status.busy":"2026-02-23T08:00:44.173063Z","iopub.status.idle":"2026-02-23T08:00:44.173364Z","shell.execute_reply.started":"2026-02-23T08:00:44.173217Z","shell.execute_reply":"2026-02-23T08:00:44.173231Z"},"trusted":true},"outputs":[],"execution_count":null}]}