{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.13.15","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!apt-get update -y\n!apt-get install -y libarchive-dev\n!pip install libarchive","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-10-15T18:37:49.114589Z","iopub.execute_input":"2026-10-15T18:37:49.11564Z","iopub.status.idle":"2026-10-15T18:38:14.38574Z","shell.execute_reply.started":"2026-10-15T18:37:49.115589Z","shell.execute_reply":"2026-10-15T18:38:14.384846Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import libarchive.public\n\nimport glob\n\n# Extract test.7z using the correct Kaggle path\ncnt = 0\nfor entry in libarchive.public.file_pour('/kaggle/input/competitions/cifar-10/test.7z'):\n    cnt += 1\n    if cnt % 1000 == 0: \n        print(f\"Extracted {cnt} files...\")\n\nprint(\"Total extracted files:\", len(glob.glob('test/*')))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-15T18:38:14.387479Z","iopub.execute_input":"2026-10-15T18:38:14.387795Z","iopub.status.idle":"2026-10-15T18:39:17.610764Z","shell.execute_reply.started":"2026-10-15T18:38:14.387773Z","shell.execute_reply":"2026-10-15T18:39:17.609736Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob, math, time\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom PIL import Image\nfrom concurrent.futures import ThreadPoolExecutor\n\n# ----------------------------- Config --------------------------------\nSEED            = 42\nEPOCHS          = 120       \nWARMUP_EPOCHS   = 3\nBATCH_SIZE      = 256\nPEAK_LR         = 2e-3\nWEIGHT_DECAY    = 5e-2       # decoupled (AdamW); not applied to BN/bias\nLABEL_SMOOTHING = 0.1\nCUTMIX_PROB     = 0.5\nVAL_SIZE        = 5000\nTRAIN_ON_ALL    = False      \nTRAIN_CSV = '/kaggle/input/competitions/cifar-10/trainLabels.csv'\nTRAIN_DIR = 'train'\nTEST_DIR  = 'test'\n\ntorch.manual_seed(SEED); np.random.seed(SEED)\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\ntorch.backends.cudnn.benchmark = True\nprint('Using device:', device)\n\nclasses = ('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog',\n           'horse', 'ship', 'truck')\nclass_to_idx = {c: i for i, c in enumerate(classes)}\n\n# --------------------- Data: load everything once --------------------\ndef load_images(paths, workers=8):\n    def _load(p):\n        with Image.open(p) as im:\n            return np.asarray(im.convert('RGB'))\n    with ThreadPoolExecutor(workers) as ex:\n        arr = list(ex.map(_load, paths))\n    return torch.from_numpy(np.stack(arr)).permute(0, 3, 1, 2).contiguous()  # uint8 N,3,32,32\n\ndf = pd.read_csv(TRAIN_CSV)\nX_all = load_images([f'{TRAIN_DIR}/{i}.png' for i in df['id']])\ny_all = torch.tensor([class_to_idx[l] for l in df['label']])\nprint('Loaded train images:', tuple(X_all.shape))\n\n# Separate, explicit split. Train and val are different tensors, so the\n# augmentation applied to train can never leak into validation (or vice versa).\ng = torch.Generator().manual_seed(SEED)\nperm = torch.randperm(len(X_all), generator=g)\nval_idx = perm[:VAL_SIZE]\ntr_idx = perm if TRAIN_ON_ALL else perm[VAL_SIZE:]\nX_tr, y_tr = X_all[tr_idx].to(device), y_all[tr_idx].to(device)\nX_val, y_val = X_all[val_idx].to(device), y_all[val_idx].to(device)\n\nMEAN = torch.tensor([0.4914, 0.4822, 0.4465], device=device).view(1, 3, 1, 1) * 255\nSTD  = torch.tensor([0.2023, 0.1994, 0.2010], device=device).view(1, 3, 1, 1) * 255\ndef normalize(x_uint8):\n    return (x_uint8.float() - MEAN) / STD\n\n# ------------------------ GPU augmentation ---------------------------\ndef random_crop(x, pad=4):\n    B, C, H, W = x.shape\n    xp = F.pad(x, (pad,) * 4, mode='reflect')\n    i = torch.randint(0, 2 * pad + 1, (B,), device=x.device)\n    j = torch.randint(0, 2 * pad + 1, (B,), device=x.device)\n    rows = i[:, None] + torch.arange(H, device=x.device)[None]\n    cols = j[:, None] + torch.arange(W, device=x.device)[None]\n    b = torch.arange(B, device=x.device)[:, None, None, None]\n    c = torch.arange(C, device=x.device)[None, :, None, None]\n    return xp[b, c, rows[:, None, :, None], cols[:, None, None, :]]\n\ndef random_flip(x):\n    m = torch.rand(x.size(0), device=x.device) < 0.5\n    return torch.where(m[:, None, None, None], x.flip(3), x)\n\ndef cutmix(x, y, alpha=1.0):\n    B, _, H, W = x.shape\n    lam = np.random.beta(alpha, alpha)\n    idx = torch.randperm(B, device=x.device)\n    cut = math.sqrt(1.0 - lam)\n    ch, cw = int(H * cut), int(W * cut)\n    cy, cx = np.random.randint(H), np.random.randint(W)\n    y1, y2 = max(cy - ch // 2, 0), min(cy + ch // 2, H)\n    x1, x2 = max(cx - cw // 2, 0), min(cx + cw // 2, W)\n    x[:, :, y1:y2, x1:x2] = x[idx, :, y1:y2, x1:x2]\n    lam = 1.0 - ((y2 - y1) * (x2 - x1) / (H * W))   # exact area ratio\n    return x, y, y[idx], lam\n\n# ------------------------- Custom network ----------------------------\nclass SE(nn.Module):\n    \"\"\"Squeeze-and-Excitation channel attention.\"\"\"\n    def __init__(self, c, r=16):\n        super().__init__()\n        self.fc1 = nn.Linear(c, c // r)\n        self.fc2 = nn.Linear(c // r, c)\n    def forward(self, x):\n        w = x.mean((2, 3))\n        w = torch.sigmoid(self.fc2(F.silu(self.fc1(w))))\n        return x * w[:, :, None, None]\n\nclass Block(nn.Module):\n    def __init__(self, cin, cout, stride):\n        super().__init__()\n        self.conv1 = nn.Conv2d(cin, cout, 3, stride, 1, bias=False)\n        self.bn1 = nn.BatchNorm2d(cout)\n        self.conv2 = nn.Conv2d(cout, cout, 3, 1, 1, bias=False)\n        self.bn2 = nn.BatchNorm2d(cout)\n        nn.init.zeros_(self.bn2.weight)            # block starts as identity\n        self.se = SE(cout)\n        self.act = nn.SiLU(inplace=True)\n        if stride != 1 or cin != cout:\n            layers = [nn.AvgPool2d(stride)] if stride > 1 else []\n            layers += [nn.Conv2d(cin, cout, 1, bias=False), nn.BatchNorm2d(cout)]\n            self.short = nn.Sequential(*layers)\n        else:\n            self.short = nn.Identity()\n    def forward(self, x):\n        out = self.act(self.bn1(self.conv1(x)))\n        out = self.se(self.bn2(self.conv2(out)))\n        return self.act(out + self.short(x))\n\nclass SENet32(nn.Module):\n    def __init__(self, num_classes=10, widths=(64, 128, 256, 512), depths=(2, 2, 2, 2)):\n        super().__init__()\n        self.stem = nn.Sequential(\n            nn.Conv2d(3, 32, 3, 1, 1, bias=False), nn.BatchNorm2d(32), nn.SiLU(inplace=True),\n            nn.Conv2d(32, widths[0], 3, 1, 1, bias=False), nn.BatchNorm2d(widths[0]), nn.SiLU(inplace=True))\n        stages, cin = [], widths[0]\n        for i, (w, d) in enumerate(zip(widths, depths)):\n            for k in range(d):\n                stages.append(Block(cin, w, stride=2 if (k == 0 and i > 0) else 1))\n                cin = w\n        self.stages = nn.Sequential(*stages)\n        self.head = nn.Sequential(nn.AdaptiveAvgPool2d(1), nn.Flatten(),\n                                  nn.Dropout(0.2), nn.Linear(cin, num_classes))\n    def forward(self, x):\n        return self.head(self.stages(self.stem(x)))\n\nmodel = SENet32().to(device).to(memory_format=torch.channels_last)\nprint('Parameters: %.2fM' % (sum(p.numel() for p in model.parameters()) / 1e6))\n\n# ----------------- Optimizer: AdamW + warmup + cosine ----------------\ndecay    = [p for p in model.parameters() if p.ndim > 1]\nno_decay = [p for p in model.parameters() if p.ndim <= 1]\noptimizer = torch.optim.AdamW(\n    [{'params': decay, 'weight_decay': WEIGHT_DECAY},\n     {'params': no_decay, 'weight_decay': 0.0}], lr=PEAK_LR, betas=(0.9, 0.999))\n\nsteps_per_epoch = len(X_tr) // BATCH_SIZE\ntotal_steps  = EPOCHS * steps_per_epoch\nwarmup_steps = WARMUP_EPOCHS * steps_per_epoch\nMIN_FRAC = 1e-3\ndef lr_lambda(step):                      # stepped every iteration\n    if step < warmup_steps:               # linear warmup\n        return (step + 1) / warmup_steps\n    t = (step - warmup_steps) / max(1, total_steps - warmup_steps)\n    return MIN_FRAC + (1 - MIN_FRAC) * 0.5 * (1 + math.cos(math.pi * t))  # cosine annealing\nscheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda)\n\ncriterion = nn.CrossEntropyLoss(label_smoothing=LABEL_SMOOTHING)\nscaler = torch.amp.GradScaler('cuda', enabled=(device.type == 'cuda'))\n\n# ------------------------------ Eval ---------------------------------\n@torch.no_grad()\ndef predict_probs(m, x_uint8, tta=False, bs=1000):\n    m.eval()\n    outs = []\n    for i in range(0, len(x_uint8), bs):\n        xb = normalize(x_uint8[i:i + bs].to(device)).contiguous(memory_format=torch.channels_last)\n        with torch.autocast(device.type, dtype=torch.float16, enabled=(device.type == 'cuda')):\n            p = m(xb).float().softmax(1)\n            if tta:\n                p = (p + m(xb.flip(3)).float().softmax(1)) / 2\n        outs.append(p)\n    return torch.cat(outs)\n\ndef val_acc(tta=False):\n    return (predict_probs(model, X_val, tta).argmax(1) == y_val).float().mean().item()\n\n# ----------------------------- Training ------------------------------\nfor epoch in range(EPOCHS):\n    model.train()\n    t0, run_loss = time.time(), 0.0\n    order = torch.randperm(len(X_tr), device=device)\n    for s in range(steps_per_epoch):\n        idx = order[s * BATCH_SIZE:(s + 1) * BATCH_SIZE]\n        xb, yb = normalize(X_tr[idx]), y_tr[idx]\n        xb = random_flip(random_crop(xb))                     # basic aug\n        use_cutmix = np.random.rand() < CUTMIX_PROB           # advanced aug\n        if use_cutmix:\n            xb, ya, yb2, lam = cutmix(xb, yb)\n        xb = xb.contiguous(memory_format=torch.channels_last)\n\n        optimizer.zero_grad(set_to_none=True)\n        with torch.autocast(device.type, dtype=torch.float16, enabled=(device.type == 'cuda')):\n            out = model(xb)\n            loss = (lam * criterion(out, ya) + (1 - lam) * criterion(out, yb2)) if use_cutmix \\\n                   else criterion(out, yb)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        scheduler.step()\n        run_loss += loss.item()\n    va = val_acc()\n    print(f'Epoch [{epoch+1}/{EPOCHS}] | LR {scheduler.get_last_lr()[0]:.5f} | '\n          f'Loss {run_loss/steps_per_epoch:.4f} | Val Acc {va:.4f} | {time.time()-t0:.0f}s', flush=True)\n\n# Use the FINAL weights (cosine ends at ~0 LR, so the last epoch is normally best).\nprint(f'Final val acc: {val_acc():.4f} | with flip-TTA: {val_acc(tta=True):.4f}')\ntorch.save(model.state_dict(), 'model.pt')\n\n# ------------------------ Kaggle test predictions --------------------\ntest_paths = sorted(glob.glob(f'{TEST_DIR}/*.png'),\n                    key=lambda p: int(os.path.basename(p).split('.')[0]))\nprint('Test images:', len(test_paths))\nids, preds, CHUNK = [], [], 20000\nfor i in range(0, len(test_paths), CHUNK):\n    chunk = test_paths[i:i + CHUNK]\n    probs = predict_probs(model, load_images(chunk), tta=True)\n    preds.extend(probs.argmax(1).cpu().tolist())\n    ids.extend(int(os.path.basename(p).split('.')[0]) for p in chunk)\n    print(f'  predicted {len(preds)}/{len(test_paths)}', flush=True)\n\npd.DataFrame({'id': ids, 'label': [classes[p] for p in preds]}).to_csv('submission.csv', index=False)\nprint(\"Saved submission.csv (in /kaggle/working/)\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from IPython.display import FileLink\nFileLink('submission.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-15T20:20:36.738417Z","iopub.execute_input":"2026-10-15T20:20:36.738681Z","iopub.status.idle":"2026-10-15T20:20:36.744829Z","shell.execute_reply.started":"2026-10-15T20:20:36.73866Z","shell.execute_reply":"2026-10-15T20:20:36.744026Z"}},"outputs":[],"execution_count":null}]}