{"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":"gpu","dataSources":[{"sourceId":130681,"databundleVersionId":15683668,"sourceType":"competition"}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\nimport timm\nfrom torch.cuda.amp import GradScaler, autocast\nimport torch.optim as optim","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:03:55.397387Z","iopub.execute_input":"2026-02-18T19:03:55.397627Z","iopub.status.idle":"2026-02-18T19:04:07.622681Z","shell.execute_reply.started":"2026-02-18T19:03:55.397601Z","shell.execute_reply":"2026-02-18T19:04:07.621930Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_PATH = \"/kaggle/input/computer-vision-homework-2026/food_cv\"\nTRAIN_DIR = os.path.join(DATA_PATH, \"train\")\nVAL_DIR = os.path.join(DATA_PATH, \"val\")\nTEST_DIR = os.path.join(DATA_PATH, \"test\")\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:07.623536Z","iopub.execute_input":"2026-02-18T19:04:07.623761Z","iopub.status.idle":"2026-02-18T19:04:07.675261Z","shell.execute_reply.started":"2026-02-18T19:04:07.623739Z","shell.execute_reply":"2026-02-18T19:04:07.674358Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Train and val dataset\n\nclass FoodDataset(Dataset):\n    def __init__(self, folder_path, transform=None, is_test=False):\n        self.folder_path = folder_path\n        self.transform = transform\n        self.is_test = is_test\n        self.image_files = sorted(os.listdir(folder_path))\n\n    def __len__(self):\n        return len(self.image_files)\n\n    def __getitem__(self, idx):\n        filename = self.image_files[idx]\n        img_path = os.path.join(self.folder_path, filename)\n\n        image = Image.open(img_path).convert(\"RGB\")\n\n        if self.transform:\n            image = self.transform(image)\n\n        if self.is_test:\n            img_id = int(filename.split(\".\")[0])\n            return image, img_id\n        else:\n            label = int(filename.split(\"_\")[0])\n            return image, label\n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:07.677207Z","iopub.execute_input":"2026-02-18T19:04:07.677473Z","iopub.status.idle":"2026-02-18T19:04:07.689293Z","shell.execute_reply.started":"2026-02-18T19:04:07.677453Z","shell.execute_reply":"2026-02-18T19:04:07.688583Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Advanced Augmentation Strategy\n\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(224, scale=(0.6, 1.0)),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandAugment(num_ops=2, magnitude=9),\n    transforms.ColorJitter(0.4, 0.4, 0.4, 0.2),\n    transforms.ToTensor(),\n    transforms.RandomErasing(p=0.3),\n    transforms.Normalize(\n        [0.485, 0.456, 0.406],\n        [0.229, 0.224, 0.225]\n    )\n])\n\nval_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        [0.485, 0.456, 0.406],\n        [0.229, 0.224, 0.225]\n    )\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:07.690114Z","iopub.execute_input":"2026-02-18T19:04:07.690464Z","iopub.status.idle":"2026-02-18T19:04:07.701167Z","shell.execute_reply.started":"2026-02-18T19:04:07.690440Z","shell.execute_reply":"2026-02-18T19:04:07.700519Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Load dataset\n\ntrain_dataset = FoodDataset(TRAIN_DIR, transform=train_transform)\nval_dataset = FoodDataset(VAL_DIR, transform=val_transform)\n\nNUM_CLASSES = 20\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=32,\n    shuffle=True,\n    num_workers=4,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=32,\n    shuffle=False,\n    num_workers=4,\n    pin_memory=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:07.702005Z","iopub.execute_input":"2026-02-18T19:04:07.702249Z","iopub.status.idle":"2026-02-18T19:04:07.830701Z","shell.execute_reply.started":"2026-02-18T19:04:07.702226Z","shell.execute_reply":"2026-02-18T19:04:07.829985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Load EfficientNet-B0\n\nmodel = timm.create_model(\"efficientnet_b0\", pretrained=True)\nmodel.classifier = nn.Linear(model.classifier.in_features, NUM_CLASSES)\nmodel = model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:07.832257Z","iopub.execute_input":"2026-02-18T19:04:07.832523Z","iopub.status.idle":"2026-02-18T19:04:09.270081Z","shell.execute_reply.started":"2026-02-18T19:04:07.832497Z","shell.execute_reply":"2026-02-18T19:04:09.269472Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Loss, Optimizer, Scheduler\n\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\noptimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)\nscheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=15)\n\nscaler = GradScaler()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:09.270949Z","iopub.execute_input":"2026-02-18T19:04:09.271225Z","iopub.status.idle":"2026-02-18T19:04:09.277217Z","shell.execute_reply.started":"2026-02-18T19:04:09.271201Z","shell.execute_reply":"2026-02-18T19:04:09.276335Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#MixUp Implementation\n\ndef mixup_data(x, y, alpha=0.4):\n    lam = np.random.beta(alpha, alpha)\n    batch_size = x.size()[0]\n    index = torch.randperm(batch_size).cuda()\n\n    mixed_x = lam * 2 + (1-lam) * x[index, :]\n    y_a, y_b = y, y[index]\n    return mixed_x, y_a, y_b, lam","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:09.278220Z","iopub.execute_input":"2026-02-18T19:04:09.278715Z","iopub.status.idle":"2026-02-18T19:04:09.290105Z","shell.execute_reply.started":"2026-02-18T19:04:09.278680Z","shell.execute_reply":"2026-02-18T19:04:09.289421Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Training Functions\n\ndef train_one_epoch(model, loader):\n    model.train()\n    total_loss = 0\n    correct = 0\n    total = 0\n\n    for images, labels in tqdm(loader):\n        images = images.to(device)\n        labels = labels.to(device)\n\n        images, target_a, target_b, lam = mixup_data(images, labels)\n\n        optimizer.zero_grad()\n\n        with autocast():\n            outputs = model(images)\n            loss = criterion(outputs, target_a) + (1 - lam) * criterion(outputs, target_b)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        total_loss += loss.item()\n\n        _, preds = outputs.max(1)\n        correct += preds.eq(labels).sum().item()\n        total += labels.size(0)\n\n    return total_loss/len(loader), 100*correct/total","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:05:13.942677Z","iopub.execute_input":"2026-02-18T19:05:13.943024Z","iopub.status.idle":"2026-02-18T19:05:13.949595Z","shell.execute_reply.started":"2026-02-18T19:05:13.942989Z","shell.execute_reply":"2026-02-18T19:05:13.948926Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def validate(model, loader):\n    model.eval()\n    total_loss = 0\n    correct = 0\n    total = 0\n\n    with torch.no_grad():\n        for images, labels in loader:\n            images = images.to(device)\n            labels = labels.to(device)\n\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n            total_loss += loss.item()\n\n            _, preds = outputs.max(1)\n            correct += preds.eq(labels).sum().item()\n            total += labels.size(0)\n\n    return total_loss/len(loader), 100*correct/total","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:05:14.924357Z","iopub.execute_input":"2026-02-18T19:05:14.924985Z","iopub.status.idle":"2026-02-18T19:05:14.929925Z","shell.execute_reply.started":"2026-02-18T19:05:14.924960Z","shell.execute_reply":"2026-02-18T19:05:14.929176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Train model\n\nbest_acc = 0\n\nfor epoch in range(15):\n    train_loss, train_acc = train_one_epoch(model, train_loader)\n    val_loss, val_acc = validate(model, val_loader)\n\n    scheduler.step()\n\n    print(f\"\\nEpoch {epoch+1}\")\n    print(f\"Train Acc: {train_acc:.4f}\")\n    print(f\"Val Acc: {val_acc:.4f}\")\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(model.state_dict(), \"best_model.pth\")\n\nprint(\"Best Validation Accuracy: \", best_acc)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:05:15.869765Z","iopub.execute_input":"2026-02-18T19:05:15.870072Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Test Predictions","metadata":{}},{"cell_type":"code","source":"#Load Best Model\n\nmodel.load_state_dict(torch.load(\"best_model.pth\"))\nmodel.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:11.186034Z","iopub.status.idle":"2026-02-18T19:04:11.186355Z","shell.execute_reply.started":"2026-02-18T19:04:11.186214Z","shell.execute_reply":"2026-02-18T19:04:11.186234Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#TTA Transform\n\ntta_transforms = [\n\n    transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.ToTensor(),\n        transforms.Normalize(\n            [0.485, 0.456, 0.406],\n            [0.229, 0.224, 0.225]\n        )\n    ]),\n    transforms.Compose([\n        transforms.Resize((224, 224)),\n        transforms.RandomHorizontalFlip(p=0.1),\n        transforms.ToTensor(),\n        transforms.Normalize(\n            [0.485, 0.456, 0.406],\n            [0.229, 0.224, 0.225]\n        )\n    ])\n]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:11.187550Z","iopub.status.idle":"2026-02-18T19:04:11.187850Z","shell.execute_reply.started":"2026-02-18T19:04:11.187725Z","shell.execute_reply":"2026-02-18T19:04:11.187741Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Test Dataset\n\ntest_dataset = FoodDataset(TEST_DIR, transform=val_transform, is_test=True)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=32,\n    shuffle=False,\n    num_workers=4\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:11.188867Z","iopub.status.idle":"2026-02-18T19:04:11.189182Z","shell.execute_reply.started":"2026-02-18T19:04:11.189037Z","shell.execute_reply":"2026-02-18T19:04:11.189053Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Generate TTA Predictions\n\ntest_folder = os.path.join(DATA_PATH, \"test\")\ntest_files = sorted(os.listdir(test_folder))\n\npredictions = []\n\nfor file in tqdm(test_files):\n    img = Image.open(os.path.join(test_folder, file)).convert(\"RGB\")\n\n    logits_sum = 0\n\n    for tform in tta_transforms:\n        input_tensor = tform(img).unsqueeze(0).cuda()\n        with torch.no_grad():\n            logits = model(input_tensor)\n        logits_sum += logits\n\n    logits_avg = logits_sum / len(tta_transforms)\n    pred = logits_avg.argmax(1).item()\n\n    img_id = int(file.split(\".\")[0])\n    predictions.append([img_id, pred])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:11.190093Z","iopub.status.idle":"2026-02-18T19:04:11.190369Z","shell.execute_reply.started":"2026-02-18T19:04:11.190252Z","shell.execute_reply":"2026-02-18T19:04:11.190266Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Submission File\n\nsubmission = pd.DataFrame(predictions, columns=[\"ID\", \"label\"])\nsubmission = submission.sort_values(\"ID\")\n\nsubmission.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:11.191689Z","iopub.status.idle":"2026-02-18T19:04:11.192023Z","shell.execute_reply.started":"2026-02-18T19:04:11.191852Z","shell.execute_reply":"2026-02-18T19:04:11.191873Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!head submission.csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T19:04:11.193274Z","iopub.status.idle":"2026-02-18T19:04:11.193546Z","shell.execute_reply.started":"2026-02-18T19:04:11.193416Z","shell.execute_reply":"2026-02-18T19:04:11.193436Z"}},"outputs":[],"execution_count":null}]}