{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","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":"# =============================================================\n# CIFAR-10 Kaggle competition - ResNet-18 (CIFAR variant)\n# Trained FROM SCRATCH: all weights randomly initialised.\n# No pre-trained weights anywhere in this notebook.\n# =============================================================\nimport os, io, time, math, subprocess\nimport numpy as np, pandas as pd\nimport torch, torch.nn as nn, torch.nn.functional as F\nfrom PIL import Image\n\nSEED = 0\ntorch.manual_seed(SEED); np.random.seed(SEED)\ntorch.backends.cudnn.benchmark = True\ndev = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint('device:', dev, torch.cuda.get_device_name(0) if torch.cuda.is_available() else '')\n\nimport glob\n_c = glob.glob('/kaggle/input/**/train.7z', recursive=True)\nprint('train.7z found at:', _c)\nIN = os.path.dirname(_c[0])\nWORK = '/kaggle/working'\n\n# ---------- 1. read the .7z archives without unpacking them to disk ----------\n# Unpacking 350k tiny PNGs onto the container filesystem takes hours, so instead\n# `7z x -so` streams every file to stdout in archive order and each PNG is\n# decoded straight from that stream. Nothing is written to disk.\ndef archive_order(path):\n    out = subprocess.run(['7z', 'l', '-slt', path],\n                         capture_output=True, text=True, check=True).stdout\n    return [l.split(' = ', 1)[1].strip() for l in out.splitlines()\n            if l.startswith('Path = ') and l.rstrip().endswith('.png')]\n\ndef _read_exact(f, n):\n    b = b''\n    while len(b) < n:\n        c = f.read(n - len(b))\n        if not c:\n            raise EOFError('archive stream ended early')\n        b += c\n    return b\n\ndef _read_png(f):\n    out = [_read_exact(f, 8)]                                   # PNG signature\n    while True:\n        hdr = _read_exact(f, 8)                                 # length + chunk type\n        out.append(hdr)\n        out.append(_read_exact(f, int.from_bytes(hdr[:4], 'big') + 4))\n        if hdr[4:8] == b'IEND':\n            return b''.join(out)\n\ndef stream_images(path):\n    names = archive_order(path)\n    print(f'{os.path.basename(path)}: {len(names)} images')\n    p = subprocess.Popen(['7z', 'x', '-so', '-bso0', '-bsp0', path],\n                         stdout=subprocess.PIPE, bufsize=1 << 22)\n    try:\n        for name in names:\n            yield name, np.asarray(Image.open(io.BytesIO(_read_png(p.stdout))).convert('RGB'))\n    finally:\n        p.stdout.close(); p.wait()\n\n# ---------- 2. load the training set into memory ----------\nlabels_df = pd.read_csv(f'{IN}/trainLabels.csv')\nCLASSES = sorted(labels_df['label'].unique())\ncls2idx = {c: i for i, c in enumerate(CLASSES)}\nprint(CLASSES)\n\nt0 = time.time()\nimgs = dict(stream_images(f'{IN}/train.7z'))\nX = np.stack([imgs[f'train/{i}.png'] for i in labels_df['id']])\ndel imgs\ny = np.array([cls2idx[c] for c in labels_df['label']], dtype=np.int64)\nprint(f'train loaded {X.shape} in {time.time()-t0:.0f}s')\n\n# small stratified hold-out purely for monitoring (the model still sees 49k imgs)\nrng = np.random.RandomState(SEED)\nval_idx = np.concatenate([rng.permutation(np.where(y == c)[0])[:100] for c in range(10)])\nmask = np.ones(len(y), bool); mask[val_idx] = False\nXtr, ytr, Xva, yva = X[mask], y[mask], X[val_idx], y[val_idx]\nprint('train/val split:', Xtr.shape[0], Xva.shape[0])\n\nMEAN = torch.tensor([0.4914, 0.4822, 0.4465], device=dev).view(1, 3, 1, 1) * 255\nSTD  = torch.tensor([0.2470, 0.2435, 0.2616], device=dev).view(1, 3, 1, 1) * 255\n\ndef to_gpu_u8(a):  # NHWC uint8 -> NCHW uint8 on gpu\n    return torch.from_numpy(np.ascontiguousarray(a)).to(dev).permute(0, 3, 1, 2).contiguous()\n\nXtr_g, Xva_g = to_gpu_u8(Xtr), to_gpu_u8(Xva)\nytr_g, yva_g = torch.from_numpy(ytr).to(dev), torch.from_numpy(yva).to(dev)\n\ndef norm(xb):\n    return (xb.float() - MEAN) / STD","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-19T15:00:16.81376Z","iopub.execute_input":"2026-09-19T15:00:16.814498Z","iopub.status.idle":"2026-09-19T15:13:21.257558Z","shell.execute_reply.started":"2026-09-19T15:00:16.814465Z","shell.execute_reply":"2026-09-19T15:13:21.256443Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---------- 3. GPU-side augmentation ----------\nPAD = 4\nar32 = torch.arange(32, device=dev)\ndef augment(x):                      # x: (N,3,32,32) float, normalised\n    N = x.size(0)\n    xp = F.pad(x, (PAD,)*4, mode='reflect')\n    i = torch.randint(0, 2*PAD+1, (N,), device=dev)\n    j = torch.randint(0, 2*PAD+1, (N,), device=dev)\n    rows = (i[:, None] + ar32[None, :])[:, None, :, None]\n    cols = (j[:, None] + ar32[None, :])[:, None, None, :]\n    n = torch.arange(N, device=dev)[:, None, None, None]\n    c = torch.arange(3, device=dev)[None, :, None, None]\n    x = xp[n, c, rows, cols]\n    flip = torch.rand(N, device=dev) < 0.5                       # random horizontal flip\n    x = torch.where(flip[:, None, None, None], x.flip(-1), x)\n    cy = torch.randint(0, 32, (N,), device=dev)                  # cutout 8x8\n    cx = torch.randint(0, 32, (N,), device=dev)\n    my = (ar32[None, :] - cy[:, None]).abs() < 4\n    mx = (ar32[None, :] - cx[:, None]).abs() < 4\n    return x.masked_fill((my[:, :, None] & mx[:, None, :])[:, None], 0.)\n\n# ---------- 4. CIFAR ResNet-18 (random init) ----------\nclass Block(nn.Module):\n    def __init__(s, cin, cout, stride=1):\n        super().__init__()\n        s.c1 = nn.Conv2d(cin, cout, 3, stride, 1, bias=False); s.b1 = nn.BatchNorm2d(cout)\n        s.c2 = nn.Conv2d(cout, cout, 3, 1, 1, bias=False);     s.b2 = nn.BatchNorm2d(cout)\n        s.sc = nn.Sequential()\n        if stride != 1 or cin != cout:\n            s.sc = nn.Sequential(nn.Conv2d(cin, cout, 1, stride, bias=False),\n                                 nn.BatchNorm2d(cout))\n    def forward(s, x):\n        o = F.relu(s.b1(s.c1(x)))\n        o = s.b2(s.c2(o))\n        return F.relu(o + s.sc(x))\n\nclass ResNet18(nn.Module):\n    def __init__(s, n_cls=10):\n        super().__init__()\n        # CIFAR stem: 3x3 conv, no max-pool, so 32x32 input is not downsampled early\n        s.stem = nn.Sequential(nn.Conv2d(3, 64, 3, 1, 1, bias=False),\n                               nn.BatchNorm2d(64), nn.ReLU(inplace=True))\n        cfg = [(64, 1), (128, 2), (256, 2), (512, 2)]\n        layers, cin = [], 64\n        for cout, st in cfg:\n            layers += [Block(cin, cout, st), Block(cout, cout, 1)]\n            cin = cout\n        s.body = nn.Sequential(*layers)\n        s.fc = nn.Linear(512, n_cls)\n        for m in s.modules():                     # random (He) initialisation\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.ones_(m.weight); nn.init.zeros_(m.bias)\n        for m in s.modules():                     # zero-init last BN in each block\n            if isinstance(m, Block):\n                nn.init.zeros_(m.b2.weight)\n    def forward(s, x):\n        x = s.body(s.stem(x))\n        return s.fc(F.adaptive_avg_pool2d(x, 1).flatten(1))\n\nmodel = ResNet18().to(dev).to(memory_format=torch.channels_last)\nprint('params: %.2fM' % (sum(p.numel() for p in model.parameters()) / 1e6))\n\n# ---------- 5. training ----------\nEPOCHS, BS, LR, WD, WARM = 80, 128, 0.1, 5e-4, 2\ndecay = [p for n, p in model.named_parameters() if p.ndim > 1]\nnodecay = [p for n, p in model.named_parameters() if p.ndim <= 1]\nopt = torch.optim.SGD([{'params': decay, 'weight_decay': WD},\n                       {'params': nodecay, 'weight_decay': 0.0}],\n                      lr=LR, momentum=0.9, nesterov=True)\ncrit = nn.CrossEntropyLoss(label_smoothing=0.1)\nscaler = torch.cuda.amp.GradScaler()\nsteps = math.ceil(len(ytr_g) / BS)\ndef lr_at(ep, it):\n    t = ep + it / steps\n    if t < WARM: return LR * t / WARM\n    return 0.5 * LR * (1 + math.cos(math.pi * (t - WARM) / (EPOCHS - WARM)))\n\n@torch.no_grad()\ndef evaluate(Xg, yg, bs=1000):\n    model.eval(); correct = 0\n    for k in range(0, len(yg), bs):\n        xb = norm(Xg[k:k+bs]).to(memory_format=torch.channels_last)\n        with torch.cuda.amp.autocast():\n            p = model(xb) + model(xb.flip(-1))       # hflip TTA\n        correct += (p.argmax(1) == yg[k:k+bs]).sum().item()\n    return correct / len(yg)\n\nhist = []\nt0 = time.time()\nfor ep in range(EPOCHS):\n    model.train()\n    perm = torch.randperm(len(ytr_g), device=dev)\n    tot = 0.0\n    for it in range(steps):\n        idx = perm[it*BS:(it+1)*BS]\n        xb = augment(norm(Xtr_g[idx])).to(memory_format=torch.channels_last)\n        yb = ytr_g[idx]\n        for g in opt.param_groups: g['lr'] = lr_at(ep, it)\n        opt.zero_grad(set_to_none=True)\n        with torch.cuda.amp.autocast():\n            loss = crit(model(xb), yb)\n        scaler.scale(loss).backward(); scaler.step(opt); scaler.update()\n        tot += loss.item()\n    if ep % 5 == 4 or ep >= EPOCHS - 5 or ep == 0:\n        acc = evaluate(Xva_g, yva_g)\n        hist.append((ep+1, tot/steps, acc))\n        print(f'ep {ep+1:3d}/{EPOCHS}  loss {tot/steps:.3f}  val_acc {acc:.4f}  '\n              f'lr {lr_at(ep, steps-1):.4f}  {time.time()-t0:.0f}s')\nprint('training time: %.1f min' % ((time.time()-t0)/60))\nfinal_val = evaluate(Xva_g, yva_g)\nprint('FINAL held-out accuracy:', final_val)\ntorch.save(model.state_dict(), f'{WORK}/resnet18_cifar.pt')\npd.DataFrame(hist, columns=['epoch', 'train_loss', 'val_acc']).to_csv(f'{WORK}/history.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T14:55:27.65765Z","iopub.status.idle":"2026-09-19T14:55:27.658036Z","shell.execute_reply.started":"2026-09-19T14:55:27.657842Z","shell.execute_reply":"2026-09-19T14:55:27.657869Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---------- 6. predict the 300k Kaggle test images ----------\n# Streamed straight out of test.7z in 20k-image batches, so the images are\n# never written to disk and only one batch is held in memory at a time.\nmodel.eval()\nids, chunks = [], []\nbuf_id, buf_px = [], []\nt0 = time.time()\n\ndef predict(px):\n    xg = to_gpu_u8(np.stack(px))\n    out = []\n    with torch.no_grad():\n        for k in range(0, len(px), 1000):\n            xb = norm(xg[k:k+1000]).to(memory_format=torch.channels_last)\n            with torch.cuda.amp.autocast():\n                p = model(xb) + model(xb.flip(-1))       # horizontal-flip TTA\n            out.append(p.argmax(1).cpu().numpy())\n    return np.concatenate(out)\n\nfor name, arr in stream_images(f'{IN}/test.7z'):\n    buf_id.append(int(os.path.basename(name).split('.')[0]))\n    buf_px.append(arr)\n    if len(buf_px) == 20000:\n        ids.extend(buf_id); chunks.append(predict(buf_px)); buf_id, buf_px = [], []\n        print(f'  {len(ids)} predicted  {time.time()-t0:.0f}s')\nif buf_px:\n    ids.extend(buf_id); chunks.append(predict(buf_px))\n\npreds = np.concatenate(chunks)\nsub = pd.DataFrame({'id': ids, 'label': [CLASSES[i] for i in preds]}).sort_values('id')\nsub.to_csv(f'{WORK}/submission.csv', index=False)\nprint(sub.head())\nprint('submission rows:', len(sub), ' unique ids:', sub['id'].nunique())\nprint('HELD-OUT ACCURACY (estimate of Kaggle score):', final_val)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-19T14:55:27.659866Z","iopub.status.idle":"2026-09-19T14:55:27.66021Z","shell.execute_reply.started":"2026-09-19T14:55:27.660077Z","shell.execute_reply":"2026-09-19T14:55:27.660095Z"}},"outputs":[],"execution_count":null}]}