{"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":[{"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":"First section: Setup for Loading data\n","metadata":{}},{"cell_type":"code","source":"from torchvision.io import read_image\nimport torchvision.transforms.v2 as v2\nimport os\nfrom torch.utils.data import Dataset\nimport torch\n\nclass ImageDataset(Dataset):\n\n    def __init__(self, imgs_path: str = None, transform = None):\n        super().__init__()\n        \n        images: list[str] = list(sorted(os.listdir(imgs_path)))\n        labels: list[int] = [int(x.split(\"_\")[0]) for x in images]\n        \n        self.img_paths = images\n        self.labels = labels\n        self.root = imgs_path\n        self.transform = transform\n        \n    def __getitem__(self, index: int = None) -> tuple[torch.Tensor, int]:\n        img = read_image(os.path.join(self.root, self.img_paths[index]))\n        if self.transform:\n            img = self.transform(img)\n        label = self.labels[index]\n        return img, label\n        \n    def __len__(self) -> int:\n        return len(self.labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:29.091994Z","iopub.execute_input":"2026-03-20T14:43:29.092703Z","iopub.status.idle":"2026-03-20T14:43:34.554214Z","shell.execute_reply.started":"2026-03-20T14:43:29.092674Z","shell.execute_reply":"2026-03-20T14:43:34.553629Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_tf: v2.Compose = v2.Compose([\n    v2.Resize(256),\n    v2.RandomResizedCrop(224, scale = (0.7, 1.0)),\n    v2.RandomHorizontalFlip(0.5),\n    v2.RandomRotation(30),\n    v2.GaussianBlur((3,5)),\n    v2.ColorJitter(0.15, 0.15, 0.05, 0.01),\n    v2.ToDtype(torch.float32, scale = True),\n    v2.Normalize(\n        mean = [0.485, 0.456, 0.406],\n        std  = [0.229, 0.224, 0.225]\n    )\n])\n\ntest_tf: v2.Compose = v2.Compose([\n    v2.Resize(256),\n    v2.CenterCrop(224),\n    v2.ToDtype(torch.float32, scale = True),\n    v2.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-20T14:43:34.555527Z","iopub.execute_input":"2026-03-20T14:43:34.555873Z","iopub.status.idle":"2026-03-20T14:43:34.58964Z","shell.execute_reply.started":"2026-03-20T14:43:34.555844Z","shell.execute_reply":"2026-03-20T14:43:34.589133Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"root_path: str = \"/kaggle/input/competitions/computer-vision-homework-2026/food_cv\"\n\nTrainingDataset = ImageDataset(os.path.join(root_path, \"train\"))\nValidationDataset = ImageDataset(os.path.join(root_path, \"val\"))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:34.590346Z","iopub.execute_input":"2026-03-20T14:43:34.590582Z","iopub.status.idle":"2026-03-20T14:43:34.72125Z","shell.execute_reply.started":"2026-03-20T14:43:34.590549Z","shell.execute_reply":"2026-03-20T14:43:34.720716Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Step 2: #Preparaing training and testing transformations","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport random\n\n\nfig, axes = plt.subplots(4,4)\nfor i in range(4):\n    for j in range(4):\n        random_index = random.randint(0, len(TrainingDataset))\n        img, label = TrainingDataset[random_index]\n        img = img.permute(1, 2, 0).numpy()\n        axes[i , j].imshow(img, cmap = \"viridis\")\n        axes[i,j].set_title(f\"label: {label}\", loc = \"center\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:34.722022Z","iopub.execute_input":"2026-03-20T14:43:34.722302Z","iopub.status.idle":"2026-03-20T14:43:36.253844Z","shell.execute_reply.started":"2026-03-20T14:43:34.722266Z","shell.execute_reply":"2026-03-20T14:43:36.253121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"random_index = random.randint(0, len(TrainingDataset))\nimg, _ = TrainingDataset[random_index]\nfig_2, axes_2 = plt.subplots(1, 2)\naxes_2[0].imshow(img.permute(1, 2, 0).numpy(), cmap = \"viridis\")\nimg = train_tf(img)\naxes_2[1].imshow(img.permute(1, 2, 0).numpy(), cmap = \"viridis\")\nplt.title(\"Image Before ----- | ------ After Training\", loc = \"left\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:36.255743Z","iopub.execute_input":"2026-03-20T14:43:36.256462Z","iopub.status.idle":"2026-03-20T14:43:36.659207Z","shell.execute_reply.started":"2026-03-20T14:43:36.256428Z","shell.execute_reply":"2026-03-20T14:43:36.658424Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TrainingDataset.transform = train_tf\nValidationDataset.transform = test_tf","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:36.660184Z","iopub.execute_input":"2026-03-20T14:43:36.660491Z","iopub.status.idle":"2026-03-20T14:43:36.664053Z","shell.execute_reply.started":"2026-03-20T14:43:36.660467Z","shell.execute_reply":"2026-03-20T14:43:36.663355Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Step 3: Define architecture\n","metadata":{}},{"cell_type":"code","source":"import torch.nn as nn\nimport torchvision.models as models\n\nprint(models.resnet18())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:36.66501Z","iopub.execute_input":"2026-03-20T14:43:36.66524Z","iopub.status.idle":"2026-03-20T14:43:36.831236Z","shell.execute_reply.started":"2026-03-20T14:43:36.66522Z","shell.execute_reply":"2026-03-20T14:43:36.830595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\nclass_counts: dict = {}\nplt.title(\"-----  Class counts -----\")\nfor label in TrainingDataset.labels:\n    if label not in class_counts:\n        class_counts[label] = 0\n    class_counts[label] += 1\nsns.barplot(\n    x = class_counts.keys(),\n    y = class_counts.values(),\n    hue = class_counts.keys(),\n    width = 0.8,\n    alpha = 0.9,\n    palette = \"tab10\",\n    zorder = 3\n)\nplt.grid(True)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:36.832244Z","iopub.execute_input":"2026-03-20T14:43:36.832479Z","iopub.status.idle":"2026-03-20T14:43:39.640758Z","shell.execute_reply.started":"2026-03-20T14:43:36.832457Z","shell.execute_reply":"2026-03-20T14:43:39.640164Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:39.641709Z","iopub.execute_input":"2026-03-20T14:43:39.642288Z","iopub.status.idle":"2026-03-20T14:43:39.691543Z","shell.execute_reply.started":"2026-03-20T14:43:39.642207Z","shell.execute_reply":"2026-03-20T14:43:39.690708Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class_weights = [class_counts[x] for x in range(len(class_counts))]\nclass_weights: torch.Tensor = torch.Tensor(list(map(lambda x: x/max(class_weights), class_weights)))\nclass_weights = class_weights.to(device)\nprint(class_weights)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:39.692629Z","iopub.execute_input":"2026-03-20T14:43:39.693159Z","iopub.status.idle":"2026-03-20T14:43:40.1531Z","shell.execute_reply.started":"2026-03-20T14:43:39.693133Z","shell.execute_reply":"2026-03-20T14:43:40.152238Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch.nn as nn\nimport torchvision.models as models\n\nclass ChannelAttention(nn.Module):\n\n    def __init__(self, in_channels, reduction = 16):\n\n        super().__init__()\n        self.avg_pool = nn.AdaptiveAvgPool2d((1,1))\n        self.max_pool = nn.AdaptiveMaxPool2d((1,1))\n        self.bottleneck = nn.Sequential(\n            nn.Conv2d(in_channels, in_channels//reduction, 1, bias = False),\n            nn.ReLU(),\n            nn.Conv2d(in_channels//reduction, in_channels, 1, bias = False),\n        )\n        self.sig = nn.Sigmoid()\n        \n    def forward(self, x: torch.Tensor):\n        avg_out = self.bottleneck(self.avg_pool(x))\n        max_out = self.bottleneck(self.max_pool(x))\n        out_sum = avg_out + max_out\n        return x * self.sig(out_sum)\n        \nclass SpatialAttention(nn.Module):\n\n    def __init__(self, kernel_size: int = 7):\n\n        super().__init__()\n        padding = kernel_size // 2\n        self.conv = nn.Conv2d(\n            2,\n            1,\n            kernel_size = kernel_size,\n            stride = 1,\n            padding = padding,\n        )\n        self.sig = nn.Sigmoid()\n        \n    def forward(self, x: torch.Tensor):\n        \n        avg_out = torch.mean(x, dim = 1, keepdim = True)\n        max_out, _  = torch.max(x, dim = 1, keepdim = True)\n        \n        weights = self.conv(torch.cat([avg_out, max_out], dim = 1))\n        \n        return x * self.sig(weights)\nclass CNNModel(nn.Module):\n\n    def __init__(self, num_classes: int):\n        super().__init__()\n        self.backbone = models.resnet18(weights = models.ResNet18_Weights.IMAGENET1K_V1)\n        self.fc = nn.Identity()\n        \n        for param in self.backbone.parameters():\n            param.requires_grad = False\n\n        for param in self.backbone.layer4.parameters():\n            param.requires_grad = True\n            \n        for param in self.backbone.layer3.parameters():\n            param.requires_grad = True\n            \n        \n        self.fc = nn.Sequential(\n            nn.Flatten(),\n\n            nn.Linear(512, 128, bias = False),\n            nn.BatchNorm1d(128),\n            nn.ELU(),\n            nn.Dropout(0.5),\n\n\n            nn.Linear(128, 32, bias = False),\n            nn.BatchNorm1d(32),\n            nn.ELU(),\n            nn.Dropout(0.5),\n            \n            nn.Linear(32, num_classes, bias = True)\n        )\n    def forward(self, x: torch.Tensor) -> torch.Tensor:\n        \n        x = self.backbone.conv1(x)\n        x = self.backbone.bn1(x)\n        x = self.backbone.relu(x)\n        x = self.backbone.maxpool(x)\n        \n        x = self.backbone.layer1(x)\n        x = self.backbone.layer2(x)      \n        x = self.backbone.layer3(x)\n        x = self.backbone.layer4(x)\n        \n        x = self.backbone.avgpool(x)\n        x = self.fc(x)\n        \n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:40.154189Z","iopub.execute_input":"2026-03-20T14:43:40.154559Z","iopub.status.idle":"2026-03-20T14:43:40.166794Z","shell.execute_reply.started":"2026-03-20T14:43:40.154534Z","shell.execute_reply":"2026-03-20T14:43:40.166154Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\ndef UpdateLoss(loss: torch.Tensor, labels: torch.Tensor, prev_cw_losses: torch.Tensor) -> torch.Tensor:\n\n    with torch.no_grad():\n        cw_losses: torch.Tensor = torch.zeros(len(class_counts), device = device)\n        cw_counts: torch.Tensor = torch.zeros(len(class_counts), device = device)\n        \n        cw_losses.scatter_add_(0, labels, loss)\n        cw_counts.scatter_add_(0, labels, torch.ones_like(loss))\n        \n        mask = cw_counts > 0\n        avg = cw_losses[mask] / cw_counts[mask]\n        prev_cw_losses[mask] = prev_cw_losses[mask]*0.9 + avg*0.1\n        scale = 2*(1/np.log(len(class_counts)))\n    return (loss * (1 + prev_cw_losses[labels] * scale) * class_weights[labels]).mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:40.167658Z","iopub.execute_input":"2026-03-20T14:43:40.167972Z","iopub.status.idle":"2026-03-20T14:43:40.182159Z","shell.execute_reply.started":"2026-03-20T14:43:40.167949Z","shell.execute_reply":"2026-03-20T14:43:40.181608Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"num_epochs = 50\nimport torch.nn.init as init\nfrom torch.optim import AdamW\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\n\ndef xavier_update(layer) -> None:\n    if isinstance(layer, (nn.Linear , nn.Conv2d)):\n        init.xavier_uniform_(layer.weight)\n        if layer.bias is not None:\n            init.zeros_(layer.bias)\n\nmodel: CNNModel = CNNModel(len(class_counts))\nparam_group: list[dict[nn.Module]] = [\n    {\"params\": model.backbone.layer4.parameters(), \"lr\": 5e-5},\n    {\"params\": model.backbone.layer3.parameters(), \"lr\": 1e-5},\n\n    {\"params\": model.fc.parameters(), \"lr\": 2e-4}\n]\noptimizer: AdamW = AdamW(param_group, lr = 1e-4, weight_decay = 0.01)\ncriterion: nn.CrossEntropyLoss = nn.CrossEntropyLoss(reduction = \"none\", label_smoothing = 0.05)\nscheduler: CosineAnnealingLR = CosineAnnealingLR(optimizer, num_epochs, eta_min = 5e-5)\nevaluator: nn.CrossEntropyLoss = nn.CrossEntropyLoss()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:40.183135Z","iopub.execute_input":"2026-03-20T14:43:40.183406Z","iopub.status.idle":"2026-03-20T14:43:40.420307Z","shell.execute_reply.started":"2026-03-20T14:43:40.183377Z","shell.execute_reply":"2026-03-20T14:43:40.419624Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.fc.apply(xavier_update)\n\nmodel.to(device)\ncriterion.to(device)\nevaluator.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:40.422841Z","iopub.execute_input":"2026-03-20T14:43:40.423093Z","iopub.status.idle":"2026-03-20T14:43:40.445791Z","shell.execute_reply.started":"2026-03-20T14:43:40.42307Z","shell.execute_reply":"2026-03-20T14:43:40.445269Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from torch.utils.data import DataLoader\n\nbatch_size = 64\nTrainingLoader = DataLoader(TrainingDataset, batch_size, shuffle = True, num_workers = 4, pin_memory = True)\nValLoader = DataLoader(ValidationDataset, batch_size, shuffle = True, num_workers = 4, pin_memory = True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:40.446611Z","iopub.execute_input":"2026-03-20T14:43:40.446886Z","iopub.status.idle":"2026-03-20T14:43:40.451207Z","shell.execute_reply.started":"2026-03-20T14:43:40.446856Z","shell.execute_reply":"2026-03-20T14:43:40.450422Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for col in [\"Train Accuracy\", \"Val Accuracy\"]:\n    items = []\n    for item in epoch_history[col]:\n        items.append(item.item())\n    epoch_history[col] = items\n\nimport pprint\n\npprint.pprint(epoch_history)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:40.452127Z","iopub.execute_input":"2026-03-20T14:43:40.45242Z","iopub.status.idle":"2026-03-20T14:43:40.466504Z","shell.execute_reply.started":"2026-03-20T14:43:40.452384Z","shell.execute_reply":"2026-03-20T14:43:40.465604Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import seaborn as sns\n\nfig_3, axes_3 = plt.subplots(1, 2)\nindices = list(range(len(epoch_history[\"Train Loss\"])))\naxes_3[0].grid(True)\naxes_3[1].grid(True)\naxes_3[0].plot(\n    indices,\n    epoch_history[\"Train Loss\"],\n    color = \"blue\",\n    label = \"Train Loss\"\n)\naxes_3[0].plot(\n    indices,\n    epoch_history[\"Val Loss\"],\n    color = \"orange\",\n    label = \"Val Loss\"\n)\n\naxes_3[1].plot(\n    indices,\n    epoch_history[\"Train Accuracy\"],\n    color = \"blue\",\n    label = \"Train Accuracy\"\n)\naxes_3[1].plot(\n    indices,\n    epoch_history[\"Val Accuracy\"],\n    color = \"orange\",\n    label = \"Val Accuracy\"\n)\nplt.legend()\nplt.tight_layout()\nplt.title(\"Training and validation loss + accuracy\", loc = \"left\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:43:40.467178Z","iopub.status.idle":"2026-03-20T14:43:40.467409Z","shell.execute_reply.started":"2026-03-20T14:43:40.467297Z","shell.execute_reply":"2026-03-20T14:43:40.46731Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"eval_model = CNNModel(len(class_counts))\neval_model.load_state_dict(torch.load(\"/kaggle/working/best_model.pth\")[\"model_state_dict\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:44:00.282242Z","iopub.execute_input":"2026-03-20T14:44:00.282874Z","iopub.status.idle":"2026-03-20T14:44:00.664968Z","shell.execute_reply.started":"2026-03-20T14:44:00.282842Z","shell.execute_reply":"2026-03-20T14:44:00.664321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"eval_model.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:44:37.800096Z","iopub.execute_input":"2026-03-20T14:44:37.800616Z","iopub.status.idle":"2026-03-20T14:44:37.824647Z","shell.execute_reply.started":"2026-03-20T14:44:37.800585Z","shell.execute_reply":"2026-03-20T14:44:37.824104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"eval_model.eval()\ntest_img_paths = os.listdir(\"/kaggle/input/competitions/computer-vision-homework-2026/food_cv/test\")\nroot = \"/kaggle/input/competitions/computer-vision-homework-2026/food_cv/test\"\npreds = []\nidxs = []\nwith torch.no_grad():\n    for idx in range(len(test_img_paths)):\n        path = f\"{idx}.jpg\"\n        img = read_image(os.path.join(root, path))\n        \n        # 1. Transform first (Result: [C, H, W])\n        img = test_tf(img) \n        \n        # 2. Unsqueeze second (Result: [1, C, H, W])\n        img = img.unsqueeze(0)\n        \n\n        img = img.to(device)\n        output = eval_model(img)\n        _, pred = torch.max(output, 1)\n        \n        preds.append(pred.item())\n        idxs.append(idx)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:44:41.687139Z","iopub.execute_input":"2026-03-20T14:44:41.687425Z","iopub.status.idle":"2026-03-20T14:45:39.387674Z","shell.execute_reply.started":"2026-03-20T14:44:41.6874Z","shell.execute_reply":"2026-03-20T14:45:39.3866Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n\ndf = pd.DataFrame({\n    \"ID\" : idxs,\n    \"label\": preds\n})\n\nprint(df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:50:00.967304Z","iopub.execute_input":"2026-03-20T14:50:00.967682Z","iopub.status.idle":"2026-03-20T14:50:00.977616Z","shell.execute_reply.started":"2026-03-20T14:50:00.967653Z","shell.execute_reply":"2026-03-20T14:50:00.976849Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df.to_csv(\"result.csv\", index = False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-03-20T14:50:04.274665Z","iopub.execute_input":"2026-03-20T14:50:04.275035Z","iopub.status.idle":"2026-03-20T14:50:04.282411Z","shell.execute_reply.started":"2026-03-20T14:50:04.275008Z","shell.execute_reply":"2026-03-20T14:50:04.28176Z"}},"outputs":[],"execution_count":null}]}