{"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":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":10338,"databundleVersionId":862042}],"dockerImageVersionId":31329,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Khai báo thư viện và Cấu hình","metadata":{}},{"cell_type":"code","source":"import os\nimport pydicom\nimport cv2\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\n# Cấu hình hệ thống\nclass Config:\n    DATA_DIR = '/kaggle/input/competitions/rsna-pneumonia-detection-challenge'\n    IMG_DIR = os.path.join(DATA_DIR, 'stage_2_train_images')\n    CSV_PATH = os.path.join(DATA_DIR, 'stage_2_train_labels.csv')\n    IMG_SIZE = 256  # Resize để chạy nhanh và tiết kiệm RAM\n    BATCH_SIZE = 4\n    SEED = 42\n    DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n# Cố định Seed và tối ưu hóa GPU\nnp.random.seed(Config.SEED)\ntorch.manual_seed(Config.SEED)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed(Config.SEED)\n    torch.backends.cudnn.benchmark = True\n\nprint(f\"🚀 Đang chạy trên: {Config.DEVICE}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Phân tích dữ liệu","metadata":{}},{"cell_type":"code","source":"def run_eda():\n    df = pd.read_csv(Config.CSV_PATH)\n    print(f\"Tổng số dòng trong file nhãn: {len(df)}\")\n    \n    plt.figure(figsize=(12, 5))\n    plt.subplot(1, 2, 1)\n    sns.countplot(x='Target', data=df, hue='Target', palette='viridis', legend=False)\n    plt.title(\"Phân bổ nhãn (0: Bình thường, 1: Viêm phổi)\")\n    \n    plt.subplot(1, 2, 2)\n    df_positive = df[df['Target'] == 1]\n    df_positive.groupby('patientId').size().value_counts().plot(kind='bar', color='orange')\n    plt.title(\"Số lượng ổ viêm trên một bệnh nhân\")\n    plt.show()\n\nrun_eda()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Xây dựng Pipeline Dataset","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nclass RSNADataset(Dataset):\n    def __init__(self, df, transform=None):\n        # Gom nhóm theo bệnh nhân vì một người có thể có nhiều ổ viêm\n        self.df_grouped = df.groupby('patientId').agg({\n            'x': list, 'y': list, 'width': list, 'height': list, 'Target': 'first'\n        }).reset_index()\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.df_grouped)\n\n    def __getitem__(self, idx):\n        row = self.df_grouped.iloc[idx]\n        pid = row['patientId']\n        img_path = os.path.join(Config.IMG_DIR, f\"{pid}.dcm\")\n        \n        # Đọc DICOM\n        ds = pydicom.dcmread(img_path)\n        img = ds.pixel_array.astype(np.float32)\n        img = (img - img.min()) / (img.max() - img.min() + 1e-6) # Normalize 0-1\n\n        # Tạo Mask (1024x1024 theo gốc của RSNA)\n        mask = np.zeros((1024, 1024), dtype=np.float32)\n        if row['Target'] == 1:\n            for x, y, w, h in zip(row['x'], row['y'], row['width'], row['height']):\n                if not np.isnan(x):\n                    mask[int(y):int(y+h), int(x):int(x+w)] = 1.0\n\n        if self.transform:\n            augmented = self.transform(image=img, mask=mask)\n            img, mask = augmented['image'], augmented['mask']\n\n        return img, mask.unsqueeze(0) if mask.ndim == 2 else mask\n\n# Augmentation\ndata_transforms = A.Compose([\n    A.Resize(Config.IMG_SIZE, Config.IMG_SIZE),\n    A.HorizontalFlip(p=0.5),\n    A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.1, rotate_limit=15, p=0.3),\n    ToTensorV2()\n])\n\nfull_df = pd.read_csv(Config.CSV_PATH)\ntrain_df, val_df = train_test_split(full_df, test_size=0.2, random_state=Config.SEED)\n\ntrain_loader = DataLoader(RSNADataset(train_df, data_transforms), batch_size=Config.BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader = DataLoader(RSNADataset(val_df, data_transforms), batch_size=Config.BATCH_SIZE, shuffle=False, num_workers=2)\n\nprint(\"✅ Pipeline dữ liệu đã sẵn sàng!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Kiểm tra kết quả trực quan","metadata":{}},{"cell_type":"code","source":"def visualize_data(loader):\n    batch_img, batch_mask = next(iter(loader))\n    plt.figure(figsize=(16, 8))\n    \n    num_samples = min(4, len(batch_img))  \n    \n    for i in range(num_samples):\n        img = batch_img[i].squeeze().cpu().numpy()   # 🔥 thêm .cpu()\n        mask = batch_mask[i].squeeze().cpu().numpy() # 🔥 thêm .cpu()\n        \n        plt.subplot(1, num_samples, i+1)\n        plt.imshow(img, cmap='gray')\n        plt.imshow(mask, alpha=0.3, cmap='Reds')\n        plt.title(f\"Mẫu bệnh nhân {i+1}\")\n        plt.axis('off')\n        \n    plt.tight_layout()\n    plt.show()\n\n\n# Gọi hàm\nvisualize_data(train_loader)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Định nghĩa kiến trúc SRGAN","metadata":{}},{"cell_type":"code","source":"import torch.nn as nn\nimport torch.optim as optim\nfrom skimage.metrics import peak_signal_noise_ratio as psnr_metric\n\n# ===== Residual Block =====\nclass ResidualBlock(nn.Module):\n    def __init__(self, n_feat):\n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Conv2d(n_feat, n_feat, 3, 1, 1),\n            nn.BatchNorm2d(n_feat),\n            nn.PReLU(),\n            nn.Conv2d(n_feat, n_feat, 3, 1, 1),\n            nn.BatchNorm2d(n_feat)\n        )\n    def forward(self, x):\n        return x + self.block(x)\n\n# ===== Generator =====\nclass SRGenerator(nn.Module):\n    def __init__(self, n_res_blocks=8):   # 🔥 giảm từ 16 → 8\n        super().__init__()\n        self.initial = nn.Sequential(\n            nn.Conv2d(1, 64, 9, 1, 4),\n            nn.PReLU()\n        )\n        self.res_blocks = nn.Sequential(*[ResidualBlock(64) for _ in range(n_res_blocks)])\n        self.upsample = nn.Sequential(\n            nn.Conv2d(64, 256, 3, 1, 1),\n            nn.PixelShuffle(2),\n            nn.PReLU(),\n            nn.Conv2d(64, 1, 9, 1, 4),\n            nn.Sigmoid()   # 🔥 thay Tanh\n        )\n\n    def forward(self, x):\n        x = self.initial(x)\n        res = self.res_blocks(x)\n        return self.upsample(x + res)\n\n# ===== Discriminator =====\nclass SRDiscriminator(nn.Module):\n    def __init__(self):\n        super().__init__()\n\n        def d_block(in_f, out_f, stride):\n            return nn.Sequential(\n                nn.Conv2d(in_f, out_f, 3, stride, 1),\n                nn.BatchNorm2d(out_f),\n                nn.LeakyReLU(0.2, inplace=True)\n            )\n\n        self.model = nn.Sequential(\n            nn.Conv2d(1, 64, 3, 1, 1),\n            nn.LeakyReLU(0.2),\n\n            d_block(64, 64, 2),\n            d_block(64, 128, 1),\n            d_block(128, 128, 2),\n            d_block(128, 256, 1),\n            d_block(256, 256, 2),\n\n            nn.AdaptiveAvgPool2d(1),\n            nn.Flatten(),\n            nn.Linear(256, 512),\n            nn.LeakyReLU(0.2),\n            nn.Linear(512, 1)   # 🔥 bỏ sigmoid\n        )\n\n    def forward(self, x):\n        return self.model(x)\n\n# ===== INIT =====\nnetG = SRGenerator().to(Config.DEVICE)\nnetD = SRDiscriminator().to(Config.DEVICE)\n\noptimizer_G = optim.Adam(netG.parameters(), lr=1e-4)\noptimizer_D = optim.Adam(netD.parameters(), lr=1e-4)\n\ncriterion_pix = nn.MSELoss()\ncriterion_gan = nn.BCEWithLogitsLoss()   # 🔥 ổn định hơn\n\nprint(\"✅ GAN đã được tối ưu!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Vòng lặp huấn luyện GAN","metadata":{}},{"cell_type":"code","source":"from torch.cuda.amp import autocast, GradScaler\n\nscaler = torch.amp.GradScaler('cuda')\n\ndef train_srgan(epochs=10):\n    for epoch in range(epochs):\n        psnr_epoch = []\n        d_loss_epoch = 0\n        g_loss_epoch = 0\n        \n        for i, (imgs_hr, _) in enumerate(train_loader):\n            imgs_hr = imgs_hr.to(Config.DEVICE)\n\n            # tạo ảnh LR\n            imgs_lr = nn.functional.interpolate(\n                imgs_hr, scale_factor=0.5, mode='bilinear'\n            )\n\n            batch_size = imgs_hr.size(0)\n            real_label = torch.ones(batch_size, 1, device=Config.DEVICE)\n            fake_label = torch.zeros(batch_size, 1, device=Config.DEVICE)\n\n            # ===== TRAIN GENERATOR =====\n            optimizer_G.zero_grad()\n\n            with autocast():\n                imgs_sr = netG(imgs_lr)\n                loss_adv = criterion_gan(netD(imgs_sr), real_label)\n                loss_content = criterion_pix(imgs_sr, imgs_hr)\n                loss_G = loss_content + 0.0003 * loss_adv\n\n            scaler.scale(loss_G).backward()\n            scaler.step(optimizer_G)\n\n            g_loss_epoch += loss_G.item()\n\n            # ===== TRAIN DISCRIMINATOR (GIẢM TẦN SUẤT) =====\n            if i % 2 == 0:\n                optimizer_D.zero_grad()\n\n                with autocast():\n                    loss_real = criterion_gan(netD(imgs_hr), real_label)\n                    loss_fake = criterion_gan(netD(imgs_sr.detach()), fake_label)\n                    loss_D = (loss_real + loss_fake) / 2\n\n                scaler.scale(loss_D).backward()\n                scaler.step(optimizer_D)\n\n                d_loss_epoch += loss_D.item()\n\n            scaler.update()\n\n            # ===== PSNR =====\n            hr_np = imgs_hr[0].detach().cpu().numpy()\n            sr_np = imgs_sr[0].detach().cpu().numpy()\n            psnr_val = psnr_metric(hr_np, sr_np, data_range=1)\n            psnr_epoch.append(psnr_val)\n\n        print(f\"Epoch [{epoch+1}/{epochs}] | Loss_D: {d_loss_epoch/len(train_loader):.4f} | Loss_G: {g_loss_epoch/len(train_loader):.4f} | PSNR: {np.mean(psnr_epoch):.2f}dB\")\n\n\n# RUN\ntrain_srgan(epochs=10)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Bước kiểm tra","metadata":{}},{"cell_type":"code","source":"def check_gan_result():\n    netG.eval()\n\n    imgs, _ = next(iter(train_loader))\n    imgs = imgs.to(Config.DEVICE)  # 🔥 thêm dòng này\n    \n    # tạo ảnh LR\n    imgs_lr = nn.functional.interpolate(\n        imgs, scale_factor=0.5, mode='bilinear'\n    )\n    \n    with torch.no_grad():\n        sr_img = netG(imgs_lr)\n    \n    plt.figure(figsize=(10, 5))\n    \n    plt.subplot(1, 2, 1)\n    plt.imshow(imgs_lr[0].cpu().squeeze(), cmap='gray')\n    plt.title(\"Ảnh chất lượng thấp (LR)\")\n    plt.axis('off')\n    \n    plt.subplot(1, 2, 2)\n    plt.imshow(sr_img[0].cpu().squeeze(), cmap='gray')\n    plt.title(\"Ảnh nâng cao qua GAN (SR)\")\n    plt.axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\n\n# RUN\ncheck_gan_result()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Attention U-Net","metadata":{}},{"cell_type":"code","source":"# Cấu trúc bổ trợ cho Attention U-Net\nclass DoubleConv(nn.Module):\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),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_ch, out_ch, 3, padding=1),\n            nn.BatchNorm2d(out_ch),\n            nn.ReLU(inplace=True)\n        )\n    def forward(self, x):\n        return self.conv(x)\n\nclass AttentionGate(nn.Module):\n    def __init__(self, F_g, F_l, F_int):\n        super().__init__()\n        self.W_g = nn.Sequential(\n            nn.Conv2d(F_g, F_int, kernel_size=1, stride=1, padding=0, bias=True),\n            nn.BatchNorm2d(F_int)\n        )\n        self.W_x = nn.Sequential(\n            nn.Conv2d(F_l, F_int, kernel_size=1, stride=1, padding=0, bias=True),\n            nn.BatchNorm2d(F_int)\n        )\n        self.psi = nn.Sequential(\n            nn.Conv2d(F_int, 1, kernel_size=1, stride=1, padding=0, bias=True),\n            nn.BatchNorm2d(1),\n            nn.Sigmoid()\n        )\n        self.relu = nn.ReLU(inplace=True)\n\n    def forward(self, g, x):\n        g1 = self.W_g(g)\n        x1 = self.W_x(x)\n        psi = self.relu(g1 + x1)\n        psi = self.psi(psi)\n        return x * psi\n\n# Model chính \nclass AttentionUNet(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.maxpool = nn.MaxPool2d(2)\n\n        self.enc1 = DoubleConv(1, 64)\n        self.enc2 = DoubleConv(64, 128)\n        self.enc3 = DoubleConv(128, 256)\n        self.enc4 = DoubleConv(256, 512)\n\n        self.up4 = nn.ConvTranspose2d(512, 256, 2, stride=2)\n        self.att4 = AttentionGate(256, 256, 128)\n        self.dec4 = DoubleConv(512, 256)\n\n        self.up3 = nn.ConvTranspose2d(256, 128, 2, stride=2)\n        self.att3 = AttentionGate(128, 128, 64)\n        self.dec3 = DoubleConv(256, 128)\n\n        self.up2 = nn.ConvTranspose2d(128, 64, 2, stride=2)\n        self.att2 = AttentionGate(64, 64, 32)\n        self.dec2 = DoubleConv(128, 64)\n\n        self.final = nn.Conv2d(64, 1, kernel_size=1)\n\n    def forward(self, x):\n        e1 = self.enc1(x)\n        e2 = self.enc2(self.maxpool(e1))\n        e3 = self.enc3(self.maxpool(e2))\n        e4 = self.enc4(self.maxpool(e3))\n\n        d4 = self.up4(e4)\n        x3 = self.att4(g=d4, x=e3)\n        d4 = self.dec4(torch.cat((x3, d4), dim=1))\n\n        d3 = self.up3(d4)\n        x2 = self.att3(g=d3, x=e2)\n        d3 = self.dec3(torch.cat((x2, d3), dim=1))\n\n        d2 = self.up2(d3)\n        x1 = self.att2(g=d2, x=e1)\n        d2 = self.dec2(torch.cat((x1, d2), dim=1))\n\n        return self.final(d2)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Khởi tạo thực thể","metadata":{}},{"cell_type":"markdown","source":"# Dice Loss","metadata":{}},{"cell_type":"code","source":"class DiceBCELoss(nn.Module):\n    def __init__(self):\n        super(DiceBCELoss, self).__init__()\n\n    def forward(self, inputs, targets, smooth=1e-6):\n        # 1. Tính BCE với Logits (An toàn cho autocast)\n        # Không cần dùng torch.sigmoid(inputs) ở đây vì hàm này đã bao gồm sigmoid bên trong\n        bce = nn.functional.binary_cross_entropy_with_logits(inputs, targets)\n\n        # 2. Tính Dice (Cần dùng sigmoid để đưa về khoảng 0-1)\n        inputs_soft = torch.sigmoid(inputs)\n        \n        inputs_flat = inputs_soft.view(-1)\n        targets_flat = targets.view(-1)\n\n        intersection = (inputs_flat * targets_flat).sum()\n        dice_loss = 1 - (2. * intersection + smooth) / (\n            inputs_flat.sum() + targets_flat.sum() + smooth\n        )\n\n        # Kết hợp cả hai\n        return 0.5 * bce + 0.5 * dice_loss","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\n# 1. Khởi tạo model\nmodel_unet = AttentionUNet().to(Config.DEVICE)\n\n# 2. Thay đổi Optimizer: Dùng AdamW để kiểm soát overfitting tốt hơn\noptimizer_unet = torch.optim.AdamW(model_unet.parameters(), lr=1e-4, weight_decay=1e-4)\n\n# 3. Thêm Scheduler: Giảm LR theo hình cosin giúp hội tụ sâu hơn\n# T_max nên bằng tổng số epochs bạn định chạy\nscheduler_unet = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer_unet, T_max=15)\n\n# 4. Khởi tạo Loss (Nhớ dùng bản đã sửa để an toàn cho autocast)\ncriterion_unet = DiceBCELoss()\n\nprint(\"✅ Đã cập nhật Optimizer AdamW và Cosine Scheduler!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# huấn luyện Attention U-Net","metadata":{}},{"cell_type":"code","source":"#import gc\n#import torch\n\n# Xóa các biến rác và giải phóng cache\n#gc.collect()\n#torch.cuda.empty_cache()\n#print(\"✅ Đã dọn dẹp GPU!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#import gc\n#import torch\n\n# Xóa các biến không còn dùng\n#if 'imgs_hr' in locals(): del imgs_hr\n#if 'imgs_sr' in locals(): del imgs_sr\n#if 'preds' in locals(): del preds\n\n# Giải phóng bộ nhớ đệm của CUDA\n#gc.collect()\n#torch.cuda.empty_cache()\n#print(\"✅ Đã dọn dẹp bộ nhớ GPU!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\ndef dice_coef(y_true, y_pred, smooth=1e-6):\n    \"\"\"\n    Tính Dice Coefficient (độ tương đồng)\n    \"\"\"\n    # 🔥 sigmoid vì model output là logits\n    y_pred = torch.sigmoid(y_pred)\n\n    # (optional) chuyển về nhị phân\n    y_pred = (y_pred > 0.7).float()\n\n    y_true_f = y_true.view(-1)\n    y_pred_f = y_pred.view(-1)\n    \n    intersection = (y_true_f * y_pred_f).sum()\n    \n    return (2. * intersection + smooth) / (\n        y_true_f.sum() + y_pred_f.sum() + smooth\n    )\n\nprint(\"✅ Dice coef chuẩn đã sẵn sàng!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def iou_score(pred, target, smooth=1e-6):\n    pred = torch.sigmoid(pred)\n    pred = (pred > 0.5).float()\n\n    pred = pred.view(-1)\n    target = target.view(-1)\n\n    intersection = (pred * target).sum()\n    union = pred.sum() + target.sum() - intersection\n\n    return (intersection + smooth) / (union + smooth)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.amp import autocast, GradScaler\n\n# Sử dụng GradScaler chuẩn mới\nscaler = GradScaler('cuda')\n\ndef train_segmentation(epochs=15):\n    model_unet.train()\n    netG.eval() # GAN chỉ dùng để tạo ảnh đầu vào, không train lại\n    \n    print(f\"🚀 Đang luyện công cho Attention U-Net trên {Config.DEVICE}...\")\n    \n    for epoch in range(epochs):\n        epoch_loss = 0\n        epoch_dice = 0\n        epoch_iou = 0\n        \n        for imgs_hr, masks in train_loader:\n            imgs_hr = imgs_hr.to(Config.DEVICE)\n            masks = masks.to(Config.DEVICE).float()\n            \n            # 1. GAN tạo ảnh SR (đầu vào chất lượng cao)\n            with torch.no_grad():\n                imgs_lr = torch.nn.functional.interpolate(imgs_hr, scale_factor=0.5, mode='bilinear')\n                imgs_sr = netG(imgs_lr)\n\n            # 2. Forward & Backward U-Net\n            optimizer_unet.zero_grad()\n            with autocast(device_type='cuda'): # Cú pháp chuẩn 2026\n                preds = model_unet(imgs_sr)\n                if masks.dim() == 3: masks = masks.unsqueeze(1)\n                loss = criterion_unet(preds, masks)\n\n            scaler.scale(loss).backward()\n            scaler.step(optimizer_unet)\n            scaler.update()\n            \n            # 3. Tính Metric\n            with torch.no_grad():\n                epoch_dice += dice_coef(masks, preds).item()\n                epoch_iou += iou_score(preds, masks).item()\n                epoch_loss += loss.item()\n        \n        # 🔥 QUAN TRỌNG: Kích hoạt Scheduler đã cấu hình ở trên\n        scheduler_unet.step() \n        \n        # Lấy LR hiện tại để kiểm tra xem nó có giảm không\n        current_lr = optimizer_unet.param_groups[0]['lr']\n\n        print(f\"Epoch [{epoch+1}/{epochs}] | Loss: {epoch_loss/len(train_loader):.4f} | \"\n              f\"Dice: {epoch_dice/len(train_loader):.4f} | LR: {current_lr:.6f}\")\n\n# RUN\ntrain_segmentation(epochs=15)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_segmentation_no_gan(epochs=10):\n    model_unet.train()\n\n    for epoch in range(epochs):\n        epoch_dice = 0\n\n        for imgs, masks in train_loader:\n            imgs = imgs.to(Config.DEVICE)\n            masks = masks.to(Config.DEVICE).float()\n\n            preds = model_unet(imgs)\n\n            if masks.dim() == 3:\n                masks = masks.unsqueeze(1)\n\n            loss = criterion_unet(preds, masks)\n\n            optimizer_unet.zero_grad()\n            loss.backward()\n            optimizer_unet.step()\n\n            epoch_dice += dice_coef(masks, preds).item()\n\n        print(f\"[NO GAN] Epoch {epoch+1} Dice: {epoch_dice/len(train_loader):.4f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Trực quan hóa kết quả phân vùng","metadata":{}},{"cell_type":"code","source":"def demo_segmentation_results(num_samples=3):\n    model_unet.eval()\n    netG.eval()\n    \n    # Lọc các index có Target = 1 (có viêm phổi)\n    positive_samples = val_df[val_df['Target'] == 1].head(num_samples)\n    demo_dataset = RSNADataset(positive_samples, data_transforms)\n    demo_loader = DataLoader(demo_dataset, batch_size=num_samples)\n    \n    imgs_hr, masks = next(iter(demo_loader))\n    imgs_hr = imgs_hr.to(Config.DEVICE)\n    \n    with torch.no_grad():\n        # Tạo ảnh SR qua GAN\n        imgs_lr = nn.functional.interpolate(imgs_hr, scale_factor=0.5, mode='bilinear')\n        imgs_sr = netG(imgs_lr)\n        # Dự đoán phân vùng\n        preds = model_unet(imgs_sr)\n    \n    # Hiển thị\n    plt.figure(figsize=(15, 5 * num_samples))\n    for i in range(num_samples):\n        # Ảnh gốc SR\n        plt.subplot(num_samples, 3, i*3 + 1)\n        plt.imshow(imgs_sr[i].cpu().squeeze(), cmap='gray')\n        plt.title(\"Ảnh X-quang (SR)\")\n        plt.axis('off')\n        \n        # Nhãn thực tế (Ground Truth) - Hiển thị màu đỏ\n        plt.subplot(num_samples, 3, i*3 + 2)\n        plt.imshow(imgs_sr[i].cpu().squeeze(), cmap='gray')\n        plt.imshow(masks[i].cpu().squeeze(), alpha=0.3, cmap='Reds')\n        plt.title(\"Vùng bệnh (Bác sĩ vẽ)\")\n        plt.axis('off')\n        \n        # AI dự đoán - Hiển thị màu xanh lá\n        plt.subplot(num_samples, 3, i*3 + 3)\n        plt.imshow(imgs_sr[i].cpu().squeeze(), cmap='gray')\n        pred_mask = torch.sigmoid(preds[i]).cpu().squeeze()\n        pred_mask = (pred_mask > 0.3).float() # Hạ ngưỡng xuống 0.3 để dễ thấy demo\n        plt.imshow(pred_mask, alpha=0.3, cmap='Greens')\n        plt.title(\"AI Dự đoán\")\n        plt.axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\n# Chạy demo\ndemo_segmentation_results()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\nimport torch\n\ndef visualize_results(model, dataloader, device, num_samples=3):\n    model.eval()\n\n    images, masks = next(iter(dataloader))\n    images = images.to(device)\n    masks = masks.to(device)\n\n    with torch.no_grad():\n        preds = model(images)\n        preds = torch.sigmoid(preds)\n\n    images = images.cpu().numpy()\n    masks = masks.cpu().numpy()\n    preds = preds.cpu().numpy()\n\n    preds = (preds > 0.5).astype(np.uint8)\n\n    plt.figure(figsize=(12, 4 * num_samples))\n\n    for i in range(num_samples):\n        img = images[i].transpose(1, 2, 0)\n        mask = masks[i][0]\n        pred = preds[i][0]\n\n        # Overlay prediction\n        overlay = img.copy()\n        overlay = np.squeeze(overlay)\n        overlay = np.stack([overlay]*3, axis=-1)\n\n        overlay[pred == 1] = [0, 255, 0]  # màu xanh\n\n        # Hiển thị\n        plt.subplot(num_samples, 3, i*3 + 1)\n        plt.imshow(img.squeeze(), cmap='gray')\n        plt.title(\"Ảnh X-quang\")\n        plt.axis('off')\n\n        plt.subplot(num_samples, 3, i*3 + 2)\n        plt.imshow(mask, cmap='Reds')\n        plt.title(\"Mask (Ground Truth)\")\n        plt.axis('off')\n\n        plt.subplot(num_samples, 3, i*3 + 3)\n        plt.imshow(overlay)\n        plt.title(\"Dự đoán (Overlay)\")\n        plt.axis('off')\n\n    plt.tight_layout()\n    plt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def demo_segmentation_results(num_samples=3):\n    model_unet.eval()\n    netG.eval()\n    \n    # Lọc các index có Target = 1 (có viêm phổi)\n    positive_samples = val_df[val_df['Target'] == 1].head(num_samples)\n    demo_dataset = RSNADataset(positive_samples, data_transforms)\n    demo_loader = DataLoader(demo_dataset, batch_size=num_samples)\n    \n    imgs_hr, masks = next(iter(demo_loader))\n    imgs_hr = imgs_hr.to(Config.DEVICE)\n    \n    with torch.no_grad():\n        # Tạo ảnh SR qua GAN\n        imgs_lr = nn.functional.interpolate(imgs_hr, scale_factor=0.5, mode='bilinear')\n        imgs_sr = netG(imgs_lr)\n        # Dự đoán phân vùng\n        preds = model_unet(imgs_sr)\n    \n    # Hiển thị\n    plt.figure(figsize=(15, 5 * num_samples))\n    for i in range(num_samples):\n        # Ảnh gốc SR\n        plt.subplot(num_samples, 3, i*3 + 1)\n        plt.imshow(imgs_sr[i].cpu().squeeze(), cmap='gray')\n        plt.title(\"Ảnh X-quang (SR)\")\n        plt.axis('off')\n        \n        # Nhãn thực tế (Ground Truth) - Hiển thị màu đỏ\n        plt.subplot(num_samples, 3, i*3 + 2)\n        plt.imshow(imgs_sr[i].cpu().squeeze(), cmap='gray')\n        plt.imshow(masks[i].cpu().squeeze(), alpha=0.3, cmap='Reds')\n        plt.title(\"Vùng bệnh (Bác sĩ vẽ)\")\n        plt.axis('off')\n        \n        # AI dự đoán - Hiển thị màu xanh lá\n        plt.subplot(num_samples, 3, i*3 + 3)\n        plt.imshow(imgs_sr[i].cpu().squeeze(), cmap='gray')\n        pred_mask = torch.sigmoid(preds[i]).cpu().squeeze()\n        pred_mask = (pred_mask > 0.3).float() # Hạ ngưỡng xuống 0.3 để dễ thấy demo\n        plt.imshow(pred_mask, alpha=0.3, cmap='Greens')\n        plt.title(\"AI Dự đoán\")\n        plt.axis('off')\n    \n    plt.tight_layout()\n    plt.show()\n\n# Chạy demo\ndemo_segmentation_results()","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}