{"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":130681,"databundleVersionId":15683668}],"dockerImageVersionId":31286,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Load input cho notebook","metadata":{}},{"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","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:01:19.396824Z","iopub.execute_input":"2026-03-12T16:01:19.397606Z","iopub.status.idle":"2026-03-12T16:01:59.615834Z","shell.execute_reply.started":"2026-03-12T16:01:19.397567Z","shell.execute_reply":"2026-03-12T16:01:59.614786Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Import thư viện vào tạo directory","metadata":{}},{"cell_type":"code","source":"import os\nimport glob\nfrom collections import Counter\nimport matplotlib.pyplot as plt\nimport random\nfrom PIL import Image\nimport torchvision.transforms as transforms\nfrom torch.utils.data import Dataset\nfrom torch.utils.data import DataLoader\nimport torchvision.models as models\nfrom torchvision.models import resnet50, ResNet50_Weights\nfrom torchvision.models import efficientnet_b4, EfficientNet_B4_Weights\nfrom torchvision.models import convnext, ConvNeXt_Tiny_Weights\nimport torch.nn as nn\nimport torch.optim as optim\nimport torch\nfrom tqdm import tqdm\nimport pandas as pd","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:01:59.617117Z","iopub.execute_input":"2026-03-12T16:01:59.617586Z","iopub.status.idle":"2026-03-12T16:02:07.156973Z","shell.execute_reply.started":"2026-03-12T16:01:59.617561Z","shell.execute_reply":"2026-03-12T16:02:07.156363Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dir = \"/kaggle/input/competitions/computer-vision-homework-2026/food_cv/train\"\nval_dir = \"/kaggle/input/competitions/computer-vision-homework-2026/food_cv/val\"\ntest_dir = \"/kaggle/input/competitions/computer-vision-homework-2026/food_cv/test\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:02:07.157772Z","iopub.execute_input":"2026-03-12T16:02:07.158134Z","iopub.status.idle":"2026-03-12T16:02:07.161599Z","shell.execute_reply.started":"2026-03-12T16:02:07.158110Z","shell.execute_reply":"2026-03-12T16:02:07.160872Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Thống kê số lượng ảnh","metadata":{}},{"cell_type":"code","source":"def count_images(folder):\n\n    image_paths = glob.glob(os.path.join(folder, \"*.jpg\"))\n\n    labels = []\n\n    for path in image_paths:\n        filename = os.path.basename(path)\n\n        if \"_\" in filename:\n            label = filename.split(\"_\")[0]\n            labels.append(label)\n\n    class_counts = Counter(labels)\n\n    print(\"\\nFolder:\", folder)\n    print(\"Total images:\", len(image_paths))\n    print(\"Number of classes:\", len(class_counts))\n    print(\"Class distribution:\")\n    print(class_counts)\n\n    return image_paths, labels, class_counts","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:02:07.163050Z","iopub.execute_input":"2026-03-12T16:02:07.163384Z","iopub.status.idle":"2026-03-12T16:02:07.178800Z","shell.execute_reply.started":"2026-03-12T16:02:07.163348Z","shell.execute_reply":"2026-03-12T16:02:07.178176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_paths, train_labels, train_counts = count_images(train_dir)\nval_paths, val_labels, val_counts = count_images(val_dir)\ntest_paths, test_labels, test_counts = count_images(test_dir)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:02:07.179710Z","iopub.execute_input":"2026-03-12T16:02:07.179970Z","iopub.status.idle":"2026-03-12T16:02:07.227218Z","shell.execute_reply.started":"2026-03-12T16:02:07.179938Z","shell.execute_reply":"2026-03-12T16:02:07.226570Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(10,5))\n\nplt.bar(train_counts.keys(), train_counts.values())\n\nplt.title(\"Train Class Distribution\")\nplt.xlabel(\"Class\")\nplt.ylabel(\"Number of Images\")\n\nplt.ylim(350, 380)   # zoom trục y\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:02:07.228050Z","iopub.execute_input":"2026-03-12T16:02:07.228321Z","iopub.status.idle":"2026-03-12T16:02:07.467830Z","shell.execute_reply.started":"2026-03-12T16:02:07.228286Z","shell.execute_reply":"2026-03-12T16:02:07.467162Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Xem ngẫu nhiên các ảnh","metadata":{}},{"cell_type":"code","source":"image_paths = glob.glob(os.path.join(train_dir, \"*.jpg\"))\n\nsample = random.sample(image_paths, 64)\n\nplt.figure(figsize=(12,12))\n\nfor i, path in enumerate(sample):\n    \n    img = Image.open(path)\n    \n    plt.subplot(8,8,i+1)\n    plt.imshow(img)\n    plt.axis(\"off\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:02:07.468657Z","iopub.execute_input":"2026-03-12T16:02:07.468940Z","iopub.status.idle":"2026-03-12T16:02:09.784740Z","shell.execute_reply.started":"2026-03-12T16:02:07.468905Z","shell.execute_reply":"2026-03-12T16:02:09.783885Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Kiểm tra kích thước ảnh","metadata":{}},{"cell_type":"code","source":"sizes = []\n\nfor path in image_paths:\n    img = Image.open(path)\n    sizes.append(img.size)\n\nprint(Counter(sizes).most_common(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:02:09.785732Z","iopub.execute_input":"2026-03-12T16:02:09.785977Z","iopub.status.idle":"2026-03-12T16:03:17.122128Z","shell.execute_reply.started":"2026-03-12T16:02:09.785956Z","shell.execute_reply":"2026-03-12T16:03:17.121475Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Ta nhận thấy rằng kích thước ảnh đang không đồng nhất --> cần resize kích thước ảnh về `224 x 224`","metadata":{}},{"cell_type":"markdown","source":"# Dataset","metadata":{}},{"cell_type":"code","source":"class FoodDataset(Dataset):\n    \n    def __init__(self, image_paths, labels, transform=None):\n        self.image_paths = image_paths\n        self.labels = labels\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n\n        img = Image.open(self.image_paths[idx]).convert(\"RGB\")\n    \n        if self.transform:\n            img = self.transform(img)\n    \n        label = int(self.labels[idx])\n    \n        return img, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:17.123045Z","iopub.execute_input":"2026-03-12T16:03:17.123325Z","iopub.status.idle":"2026-03-12T16:03:17.128166Z","shell.execute_reply.started":"2026-03-12T16:03:17.123302Z","shell.execute_reply":"2026-03-12T16:03:17.127512Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Data augmentation","metadata":{}},{"cell_type":"markdown","source":"## *Data augmentation chỉ dùng cho tập `train`*","metadata":{}},{"cell_type":"code","source":"train_transform = transforms.Compose([\n    transforms.RandomResizedCrop(\n        384,\n        scale=(0.6,1.0),\n        ratio=(0.75,1.33)\n    ),\n    transforms.RandomHorizontalFlip(p=0.5),\n    transforms.RandomVerticalFlip(p=0.2),\n    transforms.RandomRotation(20),\n    transforms.RandomPerspective(distortion_scale=0.2, p=0.3),\n    transforms.ColorJitter(\n        brightness=0.3,\n        contrast=0.3,\n        saturation=0.2,\n        hue=0.1\n    ),\n    transforms.GaussianBlur(kernel_size=3, sigma=(0.1, 2.0)),\n    transforms.RandomAffine(degrees=0, translate=(0.1, 0.1)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485,0.456,0.406],\n        std=[0.229,0.224,0.225]\n    ),\n    transforms.RandomErasing(\n        p=0.25,\n        scale=(0.02,0.15)\n    )\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:17.130719Z","iopub.execute_input":"2026-03-12T16:03:17.130972Z","iopub.status.idle":"2026-03-12T16:03:17.145233Z","shell.execute_reply.started":"2026-03-12T16:03:17.130951Z","shell.execute_reply":"2026-03-12T16:03:17.144512Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Transform cho tập `valid`","metadata":{}},{"cell_type":"code","source":"val_transform = transforms.Compose([\n    transforms.Resize(256),\n    transforms.CenterCrop(224),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485,0.456,0.406],\n        std=[0.229,0.224,0.225]\n    )\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:17.146225Z","iopub.execute_input":"2026-03-12T16:03:17.146625Z","iopub.status.idle":"2026-03-12T16:03:17.157347Z","shell.execute_reply.started":"2026-03-12T16:03:17.146592Z","shell.execute_reply":"2026-03-12T16:03:17.156611Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dataloader","metadata":{}},{"cell_type":"code","source":"train_dataset = FoodDataset(train_paths, train_labels, transform=train_transform)\nval_dataset   = FoodDataset(val_paths, val_labels, transform=val_transform)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=128,\n    shuffle=True,\n    num_workers=4,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=128,\n    shuffle=False,\n    num_workers=4,\n    pin_memory=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:17.158433Z","iopub.execute_input":"2026-03-12T16:03:17.158702Z","iopub.status.idle":"2026-03-12T16:03:17.169427Z","shell.execute_reply.started":"2026-03-12T16:03:17.158682Z","shell.execute_reply":"2026-03-12T16:03:17.168697Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load pretrained model\n## Sử dụng ResNet-50","metadata":{}},{"cell_type":"code","source":"model_resnet = models.resnet50(weights=ResNet50_Weights.DEFAULT)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:17.170564Z","iopub.execute_input":"2026-03-12T16:03:17.170868Z","iopub.status.idle":"2026-03-12T16:03:18.316668Z","shell.execute_reply.started":"2026-03-12T16:03:17.170847Z","shell.execute_reply":"2026-03-12T16:03:18.315878Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Bổ sung backbone `effnet` và `convnext` để tăng accuracy","metadata":{}},{"cell_type":"code","source":"model_effnet = models.efficientnet_b4(weights=EfficientNet_B4_Weights.DEFAULT)\nmodel_convnext = models.convnext_tiny(weights=ConvNeXt_Tiny_Weights.DEFAULT)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:18.317663Z","iopub.execute_input":"2026-03-12T16:03:18.317991Z","iopub.status.idle":"2026-03-12T16:03:20.398771Z","shell.execute_reply.started":"2026-03-12T16:03:18.317965Z","shell.execute_reply":"2026-03-12T16:03:20.398103Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Transfer Learning","metadata":{}},{"cell_type":"code","source":"num_classes = 20\nmodel_resnet.fc = nn.Linear(\n    model_resnet.fc.in_features,\n    num_classes\n)\nmodel_effnet.classifier[1] = nn.Linear(\n    model_effnet.classifier[1].in_features,\n    num_classes\n)\nmodel_convnext.classifier[2] = nn.Linear(\n    model_convnext.classifier[2].in_features,\n    num_classes\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:20.399746Z","iopub.execute_input":"2026-03-12T16:03:20.400109Z","iopub.status.idle":"2026-03-12T16:03:20.406068Z","shell.execute_reply.started":"2026-03-12T16:03:20.400070Z","shell.execute_reply":"2026-03-12T16:03:20.405590Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Freeze backbone","metadata":{}},{"cell_type":"markdown","source":"## ResNet50","metadata":{}},{"cell_type":"code","source":"for param in model_resnet.parameters():\n    param.requires_grad = False\n\nfor param in model_resnet.layer4.parameters():\n    param.requires_grad = True\n\nfor param in model_resnet.fc.parameters():\n    param.requires_grad = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:20.406766Z","iopub.execute_input":"2026-03-12T16:03:20.406951Z","iopub.status.idle":"2026-03-12T16:03:20.419617Z","shell.execute_reply.started":"2026-03-12T16:03:20.406932Z","shell.execute_reply":"2026-03-12T16:03:20.418862Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## EfficientNet B4","metadata":{}},{"cell_type":"code","source":"for param in model_effnet.parameters():\n    param.requires_grad = False\n\nfor param in model_effnet.features[-1].parameters():\n    param.requires_grad = True\n\nfor param in model_effnet.classifier.parameters():\n    param.requires_grad = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:20.420590Z","iopub.execute_input":"2026-03-12T16:03:20.421106Z","iopub.status.idle":"2026-03-12T16:03:20.432093Z","shell.execute_reply.started":"2026-03-12T16:03:20.421083Z","shell.execute_reply":"2026-03-12T16:03:20.431478Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ConvNeXt Tiny","metadata":{}},{"cell_type":"code","source":"for param in model_convnext.parameters():\n    param.requires_grad = False\n\nfor param in model_convnext.features[-1].parameters():\n    param.requires_grad = True\n\nfor param in model_convnext.classifier.parameters():\n    param.requires_grad = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:20.433679Z","iopub.execute_input":"2026-03-12T16:03:20.433924Z","iopub.status.idle":"2026-03-12T16:03:20.443953Z","shell.execute_reply.started":"2026-03-12T16:03:20.433902Z","shell.execute_reply":"2026-03-12T16:03:20.443466Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Loss and Optimizer","metadata":{}},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss(label_smoothing=0.05)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:20.444904Z","iopub.execute_input":"2026-03-12T16:03:20.445148Z","iopub.status.idle":"2026-03-12T16:03:20.453893Z","shell.execute_reply.started":"2026-03-12T16:03:20.445121Z","shell.execute_reply":"2026-03-12T16:03:20.453281Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Cài đặt số epoch","metadata":{}},{"cell_type":"code","source":"num_epochs = 15","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Khởi tạo optimizer","metadata":{}},{"cell_type":"code","source":"optimizer_resnet = torch.optim.AdamW(\n    model_resnet.parameters(),\n    lr=1e-4,\n    weight_decay=1e-4\n)\n\noptimizer_effnet = torch.optim.AdamW(\n    model_effnet.parameters(),\n    lr=1e-4,\n    weight_decay=1e-4\n)\n\noptimizer_convnext = torch.optim.AdamW(\n    model_convnext.parameters(),\n    lr=1e-4,\n    weight_decay=1e-4\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Khởi tạo scheduler","metadata":{}},{"cell_type":"code","source":"scheduler_resnet = torch.optim.lr_scheduler.OneCycleLR(\n    optimizer_resnet,\n    max_lr=3e-4,\n    epochs=num_epochs,\n    steps_per_epoch=len(train_loader),\n    pct_start=0.3\n)\n\nscheduler_effnet = torch.optim.lr_scheduler.OneCycleLR(\n    optimizer_effnet,\n    max_lr=3e-4,\n    epochs=num_epochs,\n    steps_per_epoch=len(train_loader),\n    pct_start=0.3\n)\n\nscheduler_convnext = torch.optim.lr_scheduler.OneCycleLR(\n    optimizer_convnext,\n    max_lr=3e-4,\n    epochs=num_epochs,\n    steps_per_epoch=len(train_loader),\n    pct_start=0.3\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:20.454781Z","iopub.execute_input":"2026-03-12T16:03:20.455002Z","iopub.status.idle":"2026-03-12T16:03:20.468353Z","shell.execute_reply.started":"2026-03-12T16:03:20.454971Z","shell.execute_reply":"2026-03-12T16:03:20.467782Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training loop","metadata":{}},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel_resnet = model_resnet.to(device)\nmodel_effnet = model_effnet.to(device)\nmodel_convnext = model_convnext.to(device)\nprint(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:20.469297Z","iopub.execute_input":"2026-03-12T16:03:20.469587Z","iopub.status.idle":"2026-03-12T16:03:21.099223Z","shell.execute_reply.started":"2026-03-12T16:03:20.469558Z","shell.execute_reply":"2026-03-12T16:03:21.098516Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_model(model, model_name, train_loader, val_loader, optimizer, scheduler, num_epochs):\n\n    best_acc = 0\n    best_loss = float('inf')\n    model = model.to(device)\n\n    train_losses = []\n    val_losses = []\n\n    for epoch in range(num_epochs):\n\n        print(f\"\\nEpoch {epoch+1}\")\n\n        # ===== TRAIN =====\n        model.train()\n        correct = 0\n        total = 0\n        train_loss = 0.0\n\n        for images, labels in tqdm(train_loader, desc=\"Training\"):\n\n            images = images.to(device)\n            labels = labels.to(device)\n\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n            optimizer.zero_grad()\n            loss.backward()\n\n            torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n\n            optimizer.step()\n            scheduler.step()\n\n            train_loss += loss.item()\n\n            _, preds = torch.max(outputs, 1)\n            correct += (preds == labels).sum().item()\n            total += labels.size(0)\n\n        train_acc = correct / total\n        train_loss = train_loss / len(train_loader)\n        train_losses.append(train_loss)\n\n        # ===== VALIDATION =====\n        model.eval()\n        correct = 0\n        total = 0\n        val_loss = 0.0\n\n        with torch.no_grad():\n\n            for images, labels in tqdm(val_loader, desc=\"Validating\"):\n\n                images = images.to(device)\n                labels = labels.to(device)\n\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n\n                val_loss += loss.item()\n\n                _, preds = torch.max(outputs, 1)\n                correct += (preds == labels).sum().item()\n                total += labels.size(0)\n\n        val_acc = correct / total\n        val_loss = val_loss / len(val_loader)\n        val_losses.append(val_loss)\n\n        \n\n        lr = optimizer.param_groups[0][\"lr\"]\n\n        print(f\"Train Loss: {train_loss:.5f}, Train Acc: {train_acc:.5f}\")\n        print(f\"Val Loss: {val_loss:.5f}, Val Acc: {val_acc:.5f}\")\n        print(f\"LR: {lr:.6f}\")\n\n        # Save best model\n        if val_acc > best_acc:\n            best_acc = val_acc\n            best_loss = val_loss\n            torch.save(model.state_dict(), f\"{model_name}_best.pth\")\n            print(f\"✓ Model saved (Best Val Acc: {best_acc:.5f})\")\n\n    print(f\"\\nTraining completed. Best Val Acc: {best_acc:.5f}\")\n\n    return best_acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:21.100215Z","iopub.execute_input":"2026-03-12T16:03:21.100554Z","iopub.status.idle":"2026-03-12T16:03:21.110052Z","shell.execute_reply.started":"2026-03-12T16:03:21.100520Z","shell.execute_reply":"2026-03-12T16:03:21.109511Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_model(model_resnet, \"resnet\", train_loader, val_loader, optimizer_resnet, scheduler_resnet, num_epochs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:03:21.110958Z","iopub.execute_input":"2026-03-12T16:03:21.111224Z","iopub.status.idle":"2026-03-12T16:17:55.951434Z","shell.execute_reply.started":"2026-03-12T16:03:21.111190Z","shell.execute_reply":"2026-03-12T16:17:55.950613Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_model(model_effnet, \"effnet\", train_loader, val_loader, optimizer_effnet, scheduler_effnet, num_epochs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:17:55.952719Z","iopub.execute_input":"2026-03-12T16:17:55.952974Z","iopub.status.idle":"2026-03-12T16:32:38.709279Z","shell.execute_reply.started":"2026-03-12T16:17:55.952947Z","shell.execute_reply":"2026-03-12T16:32:38.708600Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_model(model_convnext, \"convnext\", train_loader, val_loader, optimizer_convnext, scheduler_convnext, num_epochs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:32:38.710642Z","iopub.execute_input":"2026-03-12T16:32:38.710889Z","iopub.status.idle":"2026-03-12T16:48:35.059080Z","shell.execute_reply.started":"2026-03-12T16:32:38.710863Z","shell.execute_reply":"2026-03-12T16:48:35.058390Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load best weigths","metadata":{}},{"cell_type":"code","source":"model_resnet.load_state_dict(torch.load(\"resnet_best.pth\"))\nmodel_effnet.load_state_dict(torch.load(\"effnet_best.pth\"))\nmodel_convnext.load_state_dict(torch.load(\"convnext_best.pth\"))\n\nmodel_resnet = model_resnet.to(device)\nmodel_effnet = model_effnet.to(device)\nmodel_convnext = model_convnext.to(device)\n\nmodel_resnet.eval()\nmodel_effnet.eval()\nmodel_convnext.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:49:58.625296Z","iopub.execute_input":"2026-03-12T16:49:58.625884Z","iopub.status.idle":"2026-03-12T16:49:59.107930Z","shell.execute_reply.started":"2026-03-12T16:49:58.625848Z","shell.execute_reply":"2026-03-12T16:49:59.107323Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Tạo test dataset","metadata":{}},{"cell_type":"code","source":"test_transform = val_transform\n\nclass TestDataset(Dataset):\n    def __init__(self, folder, transform=None):\n        self.paths = sorted([f for f in os.listdir(folder) if f.endswith('.jpg')], key=lambda x: int(x.split(\".\")[0]))\n        self.folder = folder\n        self.transform = transform\n        print(f\"Loaded {len(self.paths)} test images\")\n\n    def __len__(self):\n        return len(self.paths)\n\n    def __getitem__(self, idx):\n        img_path = os.path.join(self.folder, self.paths[idx])\n        if not os.path.exists(img_path):\n            raise FileNotFoundError(f\"Image not found: {img_path}\")\n        img = Image.open(img_path).convert(\"RGB\")\n\n        if self.transform:\n            img = self.transform(img)\n\n        return img, int(self.paths[idx].split(\".\")[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:50:06.035191Z","iopub.execute_input":"2026-03-12T16:50:06.035493Z","iopub.status.idle":"2026-03-12T16:50:06.048056Z","shell.execute_reply.started":"2026-03-12T16:50:06.035467Z","shell.execute_reply":"2026-03-12T16:50:06.047515Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dataset","metadata":{}},{"cell_type":"code","source":"test_dataset = TestDataset(test_dir, transform=test_transform)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size = 128,\n    shuffle = False,\n    num_workers = 4,\n    pin_memory = True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:50:08.016685Z","iopub.execute_input":"2026-03-12T16:50:08.016966Z","iopub.status.idle":"2026-03-12T16:50:08.026171Z","shell.execute_reply.started":"2026-03-12T16:50:08.016941Z","shell.execute_reply":"2026-03-12T16:50:08.025558Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Dự đoán","metadata":{}},{"cell_type":"code","source":"predictions = []\nids = []\n\n# Weights for ensemble (can be adjusted based on individual model performance)\nweights = [0.4, 0.35, 0.25]  # ResNet, EfficientNet, ConvNeXt\n\nwith torch.no_grad():\n    for images, image_ids in tqdm(test_loader, desc=\"Making predictions\"):\n\n        images = images.to(device)\n\n        out1 = model_resnet(images)\n        out2 = model_effnet(images)\n        out3 = model_convnext(images)\n\n        # Weighted average ensemble\n        outputs = (weights[0] * out1 + weights[1] * out2 + weights[2] * out3)\n\n        preds = torch.argmax(outputs, dim=1)\n\n        predictions.extend(preds.cpu().numpy())\n        ids.extend(image_ids.numpy())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:50:11.512853Z","iopub.execute_input":"2026-03-12T16:50:11.513149Z","iopub.status.idle":"2026-03-12T16:50:51.985196Z","shell.execute_reply.started":"2026-03-12T16:50:11.513124Z","shell.execute_reply":"2026-03-12T16:50:51.984415Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Tạo file submit","metadata":{}},{"cell_type":"code","source":"submission = pd.DataFrame({\n    \"ID\": ids,\n    \"label\": predictions\n})\n\nsubmission = submission.sort_values(\"ID\")\nsubmission.to_csv(\"new_submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:51:20.903545Z","iopub.execute_input":"2026-03-12T16:51:20.904267Z","iopub.status.idle":"2026-03-12T16:51:20.920636Z","shell.execute_reply.started":"2026-03-12T16:51:20.904232Z","shell.execute_reply":"2026-03-12T16:51:20.919857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!head submission.csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-12T16:50:52.002952Z","iopub.execute_input":"2026-03-12T16:50:52.003219Z","iopub.status.idle":"2026-03-12T16:50:52.160610Z","shell.execute_reply.started":"2026-03-12T16:50:52.003195Z","shell.execute_reply":"2026-03-12T16:50:52.159882Z"}},"outputs":[],"execution_count":null}]}