{"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":"none","dataSources":[{"sourceId":130681,"databundleVersionId":15683668,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":true,"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","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport glob\nfrom collections import Counter\nfrom PIL import Image\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\n\ntrain_dir = \"/kaggle/input/computer-vision-homework-2026/food_cv/train\"\nvalid_dir = \"/kaggle/input/computer-vision-homework-2026/food_cv/val\"\ntest_dir = \"/kaggle/input/computer-vision-homework-2026/food_cv/test\"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Count Images & Extract Labels\n\ndef analyze_splits(folder):\n    image_paths = glob.glob(os.path.join(folder, \"*.jpg\"))\n\n    labels = []\n    for path in image_paths:\n        filename = os.path.basename(path)\n        if \"_ \" in filename:\n            label = filename.split(\"_\")[0]\n            labels.append(label)\n\n    print(f\"\\nFolder: {folder}\")\n    print(f\"Total Images: {len(image_paths)}\")\n    print(f\"Unique classes: {len(set(labels))}\")\n\n    class_counts = Counter(labels)\n    print(\"Class Distribution:\")\n    print(class_counts)\n\n    return image_paths, labels, class_counts\n\ntrain_paths, train_labels, train_counts = analyze_splits(train_dir)\nvalid_paths, valid_labels, valid_counts = analyze_splits(valid_dir)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(12, 5))\nplt.bar(train_counts.keys(), train_counts.values())\nplt.title(\"Train class Distribution\")\nplt.xticks(rotation=90)\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_image_sizes(image_paths, sample_size=1000):\n    widths = []\n    heights = []\n\n    sample_paths = image_paths[:sample_size]\n\n    for path in tqdm(sample_paths):\n        try:\n            with Image.open(path) as img:\n                w, h = img.size\n                widths.append(w)\n                heights.append(h)\n        except:\n            continue\n\n    print(\"Width stats: \", min(widths), max(widths))\n    print(\"height stats: \", min(heights), max(heights))\n\n    return widths, heights\n\nwidths, heights = get_image_sizes(train_paths)\n                \n            ","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def check_channels(image_paths, sample_size=100):\n    modes = []\n    for path in image_paths[:sample_size]:\n        with Image.open(path) as img:\n            modes.append(img.mode)\n    print(\"Image Modes: \", Counter(modes))\n\ncheck_channels(train_paths)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Compute Dataset Mean & Std\n\ndef compute_mean_std(image_paths, sample_size=2000):\n    mean = np.zeros(3)\n    std = np.zeros(3)\n    total_pixels = 0\n\n    sample_paths = image_paths[:sample_size]\n\n    for path in tqdm(sample_paths):\n        img = Image.open(path).convert(\"RGB\")\n        img = np.array(img) / 255.0\n\n        pixels = img.reshape(-1, 3)\n        total_pixels += pixels.shape[0]\n\n        mean += pixels.sum(axis=0)\n\n    mean = mean / total_pixels\n\n    #Compute std\n    for path in tqdm(sample_paths):\n        img = Image.open(path).convert('RGB')\n        img = np.array(img) / 255.0\n        pixels = img.reshape(-1, 3)\n        std += ((pixels - mean) ** 2).sum(axis=0)\n\n    std = np.sqrt(std / total_pixels)\n\n    print(\"Mean: \", mean)\n    print(\"Std: \", std)\n\n    return mean, std\n\nmean, std = compute_mean_std(train_paths)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Check corrupted images\n\ndef check_corrupted(image_paths):\n    corrupted = []\n    for path in tqdm(image_paths):\n        try:\n            Image.open(path).verify()\n        except:\n            corrupted.append(path)\n\n    print(\"Corrupted Image: \", len(corrupted))\n    return corrupted\n\ncorrupted_train = check_corrupted(train_paths)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nfrom tqdm import tqdm\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader, Dataset\nfrom torchvision import datasets, transforms, models\nfrom torch.cuda.amp import GradScaler, autocast\nimport torch.optim as optim\nimport matplotlib.pyplot as plt\nfrom PIL import Image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T15:34:41.219410Z","iopub.execute_input":"2026-02-16T15:34:41.219587Z","iopub.status.idle":"2026-02-16T15:34:48.975868Z","shell.execute_reply.started":"2026-02-16T15:34:41.219568Z","shell.execute_reply":"2026-02-16T15:34:48.975213Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class 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\n        self.image_paths = sorted(os.listdir(folder_path))\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        filename = self.image_paths[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\n        else:\n            label = int(filename.split(\"_\")[0])\n            return image, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T15:34:52.802376Z","iopub.execute_input":"2026-02-16T15:34:52.802661Z","iopub.status.idle":"2026-02-16T15:34:52.808966Z","shell.execute_reply.started":"2026-02-16T15:34:52.802636Z","shell.execute_reply":"2026-02-16T15:34:52.808313Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_PATH = \"/kaggle/input/computer-vision-homework-2026/food_cv\"\n\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-16T15:35:58.084979Z","iopub.execute_input":"2026-02-16T15:35:58.085800Z","iopub.status.idle":"2026-02-16T15:35:58.137950Z","shell.execute_reply.started":"2026-02-16T15:35:58.085768Z","shell.execute_reply":"2026-02-16T15:35:58.137247Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Data Augmentation\n\ntrain_transform = transforms.Compose([\n    transforms.RandomResizedCrop(224),\n    transforms.RandomHorizontalFlip(),\n    transforms.RandomRotation(20),\n    transforms.ColorJitter(\n        brightness=0.2,\n        contrast=0.2,\n        saturation=0.2\n    ),\n    transforms.ToTensor(),\n    transforms.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225]\n        \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-16T15:36:02.042323Z","iopub.execute_input":"2026-02-16T15:36:02.043051Z","iopub.status.idle":"2026-02-16T15:36:02.048182Z","shell.execute_reply.started":"2026-02-16T15:36:02.043021Z","shell.execute_reply":"2026-02-16T15:36:02.047512Z"}},"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-16T15:36:04.871651Z","iopub.execute_input":"2026-02-16T15:36:04.871943Z","iopub.status.idle":"2026-02-16T15:36:04.989875Z","shell.execute_reply.started":"2026-02-16T15:36:04.871918Z","shell.execute_reply":"2026-02-16T15:36:04.989307Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Load ResNet50\n\nmodel = models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)\n\n#Replace Final Layer\nmodel.fc = nn.Linear(model.fc.in_features, NUM_CLASSES)\n\nmodel = model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T15:36:08.125668Z","iopub.execute_input":"2026-02-16T15:36:08.126409Z","iopub.status.idle":"2026-02-16T15:36:09.256092Z","shell.execute_reply.started":"2026-02-16T15:36:08.126379Z","shell.execute_reply":"2026-02-16T15:36:09.255332Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Loss, Optimizer and Scheduler\n\ncriterion = nn.CrossEntropyLoss()\n\noptimizer = optim.AdamW(model.parameters(), lr=1e-4, weight_decay=1e-4)\n\nscheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=15)\n\nscaler = GradScaler()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T15:36:13.022469Z","iopub.execute_input":"2026-02-16T15:36:13.023233Z","iopub.status.idle":"2026-02-16T15:36:13.028766Z","shell.execute_reply.started":"2026-02-16T15:36:13.023201Z","shell.execute_reply":"2026-02-16T15:36:13.027836Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"TRaining Loop(Mixed precision)","metadata":{}},{"cell_type":"code","source":"def train_one_epoch(model, loader):\n    model.train()\n    running_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        optimizer.zero_grad()\n\n        with autocast():\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        running_loss += loss.item()\n\n        _, predicted = outputs.max(1)\n        total += labels.size(0)\n        correct += predicted.eq(labels).sum().item()\n\n    acc = 100. * correct / total\n    return running_loss / len(loader), acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T15:37:05.242319Z","iopub.execute_input":"2026-02-16T15:37:05.242996Z","iopub.status.idle":"2026-02-16T15:37:05.248696Z","shell.execute_reply.started":"2026-02-16T15:37:05.242960Z","shell.execute_reply":"2026-02-16T15:37:05.248020Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def validate(model, loader):\n    model.eval()\n    running_loss = 0\n    correct = 0\n    total = 0\n\n    with torch.no_grad():\n        for images, labels in tqdm(loader):\n            images = images.to(device)\n            labels = labels.to(device)\n\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n            running_loss += loss.item()\n\n            _, predicted = outputs.max(1)\n            total += labels.size(0)\n            correct += predicted.eq(labels).sum().item()\n\n    acc = 100. * correct / total\n    return running_loss/len(loader), acc","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T15:37:08.663997Z","iopub.execute_input":"2026-02-16T15:37:08.664305Z","iopub.status.idle":"2026-02-16T15:37:08.669871Z","shell.execute_reply.started":"2026-02-16T15:37:08.664278Z","shell.execute_reply":"2026-02-16T15:37:08.669249Z"}},"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:.2f}\")\n    print(f\"Val Acc: {val_acc:.2f}\")\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-16T15:37:11.934174Z","iopub.execute_input":"2026-02-16T15:37:11.934516Z","iopub.status.idle":"2026-02-16T15:47:25.398003Z","shell.execute_reply.started":"2026-02-16T15:37:11.934490Z","shell.execute_reply":"2026-02-16T15:47:25.397249Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Test Prediction and submission","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-16T15:47:33.985361Z","iopub.execute_input":"2026-02-16T15:47:33.985980Z","iopub.status.idle":"2026-02-16T15:47:34.125499Z","shell.execute_reply.started":"2026-02-16T15:47:33.985946Z","shell.execute_reply":"2026-02-16T15:47:34.124787Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Create Test Dataset\n\ntest_transform = val_transform\n\nclass TestDataset(Dataset):\n    def __init__(self, folder, transform=None):\n        self.paths = sorted(os.listdir(folder), key=lambda x: int(x.split(\".\")[0]))\n        self.folder = folder\n        self.transform = transform\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        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])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T15:47:40.119342Z","iopub.execute_input":"2026-02-16T15:47:40.119877Z","iopub.status.idle":"2026-02-16T15:47:40.125802Z","shell.execute_reply.started":"2026-02-16T15:47:40.119851Z","shell.execute_reply":"2026-02-16T15:47:40.125160Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Dataset\n\ntest_dataset = TestDataset(TEST_DIR, transform=test_transform)\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-16T15:47:42.221597Z","iopub.execute_input":"2026-02-16T15:47:42.222382Z","iopub.status.idle":"2026-02-16T15:47:42.273714Z","shell.execute_reply.started":"2026-02-16T15:47:42.222351Z","shell.execute_reply":"2026-02-16T15:47:42.273042Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Generate predictions\n\npredictions = []\n\nwith torch.no_grad():\n    for images, ids in tqdm(test_loader):\n        images = images.to(device)\n        outputs = model(images)\n        _, preds = outputs.max(1)\n\n        for id_, pred in zip(ids, preds):\n            predictions.append([id_.item(), pred.item()])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T15:47:44.377440Z","iopub.execute_input":"2026-02-16T15:47:44.378005Z","iopub.status.idle":"2026-02-16T15:47:57.096639Z","shell.execute_reply.started":"2026-02-16T15:47:44.377977Z","shell.execute_reply":"2026-02-16T15:47:57.095836Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#Create submission file\n\nsubmission = pd.DataFrame(predictions, columns=[\"ID\", \" label\"])\nsubmission = submission.sort_values(\"ID\")\n\nsubmission.to_csv(\"submission.csv\", index=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T16:29:03.707638Z","iopub.execute_input":"2026-02-16T16:29:03.708454Z","iopub.status.idle":"2026-02-16T16:29:03.719439Z","shell.execute_reply.started":"2026-02-16T16:29:03.708423Z","shell.execute_reply":"2026-02-16T16:29:03.718807Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!head submission.csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-16T16:29:06.590565Z","iopub.execute_input":"2026-02-16T16:29:06.591196Z","iopub.status.idle":"2026-02-16T16:29:06.730959Z","shell.execute_reply.started":"2026-02-16T16:29:06.591167Z","shell.execute_reply":"2026-02-16T16:29:06.730265Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}