{"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"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Setup","metadata":{}},{"cell_type":"code","source":"!pip -q install timm==1.0.3 --no-warn-conflicts\n!apt install libarchive-dev\n!pip install libarchive","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:08:14.451503Z","iopub.execute_input":"2025-10-02T08:08:14.451753Z","iopub.status.idle":"2025-10-02T08:09:42.692282Z","shell.execute_reply.started":"2025-10-02T08:08:14.451730Z","shell.execute_reply":"2025-10-02T08:09:42.691459Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, sys, math, time, random, glob, csv\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader\nfrom torchvision import transforms, datasets\n\nfrom sklearn.metrics import (\n    accuracy_score,\n    precision_recall_fscore_support,\n    classification_report,\n    confusion_matrix,\n    ConfusionMatrixDisplay\n)\n\nimport matplotlib.pyplot as plt\nimport libarchive.public  # for Kaggle test.7z\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nSEED = 1337\nDATA_DIR = \"./data\"\n\ndef seed_everything(seed=SEED):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.benchmark = True\n\nseed_everything()\nprint(\"Device:\", DEVICE)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:09:42.693185Z","iopub.execute_input":"2025-10-02T08:09:42.693408Z","iopub.status.idle":"2025-10-02T08:09:50.485625Z","shell.execute_reply.started":"2025-10-02T08:09:42.693386Z","shell.execute_reply":"2025-10-02T08:09:50.484918Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Augmentation Transforms for CutOut and CutMix","metadata":{}},{"cell_type":"code","source":"class CutOut(object):\n    def __init__(self, n_holes=1, length=8):\n        self.n_holes, self.length = n_holes, length\n    def __call__(self, img):  # img: Tensor CxHxW\n        h, w = img.shape[1], img.shape[2]\n        mask = torch.ones((h, w), dtype=img.dtype, device=img.device)\n        for _ in range(self.n_holes):\n            y = np.random.randint(h); x = np.random.randint(w)\n            y1 = np.clip(y - self.length//2, 0, h); y2 = np.clip(y + self.length//2, 0, h)\n            x1 = np.clip(x - self.length//2, 0, w); x2 = np.clip(x + self.length//2, 0, w)\n            mask[int(y1):int(y2), int(x1):int(x2)] = 0.\n        return img * mask.expand_as(img)\n\nMEAN = (0.4914, 0.4822, 0.4465)\nSTD  = (0.2023, 0.1994, 0.2010)\n\ntrain_tfms = transforms.Compose([\n    transforms.RandomCrop(32, padding=4),\n    transforms.RandomHorizontalFlip(),\n    transforms.ColorJitter(0.2,0.2,0.2,0.1),\n    transforms.ToTensor(),\n    transforms.Normalize(MEAN, STD),\n    CutOut(n_holes=1, length=8),\n])\n\nval_tfms = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(MEAN, STD),\n])\n\ndef mixup_data(x, y, alpha=0.2):\n    lam = np.random.beta(alpha, alpha) if alpha > 0 else 1.0\n    index = torch.randperm(x.size(0))\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 cutmix_data(x, y, alpha=1.0):\n    lam = np.random.beta(alpha, alpha)\n    bs, _, H, W = x.size()\n    index = torch.randperm(bs)\n    y_a, y_b = y, y[index]\n    x1, y1, x2, y2 = rand_bbox(W, H, lam)  # you already have rand_bbox(W,H,lam)\n    x[:, :, y1:y2, x1:x2] = x[index, :, y1:y2, x1:x2]\n    lam = 1 - ((x2 - x1) * (y2 - y1) / (W * H))  # area-corrected\n    return x, y_a, y_b, lam\n\ndef collate_cutmix_mixup(batch,\n                         p_cutmix=0.5,       # turn this up/down\n                         p_mixup=0.0,        # set >0 to enable mixup too\n                         alpha_cutmix=1.0,\n                         alpha_mixup=0.2):\n    imgs = torch.stack([b[0] for b in batch], 0)\n    targets = torch.tensor([b[1] for b in batch], dtype=torch.long)\n\n    u = np.random.rand()\n    if u < p_cutmix:\n        imgs, y1, y2, lam = cutmix_data(imgs, targets, alpha_cutmix)\n        return imgs, (y1, y2, lam)\n    elif u < p_cutmix + p_mixup:\n        imgs, y1, y2, lam = mixup_data(imgs, targets, alpha_mixup)\n        return imgs, (y1, y2, lam)\n    else:\n        # no mixing this batch\n        return imgs, (targets, None, None)\n\n\ndef rand_bbox(W, H, lam):\n    cut_rat = np.sqrt(1. - lam)\n    cut_w, cut_h = int(W * cut_rat), int(H * cut_rat)\n    cx, cy = np.random.randint(W), np.random.randint(H)\n    x1 = np.clip(cx - cut_w//2, 0, W); y1 = np.clip(cy - cut_h//2, 0, H)\n    x2 = np.clip(cx + cut_w//2, 0, W); y2 = np.clip(cy + cut_h//2, 0, H)\n    return x1, y1, x2, y2\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:09:50.487196Z","iopub.execute_input":"2025-10-02T08:09:50.487531Z","iopub.status.idle":"2025-10-02T08:09:50.501850Z","shell.execute_reply.started":"2025-10-02T08:09:50.487513Z","shell.execute_reply":"2025-10-02T08:09:50.500965Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load Data","metadata":{}},{"cell_type":"code","source":"train_set = datasets.CIFAR10(DATA_DIR, train=True,  download=True, transform=train_tfms)\nval_set   = datasets.CIFAR10(DATA_DIR, train=False, download=True, transform=val_tfms)\n\nCLASSES = train_set.classes\nprint(\"Classes:\", CLASSES)\n\nBATCH_SIZE = 256\nNUM_WORKERS = 2\ntrain_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True,  num_workers=NUM_WORKERS, pin_memory=True, drop_last=True)\nval_loader   = DataLoader(val_set,   batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True)\ntrain_set = datasets.CIFAR10(DATA_DIR, train=True,  download=True, transform=train_tfms)\nval_set   = datasets.CIFAR10(DATA_DIR, train=False, download=True, transform=val_tfms)\n\ntrain_loader = DataLoader(\n    train_set, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS,\n    pin_memory=True, drop_last=True,\n    collate_fn=lambda b: collate_cutmix_mixup(b, p_cutmix=0.5, p_mixup=0.0)  # CutMix on\n)\nval_loader = DataLoader(\n    val_set, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:09:50.505281Z","iopub.execute_input":"2025-10-02T08:09:50.505512Z","iopub.status.idle":"2025-10-02T08:10:03.710431Z","shell.execute_reply.started":"2025-10-02T08:09:50.505494Z","shell.execute_reply":"2025-10-02T08:10:03.709797Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Build Model","metadata":{}},{"cell_type":"code","source":"class DropPath(nn.Module):\n    def __init__(self, p=0.0):\n        super().__init__()\n        self.p = p\n    def forward(self, x):\n        if not self.training or self.p == 0.0:\n            return x\n        keep = 1 - self.p\n        shape = (x.shape[0],) + (1,) * (x.ndim - 1)\n        rand = torch.rand(shape, dtype=x.dtype, device=x.device)\n        return x * (rand < keep) / keep\n\nclass SE(nn.Module):\n    def __init__(self, c, r=8):\n        super().__init__()\n        self.pool = nn.AdaptiveAvgPool2d(1)\n        self.fc = nn.Sequential(\n            nn.Conv2d(c, c//r, 1), nn.GELU(),\n            nn.Conv2d(c//r, c, 1), nn.Sigmoid()\n        )\n    def forward(self, x):\n        return x * self.fc(self.pool(x))\n\nclass ConvNeXtBlock(nn.Module):\n    def __init__(self, c, exp=4, drop_path=0.0):\n        super().__init__()\n        self.dw = nn.Conv2d(c, c, kernel_size=7, padding=3, groups=c)\n        self.gn = nn.GroupNorm(1, c)   # channel LN\n        hidden = int(exp * c)\n        self.pw1 = nn.Conv2d(c, hidden, 1)\n        self.act = nn.GELU()\n        self.pw2 = nn.Conv2d(hidden, c, 1)\n        self.se  = SE(c, r=8)\n        self.drop = DropPath(drop_path)\n\n    def forward(self, x):\n        residual = x\n        x = self.dw(x)\n        x = self.gn(x)\n        x = self.pw1(x)\n        x = self.act(x)\n        x = self.pw2(x)\n        x = self.se(x)\n        x = residual + self.drop(x)\n        return x\n\nclass DownsampleResidual(nn.Module):\n    def __init__(self, in_ch, out_ch, k=2, norm='gn'):\n        super().__init__()\n        self.main = nn.Conv2d(in_ch, out_ch, kernel_size=k, stride=k, padding=0, bias=False)\n        self.skip = nn.Sequential(\n            nn.AvgPool2d(kernel_size=k, stride=k),\n            nn.Conv2d(in_ch, out_ch, kernel_size=1, bias=False)\n        )\n        if norm == 'gn':\n            self.norm = nn.GroupNorm(1, out_ch)     # matches your ConvNeXt-style normalization\n        else:\n            self.norm = nn.BatchNorm2d(out_ch)\n\n    def forward(self, x):\n        return self.norm(self.main(x) + self.skip(x))\n\n\nclass CifarNeXt(nn.Module):\n    def __init__(self, dims=(64, 128, 256, 384), depths=(2,3,4,2),\n                 num_classes=10, drop_path_rate=0.1):\n        super().__init__()\n        # Stem (32 -> 16)\n        self.stem = nn.Sequential(\n            nn.Conv2d(3, dims[0], kernel_size=4, stride=2, padding=1),\n            nn.GELU(),\n            nn.Conv2d(dims[0], dims[0], kernel_size=3, stride=1, padding=1)\n        )\n\n        stages = []\n        total_blocks = sum(depths)\n        dp_rates = torch.linspace(0, drop_path_rate, total_blocks).tolist()\n        i = 0\n        in_c = dims[0]\n\n        for s, (c, d) in enumerate(zip(dims, depths)):\n            if s > 0:\n                # swap in the residual downsample\n                stages.append(DownsampleResidual(in_c, c, k=2, norm='gn'))\n                in_c = c\n            blocks = []\n            for _ in range(d):\n                blocks.append(ConvNeXtBlock(c, exp=4, drop_path=dp_rates[i]))\n                i += 1\n            stages.append(nn.Sequential(*blocks))\n        self.stages = nn.Sequential(*stages)\n\n        # --- NEW: global long skip from stem -> final channels/spatial (16x16 -> 2x2)\n        # 16 -> 2 is a factor of 8, so AvgPool2d(8,8) does the spatial match.\n        self.stem2last = nn.Sequential(\n            nn.AvgPool2d(kernel_size=8, stride=8),        # 16x16 -> 2x2\n            nn.Conv2d(dims[0], dims[-1], kernel_size=1, bias=False),\n            nn.GroupNorm(1, dims[-1])\n        )\n\n        self.head = nn.Sequential(\n            nn.GroupNorm(1, dims[-1]),\n            nn.GELU(),\n            nn.AdaptiveAvgPool2d(1),\n            nn.Flatten(),\n            nn.Linear(dims[-1], num_classes)\n        )\n\n        self.apply(self._init)\n\n    @staticmethod\n    def _init(m):\n        if isinstance(m, nn.Conv2d):\n            nn.init.kaiming_normal_(m.weight, nonlinearity=\"relu\")\n            if getattr(m, \"bias\", None) is not None: nn.init.zeros_(m.bias)\n        elif isinstance(m, nn.Linear):\n            nn.init.trunc_normal_(m.weight, std=0.02)\n            if m.bias is not None: nn.init.zeros_(m.bias)\n\n    def forward(self, x):\n        s = self.stem(x)        # 16x16, dims[0]\n        y = self.stages(s)      # ends at 2x2, dims[-1]\n        y = y + self.stem2last(s)   # <-- global long skip\n        y = self.head(y)\n        return y\n\n\n\ndef cifarnext_tiny(num_classes=10):\n    # tuned for CIFAR-10 speed/quality\n    return CifarNeXt(dims=(64,128,256,384), depths=(2,3,4,2), num_classes=num_classes, drop_path_rate=0.15)\n\nmodel = cifarnext_tiny(num_classes=len(CLASSES)).to(DEVICE)\nprint(\"Params (M):\", round(sum(p.numel() for p in model.parameters())/1e6, 3))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:10:03.711387Z","iopub.execute_input":"2025-10-02T08:10:03.711687Z","iopub.status.idle":"2025-10-02T08:10:03.995891Z","shell.execute_reply.started":"2025-10-02T08:10:03.711655Z","shell.execute_reply":"2025-10-02T08:10:03.995158Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# after creating model:\nema_decay = 0.999\nema = {n: p.detach().clone() for n,p in model.named_parameters() if p.requires_grad}\n\ndef ema_update(model, ema, decay=ema_decay):\n    with torch.no_grad():\n        for (n, p) in model.named_parameters():\n            if p.requires_grad:\n                ema[n].mul_(decay).add_(p.detach(), alpha=1.0 - decay)\n\n# in training loop, right after optimizer.step():\nema_update(model, ema)\n\n@torch.no_grad()\ndef load_ema(model, ema):\n    for (n, p) in model.named_parameters():\n        if p.requires_grad:\n            p.copy_(ema[n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:10:03.996575Z","iopub.execute_input":"2025-10-02T08:10:03.996784Z","iopub.status.idle":"2025-10-02T08:10:04.046258Z","shell.execute_reply.started":"2025-10-02T08:10:03.996768Z","shell.execute_reply":"2025-10-02T08:10:04.045685Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Loss - Optimizer - Cosine Warmup (AdamW)","metadata":{}},{"cell_type":"code","source":"EPOCHS = 1000          \nBASE_LR = 3e-3\nWARMUP_EPOCHS = 3\nWEIGHT_DECAY = 6e-4\nLABEL_SMOOTH = 0.05","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:10:04.046930Z","iopub.execute_input":"2025-10-02T08:10:04.047112Z","iopub.status.idle":"2025-10-02T08:10:04.051063Z","shell.execute_reply.started":"2025-10-02T08:10:04.047097Z","shell.execute_reply":"2025-10-02T08:10:04.050435Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class LabelSmoothingLoss(nn.Module):\n    def __init__(self, n_classes, smoothing=0.0):\n        super().__init__()\n        self.conf = 1.0 - smoothing\n        self.smoothing = smoothing\n        self.n = n_classes\n    def forward(self, logits, target):\n        logp = F.log_softmax(logits, dim=1)\n        with torch.no_grad():\n            true = torch.full_like(logp, self.smoothing/(self.n-1))\n            true.scatter_(1, target.unsqueeze(1), self.conf)\n        return torch.mean(torch.sum(-true * logp, dim=1))\n\ncriterion = LabelSmoothingLoss(len(CLASSES), LABEL_SMOOTH)\noptimizer = torch.optim.AdamW(model.parameters(), lr=BASE_LR, weight_decay=WEIGHT_DECAY)\n\ndef cosine_warmup_factor(epoch):\n    if epoch < WARMUP_EPOCHS:\n        return (epoch + 1) / max(1, WARMUP_EPOCHS)\n    progress = (epoch - WARMUP_EPOCHS) / max(1, EPOCHS - WARMUP_EPOCHS)\n    return 0.5 * (1 + math.cos(math.pi * progress))\n\nscheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=cosine_warmup_factor)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:10:04.052815Z","iopub.execute_input":"2025-10-02T08:10:04.053308Z","iopub.status.idle":"2025-10-02T08:10:04.066311Z","shell.execute_reply.started":"2025-10-02T08:10:04.053289Z","shell.execute_reply":"2025-10-02T08:10:04.065688Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train","metadata":{}},{"cell_type":"code","source":"best_acc = 0.0\nbest_path = \"/kaggle/working/best_model.pth\"\nbest_path_final = \"/kaggle/working/best_model_final.pth\"\n\ndef train_one_epoch(epoch):\n    model.train()\n    total, correct, loss_sum = 0, 0, 0.0\n\n    for x, ytuple in train_loader:\n        # ytuple = (y_main, y_aux_or_None, lam_or_None)\n        y, y2, lam = ytuple\n        x = x.to(DEVICE, non_blocking=True)\n        y = y.to(DEVICE, non_blocking=True)\n        y2 = y2.to(DEVICE, non_blocking=True) if y2 is not None else None\n\n        logits = model(x)\n        if y2 is not None:  # mixed batch (CutMix or MixUp)\n            loss = lam * criterion(logits, y) + (1 - lam) * criterion(logits, y2)\n        else:\n            loss = criterion(logits, y)\n\n        optimizer.zero_grad(set_to_none=True)\n        loss.backward()\n        optimizer.step()\n\n        loss_sum += loss.item() * x.size(0)\n        preds = logits.argmax(1)\n        # accuracy judged against primary y; this is standard practice\n        correct += (preds == y).sum().item()\n        total += x.size(0)\n\n    scheduler.step()\n    return loss_sum/total, correct/total\n\n\n@torch.no_grad()\ndef validate():\n    model.eval()\n    total, correct, loss_sum = 0, 0, 0.0\n    for x, y in val_loader:\n        x = x.to(DEVICE, non_blocking=True); y = y.to(DEVICE, non_blocking=True)\n        logits = model(x)\n        loss = criterion(logits, y)\n        loss_sum += loss.item() * x.size(0)\n        correct += (logits.argmax(1) == y).sum().item()\n        total += x.size(0)\n    return loss_sum/total, correct/total\n\nfor epoch in range(1, EPOCHS+1):\n    tr_loss, tr_acc = train_one_epoch(epoch)\n    va_loss, va_acc = validate()\n\n    # EXACT print you requested\n    print(f\"Epoch {epoch:03d}/{EPOCHS} | val loss {va_loss:.4f} acc {va_acc:.4f}\")\n\n    # EXACT checkpoint behavior\n    if va_acc > best_acc:\n        best_acc = va_acc\n        torch.save({\"model\": model.state_dict(), \"classes\": CLASSES}, best_path)\n    torch.save({\"model\": model.state_dict(), \"classes\": CLASSES}, best_path_final)\n\nprint(\"Best val acc:\", best_acc)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:10:04.066943Z","iopub.execute_input":"2025-10-02T08:10:04.067171Z","iopub.status.idle":"2025-10-02T08:10:36.248112Z","shell.execute_reply.started":"2025-10-02T08:10:04.067155Z","shell.execute_reply":"2025-10-02T08:10:36.247205Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Load Model","metadata":{}},{"cell_type":"code","source":"# model = torch.load(\"/kaggle/input/resnet18-ass4/pytorch/default/1/best_model.pth\", map_location=DEVICE)\ncheckpoint = torch.load(\"/kaggle/working/best_model.pth\", map_location=DEVICE)\n\n# Rebuild model\ndef resnet18_cifar10(*args, **kwargs):  # stub kept only to match your snippet name if reused elsewhere\n    raise NotImplementedError(\"We use CifarNeXt instead of ResNet.\")\n\nmodel = cifarnext_tiny(num_classes=len(checkpoint[\"classes\"]))\nmodel.load_state_dict(checkpoint[\"model\"])\nmodel.to(DEVICE)\nmodel.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:10:36.249225Z","iopub.execute_input":"2025-10-02T08:10:36.249467Z","iopub.status.idle":"2025-10-02T08:10:36.435626Z","shell.execute_reply.started":"2025-10-02T08:10:36.249442Z","shell.execute_reply":"2025-10-02T08:10:36.434936Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Evaluation","metadata":{}},{"cell_type":"code","source":"all_preds, all_targets = [], []\n\nwith torch.no_grad():\n    for x, y in val_loader:\n        x = x.to(DEVICE, non_blocking=True)\n        logits = model(x)\n        preds = logits.argmax(1).cpu().numpy()\n        all_preds.append(preds)\n        all_targets.append(y.numpy())\n\ny_true = np.concatenate(all_targets)\ny_pred = np.concatenate(all_preds)\n\nacc = accuracy_score(y_true, y_pred)\nprec_macro, rec_macro, f1_macro, _ = precision_recall_fscore_support(\n    y_true, y_pred, average=\"macro\", zero_division=0\n)\nprec_weighted, rec_weighted, f1_weighted, _ = precision_recall_fscore_support(\n    y_true, y_pred, average=\"weighted\", zero_division=0\n)\n\nprint(f\"Accuracy: {acc:.4f}\")\nprint(f\"Macro  - Precision: {prec_macro:.4f}  Recall: {rec_macro:.4f}  F1: {f1_macro:.4f}\")\nprint(f\"Weighted - Precision: {prec_weighted:.4f}  Recall: {rec_weighted:.4f}  F1: {f1_weighted:.4f}\")\n\nprint(\"\\nPer-class report:\")\nprint(classification_report(y_true, y_pred, target_names=CLASSES, zero_division=0))\n\ncm = confusion_matrix(y_true, y_pred, labels=range(len(CLASSES)))\nfig, ax = plt.subplots(1, 2, figsize=(14, 5))\n\ndisp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=CLASSES)\ndisp.plot(ax=ax[0], xticks_rotation=45, colorbar=False)\nax[0].set_title(\"Confusion Matrix (Counts)\")\n\ncm_norm = cm.astype(np.float64) / cm.sum(axis=1, keepdims=True)\ndisp_norm = ConfusionMatrixDisplay(confusion_matrix=cm_norm, display_labels=CLASSES)\ndisp_norm.plot(ax=ax[1], xticks_rotation=45, colorbar=False, values_format=\".2f\")\nax[1].set_title(\"Confusion Matrix (Row-Normalized)\")\n\nplt.tight_layout(); plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:10:36.436501Z","iopub.execute_input":"2025-10-02T08:10:36.437035Z","iopub.status.idle":"2025-10-02T08:10:38.901878Z","shell.execute_reply.started":"2025-10-02T08:10:36.437009Z","shell.execute_reply":"2025-10-02T08:10:38.900957Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Decompress Kaggle Test Set","metadata":{}},{"cell_type":"code","source":"cnt = 0\n\nfor entry in libarchive.public.file_pour('/kaggle/input/cifar-10/test.7z'):\n    cnt += 1\n    if cnt % 75000 == 0: print(cnt)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:10:38.902835Z","iopub.execute_input":"2025-10-02T08:10:38.903065Z","iopub.status.idle":"2025-10-02T08:11:42.942555Z","shell.execute_reply.started":"2025-10-02T08:10:38.903044Z","shell.execute_reply":"2025-10-02T08:11:42.941851Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inference on Kaggle Test set","metadata":{}},{"cell_type":"code","source":"test_tfms = val_tfms\n\ndef load_image(path):\n    return test_tfms(Image.open(path).convert(\"RGB\"))\n\n# IMPORTANT: iterate ids in numeric order\ntest_dir = \"test\"\ntest_ids = sorted([int(os.path.splitext(os.path.basename(p))[0]) for p in glob.glob(os.path.join(test_dir, \"*.png\"))])\nprint(\"Test images:\", len(test_ids))\n\nBATCH = 512\npred_labels = []\n\nwith torch.no_grad():\n    batch = []\n    current_ids = []\n    for i in test_ids:\n        x = load_image(os.path.join(test_dir, f\"{i}.png\"))\n        batch.append(x)\n        current_ids.append(i)\n        if len(batch) == BATCH:\n            xb = torch.stack(batch).to(DEVICE)\n            probs = model(xb).softmax(1)\n            preds = probs.argmax(1).cpu().numpy()\n            pred_labels += [(j, CLASSES[p]) for j,p in zip(current_ids, preds)]\n            batch, current_ids = [], []\n    if batch:\n        xb = torch.stack(batch).to(DEVICE)\n        probs = model(xb).softmax(1)\n        preds = probs.argmax(1).cpu().numpy()\n        pred_labels += [(j, CLASSES[p]) for j,p in zip(current_ids, preds)]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:11:42.943550Z","iopub.execute_input":"2025-10-02T08:11:42.943879Z","iopub.status.idle":"2025-10-02T08:14:07.684074Z","shell.execute_reply.started":"2025-10-02T08:11:42.943849Z","shell.execute_reply":"2025-10-02T08:14:07.683167Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Build Submission CSV","metadata":{}},{"cell_type":"code","source":"with open(\"submission.csv\",\"w\", newline=\"\") as f:\n    w = csv.writer(f)\n    w.writerow([\"id\",\"label\"])\n    for i,lab in sorted(pred_labels, key=lambda t:t[0]):\n        w.writerow([i, lab])\nprint(\"Wrote submission.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-02T08:14:07.684969Z","iopub.execute_input":"2025-10-02T08:14:07.685212Z","iopub.status.idle":"2025-10-02T08:14:07.987290Z","shell.execute_reply.started":"2025-10-02T08:14:07.685195Z","shell.execute_reply":"2025-10-02T08:14:07.986599Z"}},"outputs":[],"execution_count":null}]}