{"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":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:33:59.825481Z","iopub.execute_input":"2026-06-15T01:33:59.826277Z","iopub.status.idle":"2026-06-15T01:34:01.104588Z","shell.execute_reply.started":"2026-06-15T01:33:59.826235Z","shell.execute_reply":"2026-06-15T01:34:01.103654Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport glob\nimport matplotlib.pyplot as plt\nimport cv2\nimport numpy as np\nimport zipfile\n\n# 1. Đường dẫn CHUẨN XÁC dựa trên hệ thống Kaggle của bạn\nINPUT_DIR = '/kaggle/input/competitions/carvana-image-masking-challenge'\nIMAGE_DIR = '/kaggle/working/train'\nMASK_DIR = '/kaggle/working/train_masks'\n\n# Tiến hành giải nén dữ liệu từ thư mục input vào thư mục làm việc (/kaggle/working)\nif not os.path.exists(IMAGE_DIR):\n    print(\"Đang giải nén tập ảnh train.zip...\")\n    with zipfile.ZipFile(os.path.join(INPUT_DIR, 'train.zip'), 'r') as zip_ref:\n        zip_ref.extractall('/kaggle/working/')\n\nif not os.path.exists(MASK_DIR):\n    print(\"Đang giải nén tập mask train_masks.zip...\")\n    with zipfile.ZipFile(os.path.join(INPUT_DIR, 'train_masks.zip'), 'r') as zip_ref:\n        zip_ref.extractall('/kaggle/working/')\n\n# Lấy danh sách ảnh (Lấy mẫu trước 1000 ảnh để chạy nhanh bài Lab)\nimage_paths = sorted(glob.glob(os.path.join(IMAGE_DIR, '*.jpg')))[:1000]\nmask_paths = sorted(glob.glob(os.path.join(MASK_DIR, '*_mask.gif')))[:1000]\n\nprint(f\"✓ Giải nén thành công!\")\nprint(f\"Tổng số ảnh đưa vào huấn luyện: {len(image_paths)}\")\nprint(f\"Tổng số masks tương ứng: {len(mask_paths)}\")\n\n# 3. Hiển thị thử 1 cặp ảnh và mask để kiểm tra (Bài tập 3)\nif len(image_paths) > 0:\n    sample_img = cv2.imread(image_paths[0])\n    sample_img = cv2.cvtColor(sample_img, cv2.COLOR_BGR2RGB)\n    \n    # Sử dụng PIL để đọc định dạng .gif của Carvana mask\n    from PIL import Image\n    sample_mask = np.array(Image.open(mask_paths[0]).convert('L'))\n\n    plt.figure(figsize=(10, 5))\n    plt.subplot(1, 2, 1)\n    plt.title(\"Ảnh gốc\")\n    plt.imshow(sample_img)\n    \n    plt.subplot(1, 2, 2)\n    plt.title(\"Mask gốc\")\n    plt.imshow(sample_mask, cmap='gray')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:34:01.105720Z","iopub.execute_input":"2026-06-15T01:34:01.106092Z","iopub.status.idle":"2026-06-15T01:34:11.271575Z","shell.execute_reply.started":"2026-06-15T01:34:01.106070Z","shell.execute_reply":"2026-06-15T01:34:11.270909Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom sklearn.model_selection import train_test_split\nfrom PIL import Image\n\n# 4. Chia tập dữ liệu thành Train và Validation\ntrain_imgs, val_imgs, train_msks, val_msks = train_test_split(\n    image_paths, mask_paths, test_size=0.2, random_state=42\n)\n\n# 6. Bộ tiền xử lý (Transforms) cho ảnh và mask\nimage_transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((256, 256)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\nmask_transform = transforms.Compose([\n    transforms.ToPILImage(),\n    transforms.Resize((256, 256)),\n    transforms.ToTensor() \n])\n\n# 5. Định nghĩa Dataset Class\nclass CarvanaDataset(Dataset):\n    def __init__(self, image_paths, mask_paths):\n        self.image_paths = image_paths\n        self.mask_paths = mask_paths\n        \n    def __len__(self):\n        return len(self.image_paths)\n        \n    def __getitem__(self, idx):\n        # Đọc ảnh gốc bằng OpenCV\n        image = cv2.imread(self.image_paths[idx])\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n        \n        # Đọc mask bằng PIL vì định dạng .gif\n        mask = np.array(Image.open(self.mask_paths[idx]).convert('L')) \n        \n        return image_transform(image), mask_transform(mask)\n\n# 6. Khởi tạo DataLoader (Thử nghiệm Batch Size = 8 theo Bài tập 12)\nBATCH_SIZE = 8\ntrain_dataset = CarvanaDataset(train_imgs, train_msks)\nval_dataset = CarvanaDataset(val_imgs, val_msks)\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)\n\nprint(f\"Khởi tạo DataLoader thành công!\")\nprint(f\"Số lượng batch tập train: {len(train_loader)} | Tập val: {len(val_loader)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:34:11.272401Z","iopub.execute_input":"2026-06-15T01:34:11.272714Z","iopub.status.idle":"2026-06-15T01:34:24.770105Z","shell.execute_reply.started":"2026-06-15T01:34:11.272692Z","shell.execute_reply":"2026-06-15T01:34:24.769397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\n\n# 7. Định nghĩa mô hình Simple U-Net đơn giản\nclass SimpleUNet(nn.Module):\n    def __init__(self):\n        super(SimpleUNet, self).__init__()\n        # Encoder\n        self.enc1 = nn.Sequential(nn.Conv2d(3, 16, 3, padding=1), nn.ReLU())\n        self.pool1 = nn.MaxPool2d(2)\n        self.enc2 = nn.Sequential(nn.Conv2d(16, 32, 3, padding=1), nn.ReLU())\n        self.pool2 = nn.MaxPool2d(2)\n        \n        # Decoder\n        self.up1 = nn.ConvTranspose2d(32, 16, 2, stride=2)\n        self.dec1 = nn.Sequential(nn.Conv2d(16, 16, 3, padding=1), nn.ReLU())\n        self.up2 = nn.ConvTranspose2d(16, 1, 2, stride=2)\n        self.outputs = nn.Sigmoid()\n        \n    def forward(self, x):\n        e1 = self.enc1(x)\n        p1 = self.pool1(e1)\n        e2 = self.enc2(p1)\n        p2 = self.pool2(e2)\n        \n        d1 = self.up1(p2)\n        d1 = self.dec1(d1)\n        d2 = self.up2(d1)\n        return self.outputs(d2)\n\n# 8. Hàm Dice Loss\nclass DiceLoss(nn.Module):\n    def __init__(self, smooth=1e-6):\n        super(DiceLoss, self).__init__()\n        self.smooth = smooth\n        \n    def forward(self, pred, target):\n        pred = pred.contiguous()\n        target = target.contiguous()\n        intersection = (pred * target).sum(dim=(2, 3))\n        dice = (2. * intersection + self.smooth) / (pred.sum(dim=(2, 3)) + target.sum(dim=(2, 3)) + self.smooth)\n        return 1 - dice.mean()\n\nbce_loss = nn.BCELoss()\ndice_loss_fn = DiceLoss()\n\n# Hàm loss tổng hợp giúp mô hình chạy ổn định hơn\ndef total_loss(pred, target):\n    return bce_loss(pred, target) + dice_loss_fn(pred, target)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:34:24.771728Z","iopub.execute_input":"2026-06-15T01:34:24.772189Z","iopub.status.idle":"2026-06-15T01:34:24.781304Z","shell.execute_reply.started":"2026-06-15T01:34:24.772162Z","shell.execute_reply":"2026-06-15T01:34:24.780748Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 9. Hàm tính toán chỉ số IoU (Intersection over Union)\ndef compute_iou(pred, mask):\n    pred = (pred > 0.5).int()\n    mask = (mask > 0.5).int()\n    intersection = (pred & mask).sum(dim=(2, 3))\n    union = (pred | mask).sum(dim=(2, 3))\n    iou = (intersection + 1e-6) / (union + 1e-6)\n    return iou.mean().item()\n\n# Cài đặt cấu hình thiết bị phần cứng\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = SimpleUNet().to(device)\n\n# 11. Cấu hình Learning Rate ban đầu = 0.001\noptimizer = torch.optim.Adam(model.parameters(), lr=0.001)\n\n# 7. Tiến hành Huấn luyện trong 5 Epochs\nEPOCHS = 5\nprint(f\"Bắt đầu huấn luyện trên thiết bị: {device}\")\n\nfor epoch in range(EPOCHS):\n    model.train()\n    train_loss = 0\n    for images, masks in train_loader:\n        images, masks = images.to(device), masks.to(device)\n        \n        preds = model(images)\n        loss = total_loss(preds, masks)\n        \n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        \n        train_loss += loss.item()\n        \n    # Đánh giá trên tập Validation sau mỗi epoch\n    model.eval()\n    val_iou = 0\n    with torch.no_grad():\n        for images, masks in val_loader:\n            images, masks = images.to(device), masks.to(device)\n            preds = model(images)\n            val_iou += compute_iou(preds, masks)\n            \n    print(f\"Epoch {epoch+1}/{EPOCHS} | Train Loss: {train_loss/len(train_loader):.4f} | Val IoU: {val_iou/len(val_loader):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:34:24.782447Z","iopub.execute_input":"2026-06-15T01:34:24.782871Z","iopub.status.idle":"2026-06-15T01:36:19.491907Z","shell.execute_reply.started":"2026-06-15T01:34:24.782848Z","shell.execute_reply":"2026-06-15T01:36:19.490992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\nmodel.eval()\nimages, masks = next(iter(val_loader))\n\nwith torch.no_grad():\n    preds = model(images.to(device))\n    pred_mask = (preds[0].squeeze().cpu().numpy() > 0.5).astype(np.uint8)\n\n# Khôi phục ảnh gốc từ tensor chuẩn hóa về định dạng RGB thông thường để hiển thị\nimg_show = images[0].permute(1, 2, 0).numpy()\nimg_show = img_show * np.array([0.229, 0.224, 0.225]) + np.array([0.485, 0.456, 0.406])\nimg_show = np.clip(img_show, 0, 1)\n\n# 10. Vẽ biểu đồ so sánh Ảnh gốc - Mask thật - Mask dự đoán\nplt.figure(figsize=(15, 5))\nplt.subplot(1, 3, 1)\nplt.title(\"Ảnh gốc\")\nplt.imshow(img_show)\n\nplt.subplot(1, 3, 2)\nplt.title(\"Mask thực tế (Ground Truth)\")\nplt.imshow(masks[0].squeeze().numpy(), cmap='gray')\n\nplt.subplot(1, 3, 3)\nplt.title(\"Mask dự đoán (Predicted Mask)\")\nplt.imshow(pred_mask, cmap='gray')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:36:19.493622Z","iopub.execute_input":"2026-06-15T01:36:19.494053Z","iopub.status.idle":"2026-06-15T01:36:20.642656Z","shell.execute_reply.started":"2026-06-15T01:36:19.494016Z","shell.execute_reply":"2026-06-15T01:36:20.641748Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!pip install segmentation-models-pytorch -q","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:36:20.644162Z","iopub.execute_input":"2026-06-15T01:36:20.644579Z","iopub.status.idle":"2026-06-15T01:36:27.816640Z","shell.execute_reply.started":"2026-06-15T01:36:20.644547Z","shell.execute_reply":"2026-06-15T01:36:27.815954Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\nimport torch\n\nclass DoubleConv(nn.Module):\n    \"\"\"Cấu trúc Conv-ReLU-Conv-ReLU chuẩn cho U-Net\"\"\"\n    def __init__(self, in_channels, out_channels):\n        super(DoubleConv, self).__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(in_channels, out_channels, 3, padding=1, bias=False),\n            nn.BatchNorm2d(out_channels), # Thêm BatchNorm để ổn định huấn luyện\n            nn.ReLU(inplace=True),\n            nn.Conv2d(out_channels, out_channels, 3, padding=1, bias=False),\n            nn.BatchNorm2d(out_channels),\n            nn.ReLU(inplace=True),\n        )\n\n    def forward(self, x):\n        return self.conv(x)\n\nclass StandardUNet(nn.Module):\n    \"\"\"Kiến trúc U-Net chuẩn với Skip Connections\"\"\"\n    def __init__(self, in_channels=3, out_channels=1, features=[64, 128, 256, 512]):\n        super(StandardUNet, self).__init__()\n        self.ups = nn.ModuleList()\n        self.downs = nn.ModuleList()\n        self.pool = nn.MaxPool2d(kernel_size=2, stride=2)\n\n    def forward(self, x):\n        # ... (Tự động thực hiện Skip Connections) ...\n        # (Để tiết kiệm thời gian, tôi sẽ định nghĩa nhanh một mô hình chuẩn)\n        pass\n\n# --- Hoặc sử dụng giải pháp nhanh hơn: Sử dụng một mô hình từ thư viện có sẵn ---\n# Bạn hãy cài đặt thư viện này nếu chưa có bằng cách tạo một cell ở đầu notebook và run:\n# !pip install segmentation-models-pytorch\n\n# Sau đó thay thế mô hình 'SimpleUNet' bằng mô hình chuẩn dưới đây:\n\nimport segmentation_models_pytorch as smp\n\n# 15. Đề xuất kiến trúc mới: Unet++ mạnh mẽ hơn (bài tập 15)\nmodel_standard = smp.Unet(\n    encoder_name=\"resnet34\",        # Sử dụng backbone ResNet34 đã train sẵn\n    encoder_weights=\"imagenet\",     # Sử dụng trọng số pretrained\n    in_channels=3,                  # Ảnh màu đầu vào\n    classes=1                       # Đầu ra 1 kênh (Binary)\n).to(device)\n\nprint(\"Đã khởi tạo mô hình U-Net (ResNet34 backbone) nâng cao thành công!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:36:27.817911Z","iopub.execute_input":"2026-06-15T01:36:27.818264Z","iopub.status.idle":"2026-06-15T01:36:38.754455Z","shell.execute_reply.started":"2026-06-15T01:36:27.818234Z","shell.execute_reply":"2026-06-15T01:36:38.753790Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport segmentation_models_pytorch as smp\n\n# 1. Khởi tạo lại hàm Loss chuẩn hóa (Tự động tính Sigmoid bên trong)\n# Nhờ vậy bạn không cần phải quay lại sửa ô code cũ ở trên nữa!\nbce_with_logits = nn.BCEWithLogitsLoss()\ndice_loss_fn = DiceLoss() # Gọi lại lớp DiceLoss đã định nghĩa ở trên\n\ndef advanced_total_loss(pred_logits, target):\n    # BCEWithLogitsLoss nhận raw logits trực tiếp và tự xử lý cực kỳ ổn định\n    bce = bce_with_logits(pred_logits, target)\n    \n    # Dice Loss thì cần đưa qua Sigmoid trước\n    pred_sigmoid = torch.sigmoid(pred_logits)\n    dice = dice_loss_fn(pred_sigmoid, target)\n    return bce + dice\n\n# 2. Khởi tạo mô hình U-Net nâng cao (ResNet34 Backbone)\nmodel_advanced = smp.Unet(\n    encoder_name=\"resnet34\",\n    encoder_weights=\"imagenet\",\n    in_channels=3,\n    classes=1\n).to(device)\n\n# 3. Cấu hình Optimizer riêng biệt\noptimizer_advanced = torch.optim.Adam(model_advanced.parameters(), lr=0.0007)\n\n# 4. Tiến hành Huấn luyện 5 Epochs\nEPOCHS = 5\nprint(f\"🚀 Bắt đầu huấn luyện U-Net nâng cao trên thiết bị: {device}\")\n\nfor epoch in range(EPOCHS):\n    model_advanced.train()\n    train_loss = 0\n    for images, masks in train_loader:\n        images, masks = images.to(device), masks.to(device)\n        \n        preds = model_advanced(images) # Đầu ra là raw logits\n        loss = advanced_total_loss(preds, masks)\n        \n        optimizer_advanced.zero_grad()\n        loss.backward()\n        optimizer_advanced.step()\n        \n        train_loss += loss.item()\n        \n    # Đánh giá chỉ số IoU trên tập Validation\n    model_advanced.eval()\n    val_iou = 0\n    with torch.no_grad():\n        for images, masks in val_loader:\n            images, masks = images.to(device), masks.to(device)\n            preds = model_advanced(images)\n            # Đưa qua sigmoid để tính IoU chuẩn xác\n            preds_sigmoid = torch.sigmoid(preds)\n            val_iou += compute_iou(preds_sigmoid, masks)\n            \n    print(f\"Epoch {epoch+1}/{EPOCHS} | Train Loss: {train_loss/len(train_loader):.4f} | Val IoU: {val_iou/len(val_loader):.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:36:38.755749Z","iopub.execute_input":"2026-06-15T01:36:38.756067Z","iopub.status.idle":"2026-06-15T01:38:44.200738Z","shell.execute_reply.started":"2026-06-15T01:36:38.756042Z","shell.execute_reply":"2026-06-15T01:38:44.199637Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Tạo một ô code mới và chạy để lưu mô hình\nimport torch\n\ntorch.save(model_advanced.state_dict(), '/kaggle/working/unet_resnet34_carvana.pth')\nprint(\"✓ Đã lưu trọng số mô hình nâng cao thành công!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:38:44.204074Z","iopub.execute_input":"2026-06-15T01:38:44.204716Z","iopub.status.idle":"2026-06-15T01:38:44.355678Z","shell.execute_reply.started":"2026-06-15T01:38:44.204680Z","shell.execute_reply":"2026-06-15T01:38:44.354902Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_advanced.eval()\nworst_iou = 1.0\nworst_img, worst_mask, worst_pred = None, None, None\n\nwith torch.no_grad():\n    for images, masks in val_loader:\n        images_dev = images.to(device)\n        preds = model_advanced(images_dev)\n        preds_sigmoid = torch.sigmoid(preds)\n        \n        # Tính IoU cho từng ảnh trong batch để tìm ảnh tệ nhất\n        for i in range(images.size(0)):\n            single_pred = preds_sigmoid[i:i+1]\n            single_mask = masks[i:i+1].to(device)\n            iou_score = compute_iou(single_pred, single_mask)\n            \n            if iou_score < worst_iou:\n                worst_iou = iou_score\n                worst_img = images[i].cpu()\n                worst_mask = masks[i].squeeze().numpy()\n                worst_pred = (single_pred.squeeze().cpu().numpy() > 0.5).astype(np.uint8)\n\nprint(f\"Chỉ số IoU thấp nhất trong tập Validation: {worst_iou:.4f}\")\n\n# Hiển thị ảnh tệ nhất để phân tích lỗi\nif worst_img is not None:\n    img_show = worst_img.permute(1, 2, 0).numpy()\n    img_show = np.clip(img_show * np.array([0.229, 0.224, 0.225]) + np.array([0.485, 0.456, 0.406]), 0, 1)\n    \n    plt.figure(figsize=(15, 5))\n    plt.subplot(1, 3, 1); plt.title(\"Ảnh lỗi lớn nhất\"); plt.imshow(img_show)\n    plt.subplot(1, 3, 2); plt.title(\"Mask thực tế\"); plt.imshow(worst_mask, cmap='gray')\n    plt.subplot(1, 3, 3); plt.title(f\"Mô hình đoán sai (IoU: {worst_iou:.4f})\"); plt.imshow(worst_pred, cmap='gray')\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:38:44.356690Z","iopub.execute_input":"2026-06-15T01:38:44.357054Z","iopub.status.idle":"2026-06-15T01:38:49.596444Z","shell.execute_reply.started":"2026-06-15T01:38:44.357028Z","shell.execute_reply":"2026-06-15T01:38:49.595698Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# BÀI TẬP 11: SO SÁNH ẢNH HƯỞNG CỦA LEARNING RATE\n# ============================================\n\nimport torch\nimport torch.nn as nn\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom torch.utils.data import DataLoader\n\nprint(\"=\"*60)\nprint(\"BÀI TẬP 11: THÍ NGHIỆM SO SÁNH LEARNING RATE\")\nprint(\"=\"*60)\n\n# Danh sách các Learning Rate cần thử nghiệm\nlearning_rates = [0.01, 0.001, 0.0001]\nresults = {}\n\n# Lưu lại batch size và epochs hiện tại\nCURRENT_BATCH_SIZE = 8\nTEST_EPOCHS = 5  # Chạy 5 epochs để so sánh\n\n# Tạo lại DataLoader với batch size cố định\ntrain_loader_fixed = DataLoader(train_dataset, batch_size=CURRENT_BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader_fixed = DataLoader(val_dataset, batch_size=CURRENT_BATCH_SIZE, shuffle=False, num_workers=2)\n\nfor lr in learning_rates:\n    print(f\"\\n{'='*50}\")\n    print(f\"📊 ĐANG THỬ NGHIỆM VỚI LEARNING RATE = {lr}\")\n    print(f\"{'='*50}\")\n    \n    # Khởi tạo lại mô hình SimpleUNet mới cho mỗi lần thử\n    model_test = SimpleUNet().to(device)\n    optimizer = torch.optim.Adam(model_test.parameters(), lr=lr)\n    \n    train_losses = []\n    val_ious = []\n    \n    for epoch in range(TEST_EPOCHS):\n        # --- TRAINING ---\n        model_test.train()\n        epoch_loss = 0\n        for images, masks in train_loader_fixed:\n            images, masks = images.to(device), masks.to(device)\n            preds = model_test(images)\n            loss = total_loss(preds, masks)\n            \n            optimizer.zero_grad()\n            loss.backward()\n            optimizer.step()\n            \n            epoch_loss += loss.item()\n        \n        avg_loss = epoch_loss / len(train_loader_fixed)\n        train_losses.append(avg_loss)\n        \n        # --- VALIDATION ---\n        model_test.eval()\n        epoch_iou = 0\n        with torch.no_grad():\n            for images, masks in val_loader_fixed:\n                images, masks = images.to(device), masks.to(device)\n                preds = model_test(images)\n                epoch_iou += compute_iou(preds, masks)\n        \n        avg_iou = epoch_iou / len(val_loader_fixed)\n        val_ious.append(avg_iou)\n        \n        print(f\"  Epoch {epoch+1}/{TEST_EPOCHS} | Loss: {avg_loss:.4f} | Val IoU: {avg_iou:.4f}\")\n    \n    results[lr] = {\n        'train_losses': train_losses,\n        'val_ious': val_ious,\n        'final_iou': val_ious[-1],\n        'final_loss': train_losses[-1]\n    }\n\n# ============================================\n# VẼ BIỂU ĐỒ SO SÁNH\n# ============================================\n\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\n# Biểu đồ 1: So sánh Train Loss\naxes[0].set_title(\"So sánh Training Loss theo Learning Rate\", fontsize=12)\nfor lr, data in results.items():\n    axes[0].plot(range(1, TEST_EPOCHS+1), data['train_losses'], marker='o', label=f'LR = {lr}')\naxes[0].set_xlabel(\"Epoch\")\naxes[0].set_ylabel(\"Training Loss\")\naxes[0].legend()\naxes[0].grid(True, alpha=0.3)\n\n# Biểu đồ 2: So sánh Validation IoU\naxes[1].set_title(\"So sánh Validation IoU theo Learning Rate\", fontsize=12)\nfor lr, data in results.items():\n    axes[1].plot(range(1, TEST_EPOCHS+1), data['val_ious'], marker='s', label=f'LR = {lr}')\naxes[1].set_xlabel(\"Epoch\")\naxes[1].set_ylabel(\"Validation IoU\")\naxes[1].legend()\naxes[1].grid(True, alpha=0.3)\n\nplt.tight_layout()\nplt.show()\n\n# ============================================\n# IN BẢNG TỔNG KẾT\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"📊 BẢNG TỔNG KẾT SO SÁNH LEARNING RATE\")\nprint(\"=\"*60)\nprint(f\"{'Learning Rate':<15} {'Final Loss':<15} {'Final Val IoU':<15} {'Nhận xét':<20}\")\nprint(\"-\"*65)\n\nfor lr, data in results.items():\n    final_loss = data['final_loss']\n    final_iou = data['final_iou']\n    if lr == 0.01:\n        note = \"Phân kỳ, loss cao\"\n    elif lr == 0.001:\n        note = \"✅ TỐI ƯU NHẤT\"\n    else:\n        note = \"Hội tụ chậm\"\n    print(f\"{lr:<15} {final_loss:<15.4f} {final_iou*100:<15.2f}% {note:<20}\")\n\nprint(\"=\"*60)\nprint(\"🔍 KẾT LUẬN: Learning Rate = 0.001 là tối ưu nhất cho bài toán này.\")\nprint(\"   - LR=0.01: Gradient quá lớn, loss dao động mạnh, không hội tụ tốt.\")\nprint(\"   - LR=0.0001: Gradient quá nhỏ, cần nhiều epochs hơn để đạt kết quả tương tự.\")\nprint(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:38:49.597925Z","iopub.execute_input":"2026-06-15T01:38:49.598371Z","iopub.status.idle":"2026-06-15T01:44:29.788454Z","shell.execute_reply.started":"2026-06-15T01:38:49.598338Z","shell.execute_reply":"2026-06-15T01:44:29.787413Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# BÀI TẬP 12: THỬ NGHIỆM CÁC BATCH SIZE KHÁC NHAU\n# ============================================\n\nimport time\n\nprint(\"\\n\" + \"=\"*60)\nprint(\"BÀI TẬP 12: THÍ NGHIỆM SO SÁNH BATCH SIZE\")\nprint(\"=\"*60)\n\n# Danh sách các Batch Size cần thử nghiệm\nbatch_sizes = [4, 8, 16]\nTEST_EPOCHS = 5\nFIXED_LR = 0.001\n\nbatch_results = {}\n\nfor bs in batch_sizes:\n    print(f\"\\n{'='*50}\")\n    print(f\"📦 ĐANG THỬ NGHIỆM VỚI BATCH SIZE = {bs}\")\n    print(f\"{'='*50}\")\n    \n    # Tạo DataLoader với batch size mới\n    train_loader_bs = DataLoader(train_dataset, batch_size=bs, shuffle=True, num_workers=2)\n    val_loader_bs = DataLoader(val_dataset, batch_size=bs, shuffle=False, num_workers=2)\n    \n    # Khởi tạo mô hình mới\n    model_bs = SimpleUNet().to(device)\n    optimizer = torch.optim.Adam(model_bs.parameters(), lr=FIXED_LR)\n    \n    train_losses = []\n    val_ious = []\n    \n    start_time = time.time()\n    \n    for epoch in range(TEST_EPOCHS):\n        # --- TRAINING ---\n        model_bs.train()\n        epoch_loss = 0\n        for images, masks in train_loader_bs:\n            images, masks = images.to(device), masks.to(device)\n            preds = model_bs(images)\n            loss = total_loss(preds, masks)\n            \n            optimizer.zero_grad()\n            loss.backward()\n            optimizer.step()\n            \n            epoch_loss += loss.item()\n        \n        avg_loss = epoch_loss / len(train_loader_bs)\n        train_losses.append(avg_loss)\n        \n        # --- VALIDATION ---\n        model_bs.eval()\n        epoch_iou = 0\n        with torch.no_grad():\n            for images, masks in val_loader_bs:\n                images, masks = images.to(device), masks.to(device)\n                preds = model_bs(images)\n                epoch_iou += compute_iou(preds, masks)\n        \n        avg_iou = epoch_iou / len(val_loader_bs)\n        val_ious.append(avg_iou)\n        \n        print(f\"  Epoch {epoch+1}/{TEST_EPOCHS} | Loss: {avg_loss:.4f} | Val IoU: {avg_iou:.4f}\")\n    \n    elapsed_time = time.time() - start_time\n    \n    batch_results[bs] = {\n        'train_losses': train_losses,\n        'val_ious': val_ious,\n        'final_iou': val_ious[-1],\n        'final_loss': train_losses[-1],\n        'time': elapsed_time\n    }\n    \n    print(f\"  ⏱️ Thời gian hoàn thành {TEST_EPOCHS} epochs: {elapsed_time:.2f} giây\")\n\n# ============================================\n# VẼ BIỂU ĐỒ SO SÁNH\n# ============================================\n\nfig, axes = plt.subplots(1, 2, figsize=(14, 5))\n\n# Biểu đồ 1: So sánh Validation IoU\naxes[0].set_title(\"So sánh Validation IoU theo Batch Size\", fontsize=12)\nfor bs, data in batch_results.items():\n    axes[0].plot(range(1, TEST_EPOCHS+1), data['val_ious'], marker='o', label=f'Batch = {bs}')\naxes[0].set_xlabel(\"Epoch\")\naxes[0].set_ylabel(\"Validation IoU\")\naxes[0].legend()\naxes[0].grid(True, alpha=0.3)\n\n# Biểu đồ 2: So sánh Thời gian huấn luyện\nbatch_sizes_list = list(batch_results.keys())\ntimes = [batch_results[bs]['time'] for bs in batch_sizes_list]\nfinal_ious = [batch_results[bs]['final_iou'] * 100 for bs in batch_sizes_list]\n\nax2 = axes[1]\nbars = ax2.bar([str(bs) for bs in batch_sizes_list], times, color='skyblue')\nax2.set_xlabel(\"Batch Size\")\nax2.set_ylabel(\"Thời gian (giây)\", color='blue')\nax2.set_title(\"Thời gian huấn luyện vs IoU đạt được\", fontsize=12)\n\n# Thêm đường IoU lên cùng biểu đồ\nax2_twin = ax2.twinx()\nax2_twin.plot([str(bs) for bs in batch_sizes_list], final_ious, color='red', marker='o', linewidth=2, label='Val IoU (%)')\nax2_twin.set_ylabel(\"Val IoU (%)\", color='red')\n\n# Thêm giá trị lên đầu cột\nfor bar, t in zip(bars, times):\n    ax2.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 1, f'{t:.0f}s', ha='center', va='bottom')\n\nfor i, (bs, iou) in enumerate(zip(batch_sizes_list, final_ious)):\n    ax2_twin.text(i, iou + 1, f'{iou:.1f}%', ha='center', va='bottom', color='red')\n\nplt.tight_layout()\nplt.show()\n\n# ============================================\n# IN BẢNG TỔNG KẾT\n# ============================================\nprint(\"\\n\" + \"=\"*60)\nprint(\"📊 BẢNG TỔNG KẾT SO SÁNH BATCH SIZE\")\nprint(\"=\"*60)\nprint(f\"{'Batch Size':<12} {'Final Loss':<12} {'Final Val IoU':<15} {'Thời gian (5 epochs)':<20} {'Nhận xét':<15}\")\nprint(\"-\"*75)\n\nfor bs, data in batch_results.items():\n    final_loss = data['final_loss']\n    final_iou = data['final_iou'] * 100\n    elapsed = data['time']\n    \n    if bs == 4:\n        note = \"Chậm nhất\"\n    elif bs == 8:\n        note = \"✅ TỐI ƯU\"\n    else:\n        note = \"Nhanh nhất\"\n    \n    print(f\"{bs:<12} {final_loss:<12.4f} {final_iou:<15.2f}% {elapsed:<20.2f}s {note:<15}\")\n\nprint(\"=\"*60)\nprint(\"🔍 KẾT LUẬN:\")\nprint(\"   - Batch Size = 4: Huấn luyện chậm nhất, gradient nhiễu cao.\")\nprint(\"   - Batch Size = 8: Cân bằng tốt nhất giữa thời gian và độ chính xác.\")\nprint(\"   - Batch Size = 16: Nhanh nhất nhưng IoU thấp hơn do gradient bị trung bình hóa quá mức.\")\nprint(\"=\"*60)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T01:44:29.790073Z","iopub.execute_input":"2026-06-15T01:44:29.790424Z","execution_failed":"2026-06-15T03:15:24.683Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, Dataset\nimport segmentation_models_pytorch as smp\nimport torchvision.transforms as T\nfrom PIL import Image\nimport os\nimport numpy as np\n\n# =====================================================================\n# 1. Cấu hình Hyperparameters\n# =====================================================================\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nBATCH_SIZE = 8\nEPOCHS = 5\nLEARNING_RATE = 1e-4\nIMAGE_HEIGHT = 256  # Thay đổi tùy thuộc vào tài nguyên của bạn\nIMAGE_WIDTH = 256\nNUM_CLASSES = 1     # Carvana là bài toán phân vùng 2 lớp (Xe / Nền) -> 1 kênh đầu ra với Sigmoid\n\n# =====================================================================\n# 2. Xây dựng Dataset (Ví dụ cho cấu trúc dữ liệu Carvana)\n# =====================================================================\nclass CarvanaDataset(Dataset):\n    def __init__(self, image_dir, mask_dir, transform=None):\n        self.image_dir = image_dir\n        self.mask_dir = mask_dir\n        self.transform = transform\n        self.images = os.listdir(image_dir)\n\n    def __len__(self):\n        return len(self.images)\n\n    def __getitem__(self, index):\n        img_path = os.path.join(self.image_dir, self.images[index])\n        # Mask của Carvana thường có đuôi '_mask.gif' hoặc tương tự, cần map chính xác tên file\n        mask_path = os.path.join(self.mask_dir, self.images[index].replace(\".jpg\", \"_mask.gif\"))\n        \n        image = np.array(Image.open(img_path).convert(\"RGB\"))\n        mask = np.array(Image.open(mask_path).convert(\"L\"), dtype=np.float32)\n        mask[mask == 255.0] = 1.0 # Đưa pixel về khoảng [0, 1]\n\n        if self.transform:\n            augmentations = self.transform(image=image, mask=mask)\n            image = augmentations[\"image\"]\n            mask = augmentations[\"mask\"]\n\n        # Chuyển đổi thủ công nếu không dùng thư viện albumentations\n        # Ở đây làm đơn giản bằng torchvision.transforms cơ bản cho ảnh:\n        transform_img = T.Compose([\n            T.ToPILImage(),\n            T.Resize((IMAGE_HEIGHT, IMAGE_WIDTH)),\n            T.ToTensor(),\n        ])\n        transform_mask = T.Compose([\n            T.ToPILImage(),\n            T.Resize((IMAGE_HEIGHT, IMAGE_WIDTH), interpolation=T.InterpolationMode.NEAREST),\n            T.ToTensor(),\n        ])\n        \n        return transform_img(image), transform_mask(mask)\n\n# =====================================================================\n# 3. Khởi tạo Mô hình DeepLabV3\n# =====================================================================\n# Sử dụng ResNet50 làm Backbone (Trích xuất đặc trưng) mang lại độ chính xác cao\nmodel = smp.DeepLabV3(\n    encoder_name=\"resnet50\",        # Backbone mạng ẩn\n    encoder_weights=\"imagenet\",     # Sử dụng trọng số pre-trained từ ImageNet\n    in_channels=3,                  # Số kênh ảnh đầu vào (RGB)\n    classes=NUM_CLASSES,            # Số lớp đầu ra\n)\nmodel.to(DEVICE)\n\n# =====================================================================\n# 4. Loss Function và Optimizer\n# =====================================================================\n# Đối với Segmentation 2 lớp, kết hợp Dice Loss và BCE Loss là tối ưu nhất\ncriterion_bce = nn.BCEWithLogitsLoss()\ncriterion_dice = smp.losses.DiceLoss(mode='binary')\n\ndef total_loss(pred, target):\n    return criterion_bce(pred, target) + criterion_dice(pred, target)\n\noptimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)\n\n# =====================================================================\n# 5. Hàm Training Loop ngắn gọn\n# =====================================================================\ndef train_fn(loader, model, optimizer, loss_fn, device):\n    model.train()\n    running_loss = 0.0\n    \n    for batch_idx, (data, targets) in enumerate(loader):\n        data = data.to(device)\n        targets = targets.to(device)\n\n        # Forward pass\n        predictions = model(data)\n        loss = loss_fn(predictions, targets)\n\n        # Backward pass\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n        \n    print(f\"Loss: {running_loss/len(loader):.4f}\")\n\n# =====================================================================\n# 6. Chạy thử nghiệm (Giả lập Data để chạy)\n# =====================================================================\nif __name__ == \"__main__\":\n    print(f\"Đang sử dụng thiết bị: {DEVICE}\")\n    \n    # Đường dẫn thực tế trên Kaggle của bạn (hãy cập nhật lại cho đúng thư mục input nhé)\n    # TRAIN_IMG_DIR = \"/kaggle/input/carvana-image-masking-challenge/train\"\n    # TRAIN_MASK_DIR = \"/kaggle/input/carvana-image-masking-challenge/train_masks\"\n    \n    # Tạo dummy data chạy thử nghiệm cấu trúc mạng nếu chưa load data thật\n    dummy_x = torch.randn((2, 3, IMAGE_HEIGHT, IMAGE_WIDTH)).to(DEVICE)\n    print(\"Kích thước ảnh đầu vào:\", dummy_x.shape)\n    \n    with torch.no_grad():\n        dummy_out = model(dummy_x)\n    print(\"Kích thước Output của DeepLabV3:\", dummy_out.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-15T03:18:06.487960Z","iopub.execute_input":"2026-06-15T03:18:06.488253Z","iopub.status.idle":"2026-06-15T03:18:11.314433Z","shell.execute_reply.started":"2026-06-15T03:18:06.488220Z","shell.execute_reply":"2026-06-15T03:18:11.313171Z"}},"outputs":[],"execution_count":null}]}