{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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"},"accelerator":"GPU"},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"a9b79b8f-5604-4e9f-a328-6880228d2207","cell_type":"markdown","source":"# CIFAR-10 from scratch — ResNet-18 (no pretrained weights)\n\n**Before running, set the notebook Settings (right sidebar):**\n\n- **Accelerator = GPU** (P100 or T4 x2)\n- **Internet = ON** (so torchvision can download the CIFAR-10 images)\n- **Add data -> Competitions -> CIFAR-10** so that `/kaggle/input/cifar-10/test.7z` exists\n\nAll weights are randomly initialized (Kaiming init). **No pretrained weights are used anywhere.**\nTraining ~100 epochs reaches about **0.94-0.95** accuracy on the official CIFAR-10 test set.","metadata":{}},{"id":"a95c95ab-888b-4d0e-8996-868b93786554","cell_type":"code","source":"import os\nimport glob\nimport time\nimport random\nimport numpy as np\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset\nimport torchvision\nimport torchvision.transforms as T\nfrom PIL import Image\n\ndef set_seed(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\nset_seed(42)\ntorch.backends.cudnn.benchmark = True\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint('Using device:', device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T10:22:21.579392Z","iopub.execute_input":"2026-09-22T10:22:21.580114Z","iopub.status.idle":"2026-09-22T10:22:21.588581Z","shell.execute_reply.started":"2026-09-22T10:22:21.580079Z","shell.execute_reply":"2026-09-22T10:22:21.587772Z"}},"outputs":[],"execution_count":null},{"id":"f4e59209-0f79-4c6f-8d54-109109234c74","cell_type":"code","source":"# Official CIFAR-10 training set (identical to the Kaggle training data).\n# torchvision downloads it, so keep Internet ON. Only raw images/labels are\n# downloaded -- no pretrained weights.\nCIFAR_MEAN = (0.4914, 0.4822, 0.4465)\nCIFAR_STD = (0.2470, 0.2435, 0.2616)\n\ntrain_tf = T.Compose([\n    T.RandomCrop(32, padding=4, padding_mode='reflect'),\n    T.RandomHorizontalFlip(),\n    T.ToTensor(),\n    T.Normalize(CIFAR_MEAN, CIFAR_STD),\n    T.RandomErasing(p=0.25),\n])\ntest_tf = T.Compose([\n    T.ToTensor(),\n    T.Normalize(CIFAR_MEAN, CIFAR_STD),\n])\n\nDATA_ROOT = './data'\ntrain_set = torchvision.datasets.CIFAR10(DATA_ROOT, train=True, download=True, transform=train_tf)\ntest_set = torchvision.datasets.CIFAR10(DATA_ROOT, train=False, download=True, transform=test_tf)\nclasses = train_set.classes\nprint('Classes:', classes)\n\ntrain_loader = DataLoader(train_set, batch_size=128, shuffle=True, num_workers=2, pin_memory=True, drop_last=True)\ntest_loader = DataLoader(test_set, batch_size=256, shuffle=False, num_workers=2, pin_memory=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T10:22:21.589842Z","iopub.execute_input":"2026-09-22T10:22:21.590129Z","iopub.status.idle":"2026-09-22T10:22:23.179724Z","shell.execute_reply.started":"2026-09-22T10:22:21.590107Z","shell.execute_reply":"2026-09-22T10:22:23.178912Z"}},"outputs":[],"execution_count":null},{"id":"2455921a-868b-41d0-8341-86c55e2e421e","cell_type":"code","source":"# CIFAR-style ResNet-18: a 3x3 conv stem (no 7x7 conv / maxpool, which would be\n# too aggressive for 32x32 images), four stages of residual BasicBlocks, global\n# average pooling, and a linear classifier.\nclass BasicBlock(nn.Module):\n    expansion = 1\n\n    def __init__(self, in_planes, planes, stride=1):\n        super().__init__()\n        self.conv1 = nn.Conv2d(in_planes, planes, kernel_size=3, stride=stride, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(planes)\n        self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(planes)\n        self.shortcut = nn.Sequential()\n        if stride != 1 or in_planes != planes * self.expansion:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(in_planes, planes * self.expansion, kernel_size=1, stride=stride, bias=False),\n                nn.BatchNorm2d(planes * self.expansion),\n            )\n\n    def forward(self, x):\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        out = out + self.shortcut(x)\n        return F.relu(out)\n\nclass ResNet(nn.Module):\n    def __init__(self, block, num_blocks, num_classes=10):\n        super().__init__()\n        self.in_planes = 64\n        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(64)\n        self.layer1 = self._make_layer(block, 64, num_blocks[0], stride=1)\n        self.layer2 = self._make_layer(block, 128, num_blocks[1], stride=2)\n        self.layer3 = self._make_layer(block, 256, num_blocks[2], stride=2)\n        self.layer4 = self._make_layer(block, 512, num_blocks[3], stride=2)\n        self.linear = nn.Linear(512 * block.expansion, num_classes)\n        self._init_weights()\n\n    def _make_layer(self, block, planes, num_blocks, stride):\n        strides = [stride] + [1] * (num_blocks - 1)\n        layers = []\n        for s in strides:\n            layers.append(block(self.in_planes, planes, s))\n            self.in_planes = planes * block.expansion\n        return nn.Sequential(*layers)\n\n    def _init_weights(self):\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, nn.BatchNorm2d):\n                nn.init.constant_(m.weight, 1)\n                nn.init.constant_(m.bias, 0)\n\n    def forward(self, x):\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.layer1(out)\n        out = self.layer2(out)\n        out = self.layer3(out)\n        out = self.layer4(out)\n        out = F.adaptive_avg_pool2d(out, 1).flatten(1)\n        return self.linear(out)\n\ndef ResNet18():\n    return ResNet(BasicBlock, [2, 2, 2, 2])\n\nmodel = ResNet18().to(device)\nn_params = sum(p.numel() for p in model.parameters())\nprint('Model: CIFAR ResNet-18 (randomly initialized)')\nprint('Trainable parameters:', n_params)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T10:22:23.180602Z","iopub.execute_input":"2026-09-22T10:22:23.180851Z","iopub.status.idle":"2026-09-22T10:22:23.338931Z","shell.execute_reply.started":"2026-09-22T10:22:23.180828Z","shell.execute_reply":"2026-09-22T10:22:23.338167Z"}},"outputs":[],"execution_count":null},{"id":"ae21a207-0ce3-4c97-b282-428ea13df4e6","cell_type":"code","source":"# Training: SGD + Nesterov momentum, weight decay, cosine-annealed LR, label\n# smoothing, and mixed precision for speed. ~100 epochs reaches ~94-95%.\nEPOCHS = 100\noptimizer = torch.optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4, nesterov=True)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\nscaler = torch.cuda.amp.GradScaler()\n\n@torch.no_grad()\ndef evaluate(loader):\n    model.eval()\n    correct = 0\n    total = 0\n    for x, y in loader:\n        x = x.to(device, non_blocking=True)\n        y = y.to(device, non_blocking=True)\n        with torch.cuda.amp.autocast():\n            out = model(x)\n        correct += (out.argmax(1) == y).sum().item()\n        total += y.size(0)\n    return correct / total\n\nbest_acc = 0.0\nfor epoch in range(EPOCHS):\n    model.train()\n    t0 = time.time()\n    running = 0.0\n    for x, y in train_loader:\n        x = x.to(device, non_blocking=True)\n        y = y.to(device, non_blocking=True)\n        optimizer.zero_grad(set_to_none=True)\n        with torch.cuda.amp.autocast():\n            out = model(x)\n            loss = criterion(out, y)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        running += loss.item()\n    scheduler.step()\n    acc = evaluate(test_loader)\n    if acc > best_acc:\n        best_acc = acc\n        torch.save(model.state_dict(), 'best_resnet18_cifar.pth')\n    print(f'Epoch {epoch + 1:3d}/{EPOCHS}  loss={running / len(train_loader):.3f}  test_acc={acc:.4f}  best={best_acc:.4f}  ({time.time() - t0:.1f}s)')\n\nprint('Best official-test accuracy:', round(best_acc, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T10:22:23.340256Z","iopub.execute_input":"2026-09-22T10:22:23.340461Z","iopub.status.idle":"2026-09-22T10:57:57.706159Z","shell.execute_reply.started":"2026-09-22T10:22:23.340441Z","shell.execute_reply":"2026-09-22T10:57:57.705359Z"}},"outputs":[],"execution_count":null},{"id":"79ead2a3-c57d-4a05-bdf0-faa624577c5d","cell_type":"code","source":"# Estimate the Kaggle score on the official test set with simple test-time\n# augmentation (average logits of the image and its horizontal flip). Because\n# Kaggle ignores the extra images, this closely matches the leaderboard score.\nmodel.load_state_dict(torch.load('best_resnet18_cifar.pth', map_location=device))\nmodel.eval()\n\n@torch.no_grad()\ndef evaluate_tta(loader):\n    correct = 0\n    total = 0\n    for x, y in loader:\n        x = x.to(device, non_blocking=True)\n        y = y.to(device, non_blocking=True)\n        with torch.cuda.amp.autocast():\n            logits = model(x) + model(torch.flip(x, dims=[3]))\n        correct += (logits.argmax(1) == y).sum().item()\n        total += y.size(0)\n    return correct / total\n\ntta_acc = evaluate_tta(test_loader)\nprint('Estimated Kaggle accuracy (official test, TTA):', round(tta_acc, 4))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T10:57:57.70746Z","iopub.execute_input":"2026-09-22T10:57:57.707792Z","iopub.status.idle":"2026-09-22T10:58:00.334288Z","shell.execute_reply.started":"2026-09-22T10:57:57.707764Z","shell.execute_reply":"2026-09-22T10:58:00.333321Z"}},"outputs":[],"execution_count":null},{"id":"b3be46b6-cfc4-433f-81e4-5687c98dbbac","cell_type":"markdown","source":"## Create the Kaggle submission\n\nDecompress the 300,000 Kaggle test images and predict on all of them. Kaggle only scores the 10,000 images that belong to the official CIFAR-10 test set, so the leaderboard score matches the estimate above.","metadata":{}},{"id":"19cfb3eb-e283-4197-bb09-1ae6ab686f42","cell_type":"code","source":"!pip -q install py7zr\nimport py7zr, glob\n\nmatches = glob.glob('/kaggle/input/**/test.7z', recursive=True)\nassert matches, 'test.7z not found — add the CIFAR-10 competition data via Add Input -> Competitions -> CIFAR-10'\nTEST_7Z = matches[0]\nprint('Using archive:', TEST_7Z)\n\nwith py7zr.SevenZipFile(TEST_7Z, mode='r') as z:\n    z.extractall(path='.')\n\ntest_files = glob.glob('test/*.png')\nprint('PNG files found:', len(test_files))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T11:35:09.17047Z","iopub.execute_input":"2026-09-22T11:35:09.171298Z","iopub.status.idle":"2026-09-22T11:38:48.066346Z","shell.execute_reply.started":"2026-09-22T11:35:09.171269Z","shell.execute_reply":"2026-09-22T11:38:48.065453Z"}},"outputs":[],"execution_count":null},{"id":"bdaa72c6-8152-4fd3-aef7-c6167afa1d70","cell_type":"code","source":"import pandas as pd\n\nclass KaggleTestSet(Dataset):\n    def __init__(self, files, transform):\n        self.files = files\n        self.transform = transform\n        self.ids = [int(os.path.splitext(os.path.basename(f))[0]) for f in files]\n\n    def __len__(self):\n        return len(self.files)\n\n    def __getitem__(self, i):\n        img = Image.open(self.files[i]).convert('RGB')\n        return self.transform(img), self.ids[i]\n\nsub_set = KaggleTestSet(test_files, test_tf)\nsub_loader = DataLoader(sub_set, batch_size=512, shuffle=False, num_workers=2, pin_memory=True)\n\nmodel.eval()\nids_all = []\npreds_all = []\nwith torch.no_grad():\n    for x, ids in sub_loader:\n        x = x.to(device, non_blocking=True)\n        with torch.cuda.amp.autocast():\n            logits = model(x) + model(torch.flip(x, dims=[3]))\n        preds_all.append(logits.argmax(1).cpu().numpy())\n        ids_all.append(ids.numpy())\n\nids_all = np.concatenate(ids_all)\npreds_all = np.concatenate(preds_all)\nlabels = [classes[p] for p in preds_all]\n\nsubmission = pd.DataFrame({'id': ids_all, 'label': labels}).sort_values('id').reset_index(drop=True)\nsubmission.to_csv('submission.csv', index=False)\nprint(submission.head())\nprint('Rows:', len(submission))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T11:41:41.584264Z","iopub.execute_input":"2026-09-22T11:41:41.585027Z","iopub.status.idle":"2026-09-22T11:43:04.556926Z","shell.execute_reply.started":"2026-09-22T11:41:41.584995Z","shell.execute_reply":"2026-09-22T11:43:04.556051Z"}},"outputs":[],"execution_count":null}]}