{"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, time, glob, shutil, subprocess\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torchvision\nfrom PIL import Image\n\ntorch.manual_seed(0)\nnp.random.seed(0)\ntorch.backends.cudnn.benchmark = True\n\nassert torch.cuda.is_available()\ndevice = \"cuda\"\nprint(\"PyTorch\", torch.__version__, \"| GPU:\", torch.cuda.get_device_name(0))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-25T10:15:43.712246Z","iopub.execute_input":"2026-09-25T10:15:43.712967Z","iopub.status.idle":"2026-09-25T10:15:52.032591Z","shell.execute_reply.started":"2026-09-25T10:15:43.712932Z","shell.execute_reply":"2026-09-25T10:15:52.031867Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_ds = torchvision.datasets.CIFAR10(\"/tmp/data\", train=True,  download=True)\ntest_ds  = torchvision.datasets.CIFAR10(\"/tmp/data\", train=False, download=True)\nCLASSES = train_ds.classes   # same names and order as the Kaggle labels\nprint(CLASSES)\n\nMEAN = torch.tensor([0.4914, 0.4822, 0.4465], device=device).view(1, 3, 1, 1)\nSTD  = torch.tensor([0.2470, 0.2435, 0.2616], device=device).view(1, 3, 1, 1)\n\ndef to_gpu_tensor(arr):\n    \"\"\"uint8 array/tensor of shape (N, 32, 32, 3) -> normalized float tensor (N, 3, 32, 32) on GPU.\"\"\"\n    x = torch.as_tensor(arr).to(device).permute(0, 3, 1, 2).float().div(255)\n    return (x - MEAN) / STD\n\nX_train = to_gpu_tensor(train_ds.data)\ny_train = torch.tensor(train_ds.targets, device=device)\nX_test  = to_gpu_tensor(test_ds.data)\ny_test  = torch.tensor(test_ds.targets, device=device)\nprint(\"train:\", X_train.shape, \" test:\", X_test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-25T10:15:52.033866Z","iopub.execute_input":"2026-09-25T10:15:52.034278Z","iopub.status.idle":"2026-09-25T10:58:13.123062Z","shell.execute_reply.started":"2026-09-25T10:15:52.034252Z","shell.execute_reply":"2026-09-25T10:58:13.122386Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def augment(x):\n    B = x.size(0)\n    x = F.pad(x, (4, 4, 4, 4), mode=\"reflect\")                       \n    i = torch.randint(0, 9, (B,), device=x.device)\n    j = torch.randint(0, 9, (B,), device=x.device)\n    ar = torch.arange(32, device=x.device)\n    rows = (i.view(B, 1) + ar).view(B, 1, 32, 1)\n    cols = (j.view(B, 1) + ar).view(B, 1, 1, 32)\n    b = torch.arange(B, device=x.device).view(B, 1, 1, 1)\n    c = torch.arange(3, device=x.device).view(1, 3, 1, 1)\n    x = x[b, c, rows, cols]                                          \n    flip = torch.rand(B, device=x.device) < 0.5\n    return torch.where(flip.view(B, 1, 1, 1), x.flip(3), x)            ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-25T10:58:13.124126Z","iopub.execute_input":"2026-09-25T10:58:13.124507Z","iopub.status.idle":"2026-09-25T10:58:13.131765Z","shell.execute_reply.started":"2026-09-25T10:58:13.124435Z","shell.execute_reply":"2026-09-25T10:58:13.131109Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BasicBlock(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        self.shortcut = nn.Sequential()\n        if stride != 1 or cin != cout:\n            self.shortcut = nn.Sequential(nn.Conv2d(cin, cout, 1, stride, bias=False),\n                                          nn.BatchNorm2d(cout))\n\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\n\nclass ResNet18(nn.Module):\n    def __init__(self, num_classes=10):\n        super().__init__()\n        self.stem = nn.Sequential(nn.Conv2d(3, 64, 3, 1, 1, bias=False),\n                                  nn.BatchNorm2d(64), nn.ReLU(inplace=True))\n        layers, cin = [], 64\n        for cout, stride in [(64, 1), (128, 2), (256, 2), (512, 2)]:\n            layers += [BasicBlock(cin, cout, stride), BasicBlock(cout, cout, 1)]\n            cin = cout\n        self.layers = nn.Sequential(*layers)\n        self.fc = nn.Linear(512, num_classes)\n\n    def forward(self, x):\n        x = self.layers(self.stem(x))\n        x = F.adaptive_avg_pool2d(x, 1).flatten(1)\n        return self.fc(x)\n\n\nmodel = ResNet18().to(device)   \nprint(f\"Parameters: {sum(p.numel() for p in model.parameters()) / 1e6:.1f}M\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-25T10:58:13.13322Z","iopub.execute_input":"2026-09-25T10:58:13.133823Z","iopub.status.idle":"2026-09-25T10:58:13.2461Z","shell.execute_reply.started":"2026-09-25T10:58:13.133784Z","shell.execute_reply":"2026-09-25T10:58:13.24552Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@torch.no_grad()\ndef predict_logits(model, X, bs=1000, tta=True):\n    model.eval()\n    outs = []\n    for k in range(0, len(X), bs):\n        xb = X[k:k + bs]\n        with torch.autocast(\"cuda\", dtype=torch.float16):\n            logits = model(xb).float()\n            if tta:                                 \n                logits = logits + model(xb.flip(3)).float()\n        outs.append(logits)\n    return torch.cat(outs)\n\ndef accuracy(model, X, y, tta=True):\n    return (predict_logits(model, X, tta=tta).argmax(1) == y).float().mean().item()\n\n\nEPOCHS = 30\nBATCH = 256\nsteps_per_epoch = len(X_train) // BATCH\n\noptimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9,\n                            nesterov=True, weight_decay=5e-4)\nscheduler = torch.optim.lr_scheduler.OneCycleLR(optimizer, max_lr=0.1, epochs=EPOCHS,\n                                                steps_per_epoch=steps_per_epoch)\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\nscaler = torch.amp.GradScaler(\"cuda\")\n\nhistory = []\nstart = time.time()\nfor epoch in range(1, EPOCHS + 1):\n    model.train()\n    t0 = time.time()\n    perm = torch.randperm(len(X_train), device=device)\n    total_loss, correct, seen = 0.0, 0, 0\n    for s in range(steps_per_epoch):\n        idx = perm[s * BATCH:(s + 1) * BATCH]\n        xb, yb = augment(X_train[idx]), y_train[idx]\n        with torch.autocast(\"cuda\", dtype=torch.float16):\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)\n        scaler.update()\n        scheduler.step()\n        total_loss += loss.item() * len(yb)\n        correct += (out.argmax(1) == yb).sum().item()\n        seen += len(yb)\n\n    test_acc = accuracy(model, X_test, y_test, tta=False)\n    history.append((epoch, total_loss / seen, correct / seen, test_acc))\n    print(f\"epoch {epoch:2d}/{EPOCHS} | loss {total_loss / seen:.4f} | train acc {correct / seen:.4f} \"\n          f\"| test acc {test_acc:.4f} | {time.time() - t0:.1f}s\")\n\nprint(f\"Total training time: {(time.time() - start) / 60:.1f} min\")\ntorch.save(model.state_dict(), \"/kaggle/working/resnet18_cifar10.pt\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-25T10:58:13.247524Z","iopub.execute_input":"2026-09-25T10:58:13.248125Z","iopub.status.idle":"2026-09-25T11:07:25.651903Z","shell.execute_reply.started":"2026-09-25T10:58:13.248089Z","shell.execute_reply":"2026-09-25T11:07:25.651047Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"acc_plain = accuracy(model, X_test, y_test, tta=False)\nacc_tta   = accuracy(model, X_test, y_test, tta=True)\nprint(f\"Official test accuracy: {acc_plain:.4f} (no TTA) | {acc_tta:.4f} (with flip TTA)\")\n\nimport matplotlib.pyplot as plt\nh = np.array(history)\nplt.figure(figsize=(7, 4))\nplt.plot(h[:, 0], h[:, 2], label=\"train acc (augmented)\")\nplt.plot(h[:, 0], h[:, 3], label=\"test acc\")\nplt.xlabel(\"epoch\"); plt.ylabel(\"accuracy\"); plt.legend(); plt.grid(alpha=0.3)\nplt.title(\"ResNet-18 on CIFAR-10\"); plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-25T11:07:25.652887Z","iopub.execute_input":"2026-09-25T11:07:25.653158Z","iopub.status.idle":"2026-09-25T11:07:29.131428Z","shell.execute_reply.started":"2026-09-25T11:07:25.653129Z","shell.execute_reply":"2026-09-25T11:07:29.130863Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, time, glob, shutil, subprocess\n\ntest_7z = \"/kaggle/input/competitions/cifar-10/test.7z\"\nassert os.path.exists(test_7z), \"test.7z not found - is the competition data added?\"\n\nif not shutil.which(\"7z\"):\n    subprocess.run(\"apt-get update -qq && apt-get install -y -qq p7zip-full > /dev/null\",\n                   shell=True, check=True)\n\nshutil.rmtree(\"/tmp/test\", ignore_errors=True)   # clear any half-finished attempt\nproc = subprocess.Popen([\"7z\", \"x\", \"-y\", test_7z, \"-o/tmp\"],\n                        stdout=subprocess.DEVNULL, stderr=subprocess.PIPE)\nt0 = time.time()\nwhile proc.poll() is None:\n    time.sleep(30)\n    n = len(os.listdir(\"/tmp/test\")) if os.path.isdir(\"/tmp/test\") else 0\n    print(f\"{time.time() - t0:5.0f}s elapsed: {n:,} / 300,000 images extracted\")\n\nprint(\"7z finished with code\", proc.returncode, proc.stderr.read().decode()[-300:])\nprint(\"Number of test images:\", len(glob.glob(\"/tmp/test/*.png\")))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-25T11:36:09.26037Z","iopub.execute_input":"2026-09-25T11:36:09.261009Z","iopub.status.idle":"2026-09-25T15:34:43.909222Z","shell.execute_reply.started":"2026-09-25T11:36:09.260978Z","shell.execute_reply":"2026-09-25T15:34:43.908523Z"},"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class PNGFolder(torch.utils.data.Dataset):\n    def __init__(self, ids):\n        self.ids = ids\n    def __len__(self):\n        return len(self.ids)\n    def __getitem__(self, k):\n        return np.array(Image.open(f\"/tmp/test/{self.ids[k]}.png\").convert(\"RGB\"))\n\nids = sorted(int(os.path.basename(p)[:-4]) for p in glob.glob(\"/tmp/test/*.png\"))\nassert len(ids) == 300000, len(ids)\n\nloader = torch.utils.data.DataLoader(PNGFolder(ids), batch_size=2000,\n                                     num_workers=os.cpu_count(), shuffle=False)\npreds = []\nfor batch in loader:\n    preds.append(predict_logits(model, to_gpu_tensor(batch), tta=True).argmax(1).cpu())\npreds = torch.cat(preds).numpy()\n\nsubmission = pd.DataFrame({\"id\": ids, \"label\": [CLASSES[p] for p in preds]})\nsubmission.to_csv(\"/kaggle/working/submission.csv\", index=False)\nprint(submission.head())\nprint(submission[\"label\"].value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-25T15:37:38.322018Z","iopub.execute_input":"2026-09-25T15:37:38.322576Z","iopub.status.idle":"2026-09-25T15:38:39.242866Z","shell.execute_reply.started":"2026-09-25T15:37:38.322545Z","shell.execute_reply":"2026-09-25T15:38:39.241818Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}