{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"},{"sourceId":13128551,"sourceType":"datasetVersion","datasetId":8316885}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Cell 1 — Installs (run first)\n!apt-get update -qq\n# Graphviz used if you want architecture rendering later (optional)\n!apt-get install -y graphviz -qq\n!pip install -q timm","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-05T12:57:10.841154Z","iopub.execute_input":"2025-10-05T12:57:10.841862Z","iopub.status.idle":"2025-10-05T12:58:33.096369Z","shell.execute_reply.started":"2025-10-05T12:57:10.841834Z","shell.execute_reply":"2025-10-05T12:58:33.095567Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 2 — Imports & config\nimport os\nimport math\nimport random\nfrom pathlib import Path\nfrom datetime import datetime\nimport numpy as np\nfrom tqdm.auto import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, Dataset, random_split\nimport torchvision\nfrom torchvision import transforms, datasets\n\n# mixed precision - FIXED IMPORT\nfrom torch.cuda.amp import autocast, GradScaler  # This is the correct import\n\n# reproducibility (rest of cell remains the same)\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(SEED)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:04:07.882443Z","iopub.execute_input":"2025-10-05T13:04:07.882848Z","iopub.status.idle":"2025-10-05T13:04:13.877442Z","shell.execute_reply.started":"2025-10-05T13:04:07.882817Z","shell.execute_reply":"2025-10-05T13:04:13.876653Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 3 — Hyperparameters & helpers\nDATA_ROOT = \"/kaggle/working/data_cifar\"\nos.makedirs(DATA_ROOT, exist_ok=True)\n\n# Training settings\nBATCH_SIZE = 128\nEPOCHS = 200\nLR = 1e-3\nWEIGHT_DECAY = 1e-4\nWARMUP_EPOCHS = 10\nNUM_WORKERS = 0  # ⚠️ CHANGE THIS TO 0 to fix the multiprocessing issue ⚠️\n\n# CutMix params\nCUTMIX_PROB = 0.6\nCUTMIX_ALPHA = 1.0\n\n# MixUp params\nMIXUP_ALPHA = 0.2\nMIXUP_PROB = 0.3\n\n# Label map\nLABEL_MAP = {\n    0: \"airplane\", 1: \"automobile\", 2: \"bird\", 3: \"cat\", 4: \"deer\",\n    5: \"dog\", 6: \"frog\", 7: \"horse\", 8: \"ship\", 9: \"truck\"\n}\n\ndef accuracy_from_logits(logits, labels):\n    preds = logits.argmax(dim=1)\n    return (preds == labels).float().mean().item()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:04:17.580060Z","iopub.execute_input":"2025-10-05T13:04:17.580423Z","iopub.status.idle":"2025-10-05T13:04:17.586323Z","shell.execute_reply.started":"2025-10-05T13:04:17.580403Z","shell.execute_reply":"2025-10-05T13:04:17.585595Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 4 — Enhanced Transforms & dataset loaders\ntry:\n    from torchvision.transforms import RandAugment\n    rand_augment = RandAugment(num_ops=3, magnitude=9)  # Increased from 2 to 3\n    print(\"Using RandAugment\")\nexcept Exception:\n    rand_augment = None\n    print(\"RandAugment not available; using strong Compose\")\n\nmean = (0.4914, 0.4822, 0.4465)\nstd  = (0.2470, 0.2435, 0.2616)\n\ntrain_transform = [\n    transforms.RandomCrop(32, padding=4),\n    transforms.RandomHorizontalFlip(0.5),\n    transforms.RandomRotation(15),\n]\n\nif rand_augment is not None:\n    train_transform.append(rand_augment)\nelse:\n    # Enhanced fallback\n    train_transform += [\n        transforms.ColorJitter(0.4, 0.4, 0.4, 0.1),\n        transforms.RandomGrayscale(p=0.1),\n    ]\n\ntrain_transform += [\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std)\n]\n\ntrain_transform = transforms.Compose(train_transform)\n\ntest_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:04:24.761943Z","iopub.execute_input":"2025-10-05T13:04:24.762229Z","iopub.status.idle":"2025-10-05T13:04:24.769218Z","shell.execute_reply.started":"2025-10-05T13:04:24.762206Z","shell.execute_reply":"2025-10-05T13:04:24.768542Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 5 — Load datasets\ntrain_full = datasets.CIFAR10(root=DATA_ROOT, train=True, download=True, transform=train_transform)\ntest_official = datasets.CIFAR10(root=DATA_ROOT, train=False, download=True, transform=test_transform)\n\nVAL_SIZE = 5000\nTRAIN_SIZE = len(train_full) - VAL_SIZE\ntrain_set, val_set = random_split(train_full, [TRAIN_SIZE, VAL_SIZE], generator=torch.Generator().manual_seed(SEED))\n\n# Set num_workers=0 for all DataLoaders\ntrain_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True, num_workers=0, pin_memory=True)\nval_loader   = DataLoader(val_set,   batch_size=BATCH_SIZE, shuffle=False, num_workers=0, pin_memory=True)\ntest_loader  = DataLoader(test_official, batch_size=BATCH_SIZE, shuffle=False, num_workers=0, pin_memory=True)\n\nprint(\"Train set:\", len(train_set), \"Val set:\", len(val_set), \"Official test:\", len(test_official))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:04:30.704112Z","iopub.execute_input":"2025-10-05T13:04:30.704817Z","iopub.status.idle":"2025-10-05T13:04:56.143034Z","shell.execute_reply.started":"2025-10-05T13:04:30.704793Z","shell.execute_reply":"2025-10-05T13:04:56.142211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 6 — Enhanced augmentation utilities (CutMix + MixUp)\ndef rand_bbox(size, lam):\n    W = size[2]\n    H = size[3]\n    cut_rat = np.sqrt(1. - lam)\n    cut_w = int(W * cut_rat)\n    cut_h = int(H * cut_rat)\n\n    cx = np.random.randint(W)\n    cy = np.random.randint(H)\n\n    bbx1 = np.clip(cx - cut_w // 2, 0, W)\n    bby1 = np.clip(cy - cut_h // 2, 0, H)\n    bbx2 = np.clip(cx + cut_w // 2, 0, W)\n    bby2 = np.clip(cy + cut_h // 2, 0, H)\n\n    return bbx1, bby1, bbx2, bby2\n\ndef apply_cutmix(images, targets, alpha):\n    if alpha <= 0:\n        return images, targets, None, None\n    lam = np.random.beta(alpha, alpha)\n    B = images.size(0)\n    index = torch.randperm(B).to(images.device)\n    bbx1, bby1, bbx2, bby2 = rand_bbox(images.size(), lam)\n    images[:, :, bbx1:bbx2, bby1:bby2] = images[index, :, bbx1:bbx2, bby1:bby2]\n    lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (images.size(-1) * images.size(-2)))\n    targets2 = targets[index]\n    return images, targets, targets2, lam\n\ndef apply_mixup(images, targets, alpha):\n    if alpha <= 0:\n        return images, targets, None, None\n    lam = np.random.beta(alpha, alpha)\n    B = images.size(0)\n    index = torch.randperm(B).to(images.device)\n    mixed_images = lam * images + (1 - lam) * images[index]\n    targets_a, targets_b = targets, targets[index]\n    return mixed_images, targets_a, targets_b, lam","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:09:21.995645Z","iopub.execute_input":"2025-10-05T13:09:21.995993Z","iopub.status.idle":"2025-10-05T13:09:22.006943Z","shell.execute_reply.started":"2025-10-05T13:09:21.995968Z","shell.execute_reply":"2025-10-05T13:09:22.006233Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 6.5 — MixUp utility\ndef mixup_data(x, y, alpha=1.0):\n    if alpha > 0:\n        lam = np.random.beta(alpha, alpha)\n    else:\n        lam = 1\n\n    batch_size = x.size()[0]\n    index = torch.randperm(batch_size).to(x.device)\n\n    mixed_x = lam * x + (1 - lam) * x[index, :]\n    y_a, y_b = y, y[index]\n    return mixed_x, y_a, y_b, lam\n\ndef mixup_criterion(criterion, pred, y_a, y_b, lam):\n    return lam * criterion(pred, y_a) + (1 - lam) * criterion(pred, y_b)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:09:29.151718Z","iopub.execute_input":"2025-10-05T13:09:29.152042Z","iopub.status.idle":"2025-10-05T13:09:29.157238Z","shell.execute_reply.started":"2025-10-05T13:09:29.152019Z","shell.execute_reply":"2025-10-05T13:09:29.156509Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 7 — Enhanced Custom ResNet-style model\nclass BasicBlock(nn.Module):\n    expansion = 1\n    def __init__(self, in_planes, planes, stride=1, dropout_rate=0.0):\n        super().__init__()\n        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)\n        self.bn1   = nn.BatchNorm2d(planes)\n        self.relu  = nn.ReLU(inplace=True)\n        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn2   = nn.BatchNorm2d(planes)\n        self.dropout = nn.Dropout2d(dropout_rate) if dropout_rate > 0 else nn.Identity()\n        self.shortcut = nn.Sequential()\n        if stride != 1 or in_planes != planes:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(in_planes, planes, kernel_size=1, stride=stride, bias=False),\n                nn.BatchNorm2d(planes)\n            )\n    def forward(self, x):\n        out = self.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        out = self.dropout(out)\n        out += self.shortcut(x)\n        out = self.relu(out)\n        return out\n\nclass CustomResNet(nn.Module):\n    def __init__(self, block, num_blocks, num_classes=10, base_width=64, dropout_rate=0.0):\n        super().__init__()\n        self.in_planes = base_width\n        self.conv1 = nn.Conv2d(3, base_width, kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn1   = nn.BatchNorm2d(base_width)\n        self.relu  = nn.ReLU(inplace=True)\n        # residual layers\n        self.layer1 = self._make_layer(block, base_width, num_blocks[0], stride=1, dropout_rate=dropout_rate)\n        self.layer2 = self._make_layer(block, base_width*2, num_blocks[1], stride=2, dropout_rate=dropout_rate)\n        self.layer3 = self._make_layer(block, base_width*4, num_blocks[2], stride=2, dropout_rate=dropout_rate)\n        self.layer4 = self._make_layer(block, base_width*8, num_blocks[3], stride=2, dropout_rate=dropout_rate)\n        self.gap = nn.AdaptiveAvgPool2d((1,1))\n        self.fc  = nn.Linear(base_width*8*block.expansion, num_classes)\n        \n        # Initialize weights\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n            elif isinstance(m, nn.BatchNorm2d):\n                nn.init.constant_(m.weight, 1)\n                nn.init.constant_(m.bias, 0)\n            elif isinstance(m, nn.Linear):\n                nn.init.normal_(m.weight, 0, 0.01)\n                nn.init.constant_(m.bias, 0)\n\n    def _make_layer(self, block, planes, num_blocks, stride, dropout_rate=0.0):\n        strides = [stride] + [1]*(num_blocks-1)\n        layers = []\n        for s in strides:\n            layers.append(block(self.in_planes, planes, s, dropout_rate))\n            self.in_planes = planes * block.expansion\n        return nn.Sequential(*layers)\n\n    def forward(self, x):\n        x = self.relu(self.bn1(self.conv1(x)))\n        x = self.layer1(x)\n        x = self.layer2(x)\n        x = self.layer3(x)\n        x = self.layer4(x)\n        x = self.gap(x)\n        x = torch.flatten(x, 1)\n        x = self.fc(x)\n        return x\n\n# Enhanced model - deeper and wider\nMODEL_WIDTH = 64\nmodel = CustomResNet(BasicBlock, [3,4,6,3], num_classes=10, base_width=MODEL_WIDTH, dropout_rate=0.1).to(device)\nprint(f\"Model parameters: {sum(p.numel() for p in model.parameters()):,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:09:33.340847Z","iopub.execute_input":"2025-10-05T13:09:33.341141Z","iopub.status.idle":"2025-10-05T13:09:33.808839Z","shell.execute_reply.started":"2025-10-05T13:09:33.341118Z","shell.execute_reply":"2025-10-05T13:09:33.808086Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 8 — Optimizer (AdamW) and LR scheduler with warmup+cosine\noptimizer = optim.AdamW(model.parameters(), lr=LR, weight_decay=WEIGHT_DECAY)\n\n# total steps per epoch\nsteps_per_epoch = len(train_loader)\ntotal_steps = EPOCHS * steps_per_epoch\nwarmup_steps = WARMUP_EPOCHS * steps_per_epoch\n\n# lambda function for LR: linear warmup then cosine decay\ndef lr_lambda(current_step):\n    if current_step < warmup_steps:\n        return float(current_step) / float(max(1, warmup_steps))\n    # cosine decay\n    progress = float(current_step - warmup_steps) / float(max(1, total_steps - warmup_steps))\n    return 0.5 * (1.0 + math.cos(math.pi * progress))\n\nscheduler = optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)\n\nscaler = GradScaler()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:09:38.791363Z","iopub.execute_input":"2025-10-05T13:09:38.791969Z","iopub.status.idle":"2025-10-05T13:09:38.797929Z","shell.execute_reply.started":"2025-10-05T13:09:38.791945Z","shell.execute_reply":"2025-10-05T13:09:38.797138Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 9 — Enhanced train & validate loops (with CutMix, MixUp, AMP & label smoothing)\nbest_val_acc = 0.0\nbest_path = \"/kaggle/working/best_model.pth\"\nhistory = {\"train_loss\":[], \"train_acc\":[], \"val_loss\":[], \"val_acc\":[]}\n\n# Cell 9 — Enhanced Train & validate loops\nfor epoch in range(1, EPOCHS+1):\n    model.train()\n    running_loss = 0.0\n    running_acc = 0.0\n    cnt = 0\n    pbar = tqdm(train_loader, desc=f\"Epoch {epoch}/{EPOCHS}\", leave=False)\n    \n    for batch in pbar:\n        imgs, labels = batch\n        imgs = imgs.to(device)\n        labels = labels.to(device).squeeze()\n\n        # Data augmentation selection\n        aug_type = random.random()\n        \n        with torch.amp.autocast(\"cuda\"):\n            if aug_type < CUTMIX_PROB:\n                # CutMix\n                imgs, labels_a, labels_b, lam = apply_cutmix(imgs.clone(), labels, CUTMIX_ALPHA)\n                outputs = model(imgs)\n                loss = lam * F.cross_entropy(outputs, labels_a) + (1 - lam) * F.cross_entropy(outputs, labels_b)\n                acc = (lam * (outputs.argmax(dim=1) == labels_a).float().mean().item() +\n                       (1-lam) * (outputs.argmax(dim=1) == labels_b).float().mean().item())\n            \n            elif aug_type < CUTMIX_PROB + MIXUP_PROB:\n                # MixUp\n                imgs, labels_a, labels_b, lam = mixup_data(imgs, labels, MIXUP_ALPHA)\n                outputs = model(imgs)\n                loss = mixup_criterion(F.cross_entropy, outputs, labels_a, labels_b, lam)\n                acc = (lam * (outputs.argmax(dim=1) == labels_a).float().mean().item() +\n                       (1-lam) * (outputs.argmax(dim=1) == labels_b).float().mean().item())\n            \n            else:\n                # Standard\n                outputs = model(imgs)\n                loss = F.cross_entropy(outputs, labels)\n                acc = (outputs.argmax(dim=1) == labels).float().mean().item()\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        optimizer.zero_grad()\n        scheduler.step()\n\n        running_loss += loss.item() * imgs.size(0)\n        running_acc += acc * imgs.size(0)\n        cnt += imgs.size(0)\n        pbar.set_postfix(loss=running_loss/cnt, acc=running_acc/cnt, lr=optimizer.param_groups[0]['lr'])\n    train_loss = running_loss / cnt\n    train_acc  = running_acc / cnt\n\n    # Validation\n    model.eval()\n    val_loss = 0.0\n    val_acc = 0.0\n    vcnt = 0\n    with torch.no_grad():\n        for imgs, labels in val_loader:\n            imgs = imgs.to(device)\n            labels = labels.to(device).squeeze()\n            outputs = model(imgs)\n            loss = F.cross_entropy(outputs, labels)\n            val_loss += loss.item() * imgs.size(0)\n            val_acc  += (outputs.argmax(dim=1) == labels).float().sum().item()\n            vcnt += imgs.size(0)\n    val_loss /= vcnt\n    val_acc  = val_acc / vcnt\n\n    history[\"train_loss\"].append(train_loss)\n    history[\"train_acc\"].append(train_acc)\n    history[\"val_loss\"].append(val_loss)\n    history[\"val_acc\"].append(val_acc)\n\n    print(f\"Epoch {epoch}/{EPOCHS} | train_loss={train_loss:.4f} train_acc={train_acc:.4f} | val_loss={val_loss:.4f} val_acc={val_acc:.4f}\")\n\n    # Save best model\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        torch.save(model.state_dict(), best_path)\n        print(\"Saved best model:\", best_path)\n\nprint(\"Training finished. Best val acc:\", best_val_acc)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T13:09:43.436220Z","iopub.execute_input":"2025-10-05T13:09:43.436977Z","iopub.status.idle":"2025-10-05T18:02:23.644357Z","shell.execute_reply.started":"2025-10-05T13:09:43.436952Z","shell.execute_reply":"2025-10-05T18:02:23.643564Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Proper TTA implementation\ndef evaluate_with_tta_fixed(model, test_loader):\n    model.eval()\n    correct = 0\n    total = 0\n    \n    with torch.no_grad():\n        for images, labels in tqdm(test_loader):\n            images, labels = images.to(device), labels.to(device)\n            \n            # Original\n            outputs1 = model(images)\n            \n            # Horizontal flip\n            outputs2 = model(torch.flip(images, [3]))\n            \n            # Average predictions\n            avg_outputs = (outputs1 + outputs2) / 2\n            preds = avg_outputs.argmax(dim=1)\n            \n            correct += (preds == labels).sum().item()\n            total += labels.size(0)\n    \n    return correct / total\n\n# Test the fixed TTA\nfixed_tta_acc = evaluate_with_tta_fixed(model, test_loader)\nprint(f\"Fixed TTA accuracy: {fixed_tta_acc:.4f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T19:27:12.472070Z","iopub.execute_input":"2025-10-05T19:27:12.472675Z","iopub.status.idle":"2025-10-05T19:27:20.214794Z","shell.execute_reply.started":"2025-10-05T19:27:12.472638Z","shell.execute_reply":"2025-10-05T19:27:20.214058Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 11 — Prepare Kaggle test dataset (images are png)\nfrom PIL import Image\nimport glob\nimport pandas as pd\n\n# Adjust this path to where your uploaded extracted test images are mounted\n# Example: \"/kaggle/input/convnettest/test/test\" or competition input path \"/kaggle/input/cifar-10/test\"\nKAGGLE_EXTRACTED_FOLDER = \"/kaggle/input/convnettest/kaggle/working/test/test\"  # <- change if needed\n\n# quick check\nfiles = sorted([str(p) for p in Path(KAGGLE_EXTRACTED_FOLDER).rglob(\"*\") if p.suffix.lower() in ('.png', '.jpg', '.jpeg')])\nprint(\"Found test images:\", len(files))\nassert len(files) >= 300000 or len(files) > 0, \"No test images found - please set KAGGLE_EXTRACTED_FOLDER correctly\"\n\nclass KaggleTestDataset(Dataset):\n    def __init__(self, files, transform=None):\n        self.files = files\n        self.transform = transform\n    def __len__(self):\n        return len(self.files)\n    def __getitem__(self, idx):\n        path = self.files[idx]\n        img = Image.open(path).convert('RGB')\n        if self.transform:\n            img = self.transform(img)\n        return img, Path(path).stem  # returns id as string\n\n\nkaggle_test_ds = KaggleTestDataset(files, transform=test_transform)\n#kaggle_test_loader = DataLoader(kaggle_test_ds, batch_size=256, shuffle=False, num_workers=NUM_WORKERS)\nkaggle_test_loader = DataLoader(kaggle_test_ds, batch_size=256, shuffle=False, num_workers=0)  # num_workers=0","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T18:13:14.538006Z","iopub.execute_input":"2025-10-05T18:13:14.538473Z","iopub.status.idle":"2025-10-05T18:32:54.124250Z","shell.execute_reply.started":"2025-10-05T18:13:14.538448Z","shell.execute_reply":"2025-10-05T18:32:54.123429Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 12 — Predict and save submission.csv (Kaggle format requires names)\nmodel.load_state_dict(torch.load(\"/kaggle/working/best_model.pth\"))\nmodel.eval()\n\nids = []\npreds = []\nwith torch.no_grad():\n    for imgs, names in tqdm(kaggle_test_loader):\n        imgs = imgs.to(device)\n        outputs = model(imgs)\n        labels_pred = outputs.argmax(dim=1).cpu().numpy().tolist()\n        preds.extend(labels_pred)\n        ids.extend([int(n) for n in names])\n\n# Map numeric preds to class names\npreds_names = [LABEL_MAP[int(p)] for p in preds]\n\n# Ensure sorted by id\ndf = pd.DataFrame({\"id\": ids, \"label\": preds_names})\ndf = df.sort_values(\"id\")\ndf.to_csv(\"/kaggle/working/submission.csv\", index=False)\nprint(\"Submission saved: /kaggle/working/submission.csv Rows:\", len(df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T18:35:03.517960Z","iopub.execute_input":"2025-10-05T18:35:03.518212Z","iopub.status.idle":"2025-10-05T19:15:12.348460Z","shell.execute_reply.started":"2025-10-05T18:35:03.518194Z","shell.execute_reply":"2025-10-05T19:15:12.347853Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Cell 13 — Quick checks\ndf_check = pd.read_csv(\"/kaggle/working/submission.csv\")\nprint(\"Rows:\", len(df_check))\nprint(df_check.head(10))\nprint(\"ID min/max:\", df_check['id'].min(), df_check['id'].max())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-05T19:17:00.443921Z","iopub.execute_input":"2025-10-05T19:17:00.444205Z","iopub.status.idle":"2025-10-05T19:17:00.521441Z","shell.execute_reply.started":"2025-10-05T19:17:00.444184Z","shell.execute_reply":"2025-10-05T19:17:00.520525Z"}},"outputs":[],"execution_count":null}]}