{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# Basics\nimport os, random, glob, csv\nfrom pathlib import Path\nimport numpy as np\nfrom PIL import Image\n\n# Torch / TorchVision\nimport torch, torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset, random_split\nfrom torchvision import datasets, transforms, models\n\n# Reproducibility\nseed = 42\nrandom.seed(seed); np.random.seed(seed); torch.manual_seed(seed)\nif torch.cuda.is_available():\n    torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.benchmark = True  =\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", device)\n\n# CIFAR-10 normalization\nC10_MEAN = (0.4914, 0.4822, 0.4465)\nC10_STD  = (0.2470, 0.2435, 0.2616)\n\n# Class order (use for submission mapping)\nIDX_TO_CLASS = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']\nprint(\"Class order:\", IDX_TO_CLASS)\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-20T10:23:23.293903Z","iopub.execute_input":"2025-09-20T10:23:23.294080Z","iopub.status.idle":"2025-09-20T10:23:30.966167Z","shell.execute_reply.started":"2025-09-20T10:23:23.294044Z","shell.execute_reply":"2025-09-20T10:23:30.965483Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 1: Train/Val data, this is 45k/5k split\n\n# Transforms\ntrain_tf = transforms.Compose([\n    transforms.RandomCrop(32, padding=4),\n    transforms.RandomHorizontalFlip(),\n    transforms.ColorJitter(0.2, 0.2, 0.2, 0.05),     # mild color jitter\n    transforms.ToTensor(),\n    transforms.Normalize(C10_MEAN, C10_STD),\n    transforms.RandomErasing(p=0.25, scale=(0.02, 0.15), ratio=(0.3, 3.3)),\n])\neval_tf = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(C10_MEAN, C10_STD),\n])\n\n# Load official CIFAR-10 training set (50k)\nroot = \"./data\"\nfull_train = datasets.CIFAR10(root=root, train=True, download=True, transform=train_tf)\n\n# Split: 45k train / 5k val \nval_size = 5000\ntrain_size = len(full_train) - val_size\ng = torch.Generator().manual_seed(seed)\ntrain_set, val_set = random_split(full_train, [train_size, val_size], generator=g)\nval_set.dataset.transform = eval_tf   # no augs on val\n\n# Dataloaders\nBATCH_SIZE = 256    \nNUM_WORKERS = 2\ntrain_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True,\n                          num_workers=NUM_WORKERS, pin_memory=True, drop_last=True)\nval_loader   = DataLoader(val_set,   batch_size=BATCH_SIZE, shuffle=False,\n                          num_workers=NUM_WORKERS, pin_memory=True)\n\n# Quick sanity check\nxb, yb = next(iter(train_loader))\nprint(\"Train batch:\", xb.shape, yb.shape)\nprint(\"Train/Val sizes:\", len(train_set), len(val_set))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T10:23:42.468883Z","iopub.execute_input":"2025-09-20T10:23:42.469185Z","iopub.status.idle":"2025-09-20T10:23:50.741495Z","shell.execute_reply.started":"2025-09-20T10:23:42.469162Z","shell.execute_reply":"2025-09-20T10:23:50.740639Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 2: Model (random init) with CIFAR stem\n# ResNet-18 RANDOM init, adapted stem for 32x32 3x3 stride1, no maxpool\nmodel = models.resnet18(weights=None, num_classes=10) ## no pre-trained weights were used\nmodel.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)\nmodel.maxpool = nn.Identity()\n\n# Explicit Kaiming init =\ndef init_kaiming(m):\n    if isinstance(m, nn.Conv2d):\n        nn.init.kaiming_normal_(m.weight, nonlinearity='relu')\n        if m.bias is not None: nn.init.zeros_(m.bias)\n    elif isinstance(m, nn.Linear):\n        nn.init.kaiming_normal_(m.weight, nonlinearity='relu')\n        if m.bias is not None: nn.init.zeros_(m.bias)\nmodel.apply(init_kaiming)\n\nmodel = model.to(device)\nprint(\"Model ready.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T10:32:18.562854Z","iopub.execute_input":"2025-09-20T10:32:18.563158Z","iopub.status.idle":"2025-09-20T10:32:18.807422Z","shell.execute_reply.started":"2025-09-20T10:32:18.563136Z","shell.execute_reply":"2025-09-20T10:32:18.806607Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 3: Training config (200 epochs, cosine, warmup, EMA)\n\nEPOCHS = 200\nbase_lr = 0.2 * (BATCH_SIZE / 256)   # LR scales with batch size\noptimizer = torch.optim.SGD(model.parameters(), lr=base_lr, momentum=0.9,\n                            weight_decay=5e-4, nesterov=True)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n\n# Simple EMA of weights (optional but helpful)\nuse_ema = True\nema_decay = 0.999\nif use_ema:\n    ema = {k: v.detach().clone() for k, v in model.state_dict().items()}\n\n@torch.no_grad()\ndef ema_update():\n    if not use_ema: return\n    for k, v in model.state_dict().items():\n        ema[k].mul_(ema_decay).add_(v.detach(), alpha=1-ema_decay)\n\nbest_acc, best_path = 0.0, \"best_resnet18_cifar10.pth\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T10:32:21.562768Z","iopub.execute_input":"2025-09-20T10:32:21.563110Z","iopub.status.idle":"2025-09-20T10:32:21.572987Z","shell.execute_reply.started":"2025-09-20T10:32:21.563087Z","shell.execute_reply":"2025-09-20T10:32:21.572328Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Step 4: Train loop\n\n# ===== Cell 5: Train loop with SAFE EMA =====\nimport torch\n\n# --- EMA settings ---\nuse_ema = True\nema_decay = 0.999\n\n# Initialize EMA snapshot of current weights\nif use_ema:\n    ema = {k: v.detach().clone() for k, v in model.state_dict().items()}\n\n@torch.no_grad()\ndef ema_update():\n    \"\"\"Update EMA weights; only average float tensors, copy others (e.g., Long buffers).\"\"\"\n    if not use_ema:\n        return\n    for k, v in model.state_dict().items():\n        if v.dtype.is_floating_point:\n            ema[k].mul_(ema_decay).add_(v.detach(), alpha=1.0 - ema_decay)\n        else:\n            ema[k] = v.detach().clone()\n\ndef run_epoch(loader, train=True):\n    model.train(train)\n    total, correct, loss_sum = 0, 0, 0.0\n    for x, y in loader:\n        x, y = x.to(device, non_blocking=True), y.to(device, non_blocking=True)\n        if train:\n            optimizer.zero_grad(set_to_none=True)\n        with torch.set_grad_enabled(train):\n            logits = model(x)\n            loss = criterion(logits, y)\n            if train:\n                loss.backward()\n                optimizer.step()\n                ema_update()  # keep EMA synced after each optimizer step\n        loss_sum += loss.item() * y.size(0)\n        correct += (logits.argmax(1) == y).sum().item()\n        total += y.size(0)\n    return loss_sum / total, correct / total\n\n# --- Training loop with 5-epoch warmup + cosine scheduler ---\nbest_acc, best_path = 0.0, \"best_resnet18_cifar10.pth\"\nwarmup_epochs = 5\n\nfor epoch in range(EPOCHS):\n    # linear warmup\n    if epoch < warmup_epochs:\n        for pg in optimizer.param_groups:\n            pg[\"lr\"] = base_lr * (epoch + 1) / warmup_epochs\n\n    tr_loss, tr_acc = run_epoch(train_loader, train=True)\n    va_loss, va_acc = run_epoch(val_loader,   train=False)\n    scheduler.step()\n\n    # save best (main weights)\n    if va_acc > best_acc:\n        best_acc = va_acc\n        torch.save({\"model\": model.state_dict()}, best_path)\n\n    print(f\"Epoch {epoch+1:03d}/{EPOCHS} | \"\n          f\"train {tr_acc:.4f}/{tr_loss:.4f} | \"\n          f\"val {va_acc:.4f}/{va_loss:.4f} | best {best_acc:.4f}\")\n\nprint(\"Best val acc:\", best_acc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T10:35:30.859965Z","iopub.execute_input":"2025-09-20T10:35:30.860832Z","iopub.status.idle":"2025-09-20T11:34:52.753858Z","shell.execute_reply.started":"2025-09-20T10:35:30.860800Z","shell.execute_reply":"2025-09-20T11:34:52.752803Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Offline accuracy on  CIFAR-10 test which is not mandatory\nfrom torchvision import datasets, transforms\nfrom torch.utils.data import DataLoader\n\nC10_MEAN=(0.4914,0.4822,0.4465); C10_STD=(0.2470,0.2435,0.2616)\ntest_tf = transforms.Compose([transforms.ToTensor(), transforms.Normalize(C10_MEAN, C10_STD)])\ntest_official = datasets.CIFAR10(root='./data', train=False, download=True, transform=test_tf)\ntest_loader = DataLoader(test_official, batch_size=BATCH_SIZE, shuffle=False,\n                         num_workers=NUM_WORKERS, pin_memory=True)\n\n# Prefer EMA weights; otherwise use best checkpoint\nif 'ema' in globals():\n    model.load_state_dict(ema, strict=False)\nelse:\n    ckpt = torch.load(best_path, map_location=device)\n    model.load_state_dict(ckpt['model'], strict=True)\n\nmodel.eval()\ntotal = correct = 0\nwith torch.no_grad():\n    for x, y in test_loader:\n        x, y = x.to(device), y.to(device)\n        pred = model(x).argmax(1)\n        correct += (pred == y).sum().item()\n        total   += y.size(0)\n\noffline_acc = correct / total\nprint(\"Official CIFAR-10 test accuracy (offline estimate):\", offline_acc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T11:35:09.859447Z","iopub.execute_input":"2025-09-20T11:35:09.860161Z","iopub.status.idle":"2025-09-20T11:35:12.254419Z","shell.execute_reply.started":"2025-09-20T11:35:09.860126Z","shell.execute_reply":"2025-09-20T11:35:12.253603Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Robust extract + verify + flatten, then recount\n!pip -q install py7zr\nimport os, glob, shutil, py7zr\nfrom pathlib import Path\n\n# 1) Locate the archive\ncands = glob.glob(\"/kaggle/input/**/test.7z\", recursive=True)\nprint(\"Candidates:\", cands)\nassert cands, \"Couldn't find test.7z. Make sure the CIFAR-10 competition is attached as an input.\"\ntest7z = cands[0]\nprint(\"Using:\", test7z, \"| size (bytes):\", os.path.getsize(test7z))\n\n# 2) Clean output dir\noutdir = \"test\"\nif os.path.exists(outdir):\n    shutil.rmtree(outdir)\nos.makedirs(outdir, exist_ok=True)\n\n# 3) Peek inside the archive, then extract\nwith py7zr.SevenZipFile(test7z, mode=\"r\") as z:\n    names = z.getnames()\n    print(\"First 10 archive entries:\", names[:10])\n    z.extractall(path=outdir)\n\n# 4) Count recursively (handles nested folders inside the archive)\npngs_recursive = glob.glob(os.path.join(outdir, \"**\", \"*.png\"), recursive=True)\nprint(\"PNG files found (recursive):\", len(pngs_recursive))\n\n# 5) If files are nested (e.g., test/test/1.png), flatten into ./test\nmoved = 0\nfor p in pngs_recursive:\n    dest = os.path.join(outdir, os.path.basename(p))\n    if os.path.abspath(p) != os.path.abspath(dest):\n        if not os.path.exists(dest):\n            shutil.move(p, dest)\n            moved += 1\nprint(\"Moved from nested dirs:\", moved)\n\n# 6) Remove empty subfolders under ./test (optional)\nfor root, dirs, files in os.walk(outdir, topdown=False):\n    if root == outdir: \n        continue\n    if not os.listdir(root):\n        os.rmdir(root)\n\n# 7) Final count should be ~300000\nfinal_count = len(glob.glob(os.path.join(outdir, \"*.png\")))\nprint(\"Final PNG count in ./test:\", final_count)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T11:45:14.541162Z","iopub.execute_input":"2025-09-20T11:45:14.541995Z","iopub.status.idle":"2025-09-20T11:47:46.660117Z","shell.execute_reply.started":"2025-09-20T11:45:14.541966Z","shell.execute_reply":"2025-09-20T11:47:46.659322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Run inference on 300k test images and make submssion file\nimport csv, glob\nfrom pathlib import Path\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\n# Load EMA (if present) or best checkpoint; set to eval\nif 'ema' in globals():\n    model.load_state_dict(ema, strict=False)\nelse:\n    ckpt = torch.load(best_path, map_location=device)\n    model.load_state_dict(ckpt['model'], strict=True)\nmodel.eval()\n\n# Same normalization as training/eval\ntest_tf_final = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(C10_MEAN, C10_STD),\n])\n\n# Sorted by numeric filename so ids align (1.png, 2.png, ...)\ntest_files = sorted(glob.glob('test/*.png'), key=lambda p: int(Path(p).stem))\n\nclass TestFolder(Dataset):\n    def __init__(self, files, tfm):\n        self.files = files; self.tfm = tfm\n    def __len__(self): return len(self.files)\n    def __getitem__(self, i):\n        fp = self.files[i]\n        img = Image.open(fp).convert('RGB')\n        return self.tfm(img), int(Path(fp).stem)\n\ntest_loader_big = DataLoader(TestFolder(test_files, test_tf_final),\n                             batch_size=BATCH_SIZE, shuffle=False,\n                             num_workers=NUM_WORKERS, pin_memory=True)\n\nIDX_TO_CLASS = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']\n\nrows = []\nwith torch.no_grad():\n    for x, ids in test_loader_big:\n        x = x.to(device)\n        pred = model(x).argmax(1).cpu().tolist()\n        for i, p in zip(ids.tolist(), pred):\n            rows.append((i, IDX_TO_CLASS[p]))\n\n# Write CSV with exact header id,label\nrows.sort(key=lambda t: t[0])\nwith open('submission.csv', 'w', newline='') as f:\n    w = csv.writer(f)\n    w.writerow(['id','label'])\n    w.writerows(rows)\n\nprint(\"Wrote submission.csv with\", len(rows), \"rows.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T11:48:40.208325Z","iopub.execute_input":"2025-09-20T11:48:40.208995Z","iopub.status.idle":"2025-09-20T11:49:52.573861Z","shell.execute_reply.started":"2025-09-20T11:48:40.208969Z","shell.execute_reply":"2025-09-20T11:49:52.573038Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\ndf = pd.read_csv(\"submission.csv\")\nprint(\"shape:\", df.shape)                        # (300000, 2)\nprint(\"columns:\", list(df.columns))              # ['id','label']\nprint(\"id range:\", df['id'].min(), df['id'].max())\nprint(\"unique labels:\", sorted(df['label'].unique()))\nassert set(df['label'].unique()) <= set(['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck'])\nassert df['id'].is_monotonic_increasing\nassert not df.isna().any().any()\nprint(\"✅ CSV looks good.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-20T11:50:36.783535Z","iopub.execute_input":"2025-09-20T11:50:36.783853Z","iopub.status.idle":"2025-09-20T11:50:37.200715Z","shell.execute_reply.started":"2025-09-20T11:50:36.783821Z","shell.execute_reply":"2025-09-20T11:50:37.199987Z"}},"outputs":[],"execution_count":null}]}