{"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":"import os, glob, time, math\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\nimport matplotlib.pyplot as plt\n\nSEED            = 42\nEPOCHS          = 40\nBATCH_SIZE      = 256\nMAX_LR          = 0.2\nWEIGHT_DECAY    = 5e-4\nLABEL_SMOOTHING = 0.1\nVAL_SIZE        = 5000\n\nOUT_DIR = \"/kaggle/working\"\ntorch.manual_seed(SEED); np.random.seed(SEED)\ntorch.backends.cudnn.benchmark = True\ndevice  = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nuse_amp = device == \"cuda\"\nprint(\"device:\", device, \"|\", torch.cuda.get_device_name(0) if use_amp else \"CPU only\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T08:58:08.832514Z","iopub.execute_input":"2026-09-23T08:58:08.833458Z","iopub.status.idle":"2026-09-23T08:58:13.147231Z","shell.execute_reply.started":"2026-09-23T08:58:08.833413Z","shell.execute_reply":"2026-09-23T08:58:13.146526Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!apt-get update -qq\n!apt-get install -y -qq libarchive13 libarchive-dev > /dev/null\n!pip install -q libarchive-c\nimport libarchive\nprint(\"libarchive OK\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T08:59:18.198525Z","iopub.execute_input":"2026-09-23T08:59:18.19924Z","iopub.status.idle":"2026-09-23T08:59:34.282237Z","shell.execute_reply.started":"2026-09-23T08:59:18.199203Z","shell.execute_reply":"2026-09-23T08:59:34.281478Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import io, libarchive\n\ndef find(name):\n    hits = glob.glob(f\"/kaggle/input/**/{name}\", recursive=True)\n    assert hits, f\"{name} not found. Attach the CIFAR-10 competition data.\"\n    return hits[0]\n\ndef decode(b):\n    return np.asarray(Image.open(io.BytesIO(b)).convert(\"RGB\"), dtype=np.uint8)\n\ndef load_7z_in_memory(archive_path, ids):\n    raw, t0 = {}, time.time()\n    with libarchive.file_reader(archive_path) as arc:\n        for entry in arc:\n            if not entry.pathname.endswith(\".png\"):\n                continue\n            raw[int(os.path.basename(entry.pathname)[:-4])] = b\"\".join(entry.get_blocks())\n            if len(raw) % 50000 == 0:\n                print(f\"  read {len(raw)} files ({time.time()-t0:.0f}s)\")\n    missing = [i for i in ids if i not in raw]\n    assert not missing, f\"{len(missing)} ids missing from archive\"\n    with ThreadPoolExecutor(max_workers=max(4, (os.cpu_count() or 2) * 2)) as ex:\n        arrs = list(ex.map(decode, (raw[i] for i in ids)))\n    print(f\"  decoded in {time.time()-t0:.0f}s total\")\n    return np.stack(arrs)\n\nlabels_df = pd.read_csv(find(\"trainLabels.csv\"))\nclasses   = sorted(labels_df[\"label\"].unique())\ncls2idx   = {c: i for i, c in enumerate(classes)}\ny_all     = labels_df[\"label\"].map(cls2idx).values\nprint(\"classes:\", classes)\n\nX_all = load_7z_in_memory(find(\"train.7z\"), labels_df[\"id\"].values)\nprint(\"train\", X_all.shape)\n\nsample_sub = pd.read_csv(find(\"sampleSubmission.csv\"))\ntest_ids   = sample_sub[\"id\"].values\nX_test = load_7z_in_memory(find(\"test.7z\"), test_ids)\nprint(\"test \", X_test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T09:02:53.576397Z","iopub.execute_input":"2026-09-23T09:02:53.57674Z","iopub.status.idle":"2026-09-23T09:05:06.615371Z","shell.execute_reply.started":"2026-09-23T09:02:53.57671Z","shell.execute_reply":"2026-09-23T09:05:06.614534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"rng  = np.random.RandomState(SEED)\nperm = rng.permutation(len(X_all))\nval_idx, tr_idx = perm[:VAL_SIZE], perm[VAL_SIZE:]\n\nto_nchw = lambda a: torch.from_numpy(a).permute(0, 3, 1, 2).contiguous()\nXtr, ytr   = to_nchw(X_all[tr_idx]).to(device),  torch.from_numpy(y_all[tr_idx]).long().to(device)\nXval, yval = to_nchw(X_all[val_idx]).to(device), torch.from_numpy(y_all[val_idx]).long().to(device)\nXte = to_nchw(X_test)\nprint(\"train\", tuple(Xtr.shape), \"| val\", tuple(Xval.shape), \"| test\", tuple(Xte.shape))\n\nMEAN = torch.tensor([0.4914, 0.4822, 0.4465], device=device).view(1, 3, 1, 1) * 255\nSTD  = torch.tensor([0.2470, 0.2435, 0.2616], device=device).view(1, 3, 1, 1) * 255\ndef normalize(x_uint8):\n    return (x_uint8.float() - MEAN) / STD","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T09:06:17.858323Z","iopub.execute_input":"2026-09-23T09:06:17.858826Z","iopub.status.idle":"2026-09-23T09:06:18.85543Z","shell.execute_reply.started":"2026-09-23T09:06:17.858792Z","shell.execute_reply":"2026-09-23T09:06:18.854792Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def augment(x):\n    B, dev = x.size(0), x.device\n    xp = F.pad(x, (4, 4, 4, 4), mode=\"reflect\")\n    i = torch.randint(0, 9, (B,), device=dev)\n    j = torch.randint(0, 9, (B,), device=dev)\n    ar = torch.arange(32, device=dev)\n    rows = (i[:, None] + ar)[:, None, :, None]\n    cols = (j[:, None] + ar)[:, None, None, :]\n    bidx = torch.arange(B, device=dev)[:, None, None, None]\n    cidx = torch.arange(3, device=dev)[None, :, None, None]\n    out  = xp[bidx, cidx, rows, cols]\n    flip = torch.rand(B, device=dev) < 0.5\n    return torch.where(flip[:, None, None, None], out.flip(3), out)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T09:07:29.34398Z","iopub.execute_input":"2026-09-23T09:07:29.344417Z","iopub.status.idle":"2026-09-23T09:07:29.350603Z","shell.execute_reply.started":"2026-09-23T09:07:29.344389Z","shell.execute_reply":"2026-09-23T09:07:29.349694Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BasicBlock(nn.Module):\n    def __init__(self, in_planes, planes, stride=1):\n        super().__init__()\n        self.conv1 = nn.Conv2d(in_planes, planes, 3, stride, 1, bias=False)\n        self.bn1   = nn.BatchNorm2d(planes)\n        self.conv2 = nn.Conv2d(planes, planes, 3, 1, 1, bias=False)\n        self.bn2   = nn.BatchNorm2d(planes)\n        self.shortcut = nn.Sequential()\n        if stride != 1 or in_planes != planes:\n            self.shortcut = nn.Sequential(nn.Conv2d(in_planes, planes, 1, stride, bias=False), nn.BatchNorm2d(planes))\n    def forward(self, x):\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        return F.relu(out + self.shortcut(x))\n\nclass ResNet18(nn.Module):\n    def __init__(self, num_classes=10):\n        super().__init__()\n        self.conv1 = nn.Conv2d(3, 64, 3, 1, 1, bias=False)\n        self.bn1   = nn.BatchNorm2d(64)\n        cfg, layers, in_planes = [(64, 1), (128, 2), (256, 2), (512, 2)], [], 64\n        for planes, stride in cfg:\n            layers += [BasicBlock(in_planes, planes, stride), BasicBlock(planes, planes, 1)]\n            in_planes = planes\n        self.layers = nn.Sequential(*layers)\n        self.fc = nn.Linear(512, num_classes)\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, BasicBlock):\n                nn.init.zeros_(m.bn2.weight)\n    def forward(self, x):\n        x = F.relu(self.bn1(self.conv1(x)))\n        x = self.layers(x)\n        x = F.adaptive_avg_pool2d(x, 1).flatten(1)\n        return self.fc(x)\n\nmodel = ResNet18().to(device).to(memory_format=torch.channels_last)\nprint(f\"parameters: {sum(p.numel() for p in model.parameters())/1e6:.2f}M\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T09:07:57.336417Z","iopub.execute_input":"2026-09-23T09:07:57.337129Z","iopub.status.idle":"2026-09-23T09:07:57.510591Z","shell.execute_reply.started":"2026-09-23T09:07:57.337095Z","shell.execute_reply":"2026-09-23T09:07:57.509975Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"steps_per_epoch = math.ceil(len(Xtr) / BATCH_SIZE)\ncriterion = nn.CrossEntropyLoss(label_smoothing=LABEL_SMOOTHING)\noptimizer = torch.optim.SGD(model.parameters(), lr=MAX_LR, momentum=0.9, weight_decay=WEIGHT_DECAY, nesterov=True)\nscheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=MAX_LR, epochs=EPOCHS, steps_per_epoch=steps_per_epoch,\n                                                pct_start=0.15, cycle_momentum=False)\nscaler = torch.amp.GradScaler(\"cuda\", enabled=use_amp)\n\n@torch.no_grad()\ndef predict_probs(model, X, bs=1000, tta=True):\n    model.eval(); outs = []\n    for k in range(0, len(X), bs):\n        xb = normalize(X[k:k+bs].to(device, non_blocking=True))\n        with torch.autocast(device_type=device, dtype=torch.float16, enabled=use_amp):\n            p = model(xb.contiguous(memory_format=torch.channels_last)).float().softmax(1)\n            if tta:\n                p = p + model(xb.flip(3).contiguous(memory_format=torch.channels_last)).float().softmax(1)\n        outs.append((p / (2 if tta else 1)).cpu())\n    return torch.cat(outs)\n\nhistory = {\"train_loss\": [], \"train_acc\": [], \"val_acc\": []}\nt_start = time.time()\nfor epoch in range(EPOCHS):\n    model.train(); t0 = time.time()\n    order = torch.randperm(len(Xtr), device=device)\n    loss_sum = torch.zeros((), device=device); correct = torch.zeros((), device=device)\n    for k in range(0, len(Xtr), BATCH_SIZE):\n        idx = order[k:k+BATCH_SIZE]\n        xb  = augment(normalize(Xtr[idx])).contiguous(memory_format=torch.channels_last)\n        yb  = ytr[idx]\n        with torch.autocast(device_type=device, dtype=torch.float16, enabled=use_amp):\n            out  = model(xb)\n            loss = criterion(out, yb)\n        optimizer.zero_grad(set_to_none=True)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer); scaler.update(); scheduler.step()\n        loss_sum += loss.detach() * len(idx); correct += (out.argmax(1) == yb).sum()\n    tr_loss, tr_acc = loss_sum.item() / len(Xtr), correct.item() / len(Xtr)\n    val_acc = (predict_probs(model, Xval, tta=False).argmax(1).to(device) == yval).float().mean().item()\n    history[\"train_loss\"].append(tr_loss); history[\"train_acc\"].append(tr_acc); history[\"val_acc\"].append(val_acc)\n    print(f\"epoch {epoch+1:2d}/{EPOCHS} | loss {tr_loss:.3f} | train acc {tr_acc:.4f} | val acc {val_acc:.4f} | {time.time()-t0:.0f}s\")\nprint(f\"total training time: {(time.time()-t_start)/60:.1f} min\")\ntorch.save(model.state_dict(), f\"{OUT_DIR}/resnet18_cifar10.pt\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T09:08:32.860604Z","iopub.execute_input":"2026-09-23T09:08:32.8614Z","iopub.status.idle":"2026-09-23T09:16:30.833071Z","shell.execute_reply.started":"2026-09-23T09:08:32.861371Z","shell.execute_reply":"2026-09-23T09:16:30.832343Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for tta in (False, True):\n    acc = (predict_probs(model, Xval, tta=tta).argmax(1).to(device) == yval).float().mean().item()\n    print(f\"held-out accuracy, TTA={tta}: {acc:.4f}\")\n\nfig, ax = plt.subplots(1, 2, figsize=(10, 3.5))\nax[0].plot(history[\"train_loss\"]); ax[0].set_title(\"training loss\"); ax[0].set_xlabel(\"epoch\")\nax[1].plot(history[\"train_acc\"], label=\"train\"); ax[1].plot(history[\"val_acc\"], label=\"held-out\")\nax[1].set_title(\"accuracy\"); ax[1].set_xlabel(\"epoch\"); ax[1].legend()\nplt.tight_layout(); plt.savefig(f\"{OUT_DIR}/curves.png\", dpi=120); plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T09:26:49.489195Z","iopub.execute_input":"2026-09-23T09:26:49.490166Z","iopub.status.idle":"2026-09-23T09:26:51.356807Z","shell.execute_reply.started":"2026-09-23T09:26:49.490131Z","shell.execute_reply":"2026-09-23T09:26:51.35607Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"t0 = time.time()\nprobs = predict_probs(model, Xte, bs=1000, tta=True)\npred  = probs.argmax(1).numpy()\nprint(f\"predicted {len(pred)} images in {time.time()-t0:.0f}s\")\n\nsubmission = pd.DataFrame({\"id\": test_ids, \"label\": [classes[i] for i in pred]})\nassert len(submission) == 300_000 and list(submission.columns) == list(sample_sub.columns)\nsubmission.to_csv(f\"{OUT_DIR}/submission.csv\", index=False)\nprint(submission.head())\nprint(submission[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T09:28:41.388731Z","iopub.execute_input":"2026-09-23T09:28:41.389588Z","iopub.status.idle":"2026-09-23T09:29:34.697384Z","shell.execute_reply.started":"2026-09-23T09:28:41.389559Z","shell.execute_reply":"2026-09-23T09:29:34.69672Z"}},"outputs":[],"execution_count":null}]}