{"nbformat":4,"nbformat_minor":5,"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","pygments_lexer":"ipython3"}},"cells":[{"cell_type":"markdown","metadata":{},"source":"# CIFAR-10 Assignment 4 — Kaggle test inference\n\nLoads the **randomly initialized, from-scratch** ResNet-18 weights produced by\n[assignment-4-cifar-10-resnet-18](https://www.kaggle.com/code/alhasanmahmood/assignment-4-cifar-10-resnet-18)\nand writes `submission.csv` for the 300,000 Kaggle test images.\n\nThis notebook does **not** load ImageNet or any other pretrained checkpoint.\n"},{"cell_type":"code","metadata":{},"outputs":[],"execution_count":null,"source":"import glob, os, shutil, subprocess, time\nfrom concurrent.futures import ThreadPoolExecutor\nfrom pathlib import Path\n\nimport cv2\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\n\nassert torch.cuda.is_available()\nDEVICE = torch.device(\"cuda\")\nprint(\"GPU:\", torch.cuda.get_device_name(0))\n\nCLASSES = [\"airplane\",\"automobile\",\"bird\",\"cat\",\"deer\",\"dog\",\"frog\",\"horse\",\"ship\",\"truck\"]\nCIFAR_MEAN = (0.4914, 0.4822, 0.4465)\nCIFAR_STD = (0.2023, 0.1994, 0.2010)\n\n\nclass 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=stride, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(planes)\n        self.conv2 = nn.Conv2d(planes, planes, 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:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(in_planes, planes, 1, stride=stride, bias=False),\n                nn.BatchNorm2d(planes),\n            )\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 ResNet18CIFAR(nn.Module):\n    def __init__(self, num_classes=10):\n        super().__init__()\n        self.in_planes = 64\n        self.conv1 = nn.Conv2d(3, 64, 3, stride=1, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(64)\n        self.layer1 = self._make_layer(64, 2, 1)\n        self.layer2 = self._make_layer(128, 2, 2)\n        self.layer3 = self._make_layer(256, 2, 2)\n        self.layer4 = self._make_layer(512, 2, 2)\n        self.fc = nn.Linear(512, num_classes)\n\n    def _make_layer(self, planes, num_blocks, stride):\n        strides = [stride] + [1] * (num_blocks - 1)\n        blocks = []\n        for s in strides:\n            blocks.append(BasicBlock(self.in_planes, planes, s))\n            self.in_planes = planes\n        return nn.Sequential(*blocks)\n\n    def forward(self, x):\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.layer1(out); out = self.layer2(out)\n        out = self.layer3(out); out = self.layer4(out)\n        out = F.avg_pool2d(out, 4)\n        return self.fc(torch.flatten(out, 1))\n\n\nweight_hits = list(Path(\"/kaggle/input\").rglob(\"resnet18_cifar.pt\"))\nprint(\"weight files:\", weight_hits)\nassert weight_hits, \"Attach the output of assignment-4-cifar-10-resnet-18\"\nmodel = ResNet18CIFAR().to(DEVICE)\nmodel.load_state_dict(torch.load(weight_hits[0], map_location=DEVICE))\nmodel.eval()\nprint(\"Loaded\", weight_hits[0], \"PRETRAINED_WEIGHTS_USED = False (these are our trained-from-scratch weights)\")\n"},{"cell_type":"code","metadata":{},"outputs":[],"execution_count":null,"source":"found = list(Path(\"/kaggle/input\").rglob(\"test.7z\"))\nassert found, \"Attach the cifar-10 competition dataset (accept the rules first).\"\narchive = found[0]\nEXTRACT_ROOT = Path(\"/tmp/cifar10_kaggle\")\nEXTRACT_ROOT.mkdir(parents=True, exist_ok=True)\nif shutil.which(\"7z\") is None:\n    subprocess.run([\"apt-get\",\"update\",\"-qq\"], check=False)\n    subprocess.run([\"apt-get\",\"install\",\"-y\",\"-qq\",\"p7zip-full\"], check=False)\nprint(\"Extracting\", archive)\nt0 = time.perf_counter()\nsubprocess.run([\"7z\",\"x\",str(archive),f\"-o{EXTRACT_ROOT}\",\"-y\"], check=True)\nprint(\"extract s\", time.perf_counter()-t0)\n\ndef find_test_dir():\n    for png in EXTRACT_ROOT.rglob(\"1.png\"):\n        return png.parent\n    raise FileNotFoundError(\"test/1.png\")\n\nTEST_DIR = find_test_dir()\nn_files = len(glob.glob(str(TEST_DIR/\"*\")))\nprint(\"Test dir\", TEST_DIR, \"n_files\", n_files)\nassert n_files >= 300000, n_files\n\nMEAN = torch.tensor(CIFAR_MEAN, device=DEVICE).view(1,3,1,1)\nSTD = torch.tensor(CIFAR_STD, device=DEVICE).view(1,3,1,1)\n\ndef load_one(i):\n    p = str(TEST_DIR/f\"{i}.png\")\n    img = cv2.imread(p, cv2.IMREAD_COLOR)\n    if img is None:\n        return np.asarray(Image.open(p).convert(\"RGB\"))\n    return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n\n@torch.no_grad()\ndef predict_tta(x):\n    logits = model(x)\n    logits_flip = model(torch.flip(x, dims=[3]))\n    return ((logits + logits_flip)/2).argmax(1).cpu().numpy()\n\nN, B = 300000, 512\npreds = np.empty(N, dtype=np.int64)\nt1 = time.perf_counter()\nfor start in range(1, N+1, B):\n    end = min(start+B, N+1)\n    ids = list(range(start, end))\n    with ThreadPoolExecutor(8) as pool:\n        arr = np.stack(list(pool.map(load_one, ids)))\n    x = torch.from_numpy(arr).to(DEVICE).permute(0,3,1,2).float()/255.0\n    x = (x - MEAN)/STD\n    preds[start-1:end-1] = predict_tta(x)\n    if start == 1 or (end-1)%20000==0 or end-1==N:\n        print(\"predicted\", end-1, \"/\", N, flush=True)\nprint(\"infer s\", time.perf_counter()-t1)\nsub = pd.DataFrame({\"id\": np.arange(1,N+1), \"label\": [CLASSES[int(i)] for i in preds]})\nsub.to_csv(\"/kaggle/working/submission.csv\", index=False)\nprint(sub.head())\nprint(sub[\"label\"].value_counts())\nprint(\"Wrote submission.csv\")\n"}]}