{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"},{"sourceId":13171327,"sourceType":"datasetVersion","datasetId":8346409}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-25T16:16:35.645920Z","iopub.execute_input":"2025-09-25T16:16:35.646197Z","iopub.status.idle":"2025-09-25T16:16:35.653753Z","shell.execute_reply.started":"2025-09-25T16:16:35.646175Z","shell.execute_reply":"2025-09-25T16:16:35.653151Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!apt install libarchive-dev\n!pip install libarchive","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T16:15:49.632615Z","iopub.execute_input":"2025-09-25T16:15:49.632881Z","iopub.status.idle":"2025-09-25T16:15:54.969199Z","shell.execute_reply.started":"2025-09-25T16:15:49.632849Z","shell.execute_reply":"2025-09-25T16:15:54.968437Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob\nfrom libarchive.public import file_reader\n\nSRC = \"/kaggle/input/cifar-10\"\nWORK = \"/kaggle/working\"\nTRAIN_OUT = f\"{WORK}/train\"\nTEST_OUT  = f\"{WORK}/test\"\nos.makedirs(TRAIN_OUT, exist_ok=True)\nos.makedirs(TEST_OUT, exist_ok=True)\n\ndef extract_7z(archive_path: str, out_dir: str, skip_if_present: bool = True):\n    \"\"\"Extracts a .7z archive using libarchive into out_dir, with progress every 1000 files.\"\"\"\n    # Skip if already extracted (common in Kaggle to avoid rework on reruns)\n    if skip_if_present and any(p.name.endswith(\".png\") for p in os.scandir(out_dir)):\n        print(f\"✔ {out_dir} already contains files. Skipping: {os.path.basename(archive_path)}\")\n        return\n\n    cnt = 0\n    print(f\"Extracting {archive_path} -> {out_dir}\")\n    with file_reader(archive_path) as entries:\n        for entry in entries:\n            # Some entries can be directories; handle both cases\n            rel = entry.pathname\n            # In these archives, files are flat (e.g., '1.png'), but we normalize anyway\n            out_path = os.path.join(out_dir, os.path.basename(rel.rstrip(\"/\")))\n            if rel.endswith(\"/\"):\n                os.makedirs(out_path, exist_ok=True)\n                continue\n            os.makedirs(os.path.dirname(out_path), exist_ok=True)\n            with open(out_path, \"wb\") as f:\n                for block in entry.get_blocks():\n                    f.write(block)\n            cnt += 1\n            if cnt % 1000 == 0:\n                print(f\"{cnt} files...\")\n\n    print(f\"✔ Done: extracted {cnt} files to {out_dir}\")\n\n# Run extraction for both archives based on your listing\nextract_7z(f\"{SRC}/train.7z\", TRAIN_OUT)\nextract_7z(f\"{SRC}/test.7z\",  TEST_OUT)\n\n# Quick sanity counts\ntrain_count = len(glob.glob(f\"{TRAIN_OUT}/*.png\"))\ntest_count  = len(glob.glob(f\"{TEST_OUT}/*.png\"))\nprint(f\"Counts -> train: {train_count} | test: {test_count}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T16:16:54.636437Z","iopub.execute_input":"2025-09-25T16:16:54.637163Z","iopub.status.idle":"2025-09-25T16:16:55.230589Z","shell.execute_reply.started":"2025-09-25T16:16:54.637134Z","shell.execute_reply":"2025-09-25T16:16:55.229834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== setup ====\nimport os, math, random, glob, time, gc, numpy as np, pandas as pd\nfrom PIL import Image\n\nimport torch, torch.nn as nn, torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED)\ntorch.manual_seed(SEED); torch.cuda.manual_seed_all(SEED)\ntorch.backends.cudnn.benchmark = True\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Device:\", DEVICE)\n\nSRC = \"/kaggle/input/cifar-10\"\nWORK = \"/kaggle/working\"\nTRAIN_DIR = f\"{WORK}/train\"\nTEST_DIR  = f\"{WORK}/test\"\nassert os.path.exists(TRAIN_DIR) and os.path.exists(TEST_DIR), \"Extract train/test to /kaggle/working/* first.\"\n\n# Canonical CIFAR-10 stats\nMEAN = (0.4914, 0.4822, 0.4465)\nSTD  = (0.2023, 0.1994, 0.2010)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T15:18:29.352562Z","iopub.execute_input":"2025-09-25T15:18:29.353077Z","iopub.status.idle":"2025-09-25T15:18:36.732563Z","shell.execute_reply.started":"2025-09-25T15:18:29.353052Z","shell.execute_reply":"2025-09-25T15:18:36.731928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== labels/split ====\nfrom sklearn.model_selection import StratifiedShuffleSplit\n\ntrain_df = pd.read_csv(f\"{SRC}/trainLabels.csv\")  # columns: id,label\ntrain_df[\"file\"] = train_df[\"id\"].astype(str) + \".png\"\n\nCLASSES = sorted(train_df[\"label\"].unique().tolist())\ncls2idx = {c:i for i,c in enumerate(CLASSES)}\nidx2cls = {i:c for c,i in cls2idx.items()}\nnum_classes = len(CLASSES)\nprint(\"Classes:\", CLASSES)\n\nsss = StratifiedShuffleSplit(n_splits=1, test_size=5000, random_state=SEED)\ntr_idx, va_idx = next(sss.split(train_df[\"file\"], train_df[\"label\"]))\ntr_df = train_df.iloc[tr_idx].reset_index(drop=True)\nva_df = train_df.iloc[va_idx].reset_index(drop=True)\nlen(tr_df), len(va_df)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T15:18:45.177109Z","iopub.execute_input":"2025-09-25T15:18:45.177817Z","iopub.status.idle":"2025-09-25T15:18:45.797133Z","shell.execute_reply.started":"2025-09-25T15:18:45.177792Z","shell.execute_reply":"2025-09-25T15:18:45.796544Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== dataset & loaders ====\ntrain_tfms = transforms.Compose([\n    transforms.RandomCrop(32, padding=4, padding_mode=\"reflect\"),\n    transforms.RandomHorizontalFlip(),\n    transforms.AutoAugment(transforms.AutoAugmentPolicy.CIFAR10),  # strong & reliable\n    transforms.ToTensor(),\n    transforms.Normalize(MEAN, STD),\n    transforms.RandomErasing(p=0.1, scale=(0.02, 0.2), value=0)\n])\nvalid_tfms = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(MEAN, STD)\n])\n\nclass CIFAR10Files(Dataset):\n    def __init__(self, root_dir, df, transform):\n        self.root = root_dir\n        self.df = df.reset_index(drop=True)\n        self.t = transform\n        self.has_labels = \"label\" in df.columns\n    def __len__(self): return len(self.df)\n    def __getitem__(self, i):\n        r = self.df.iloc[i]\n        img = Image.open(f\"{self.root}/{r['file']}\").convert(\"RGB\")\n        img = self.t(img) if self.t else img\n        if self.has_labels: return img, cls2idx[r[\"label\"]]\n        return img, r[\"file\"]\n\nBATCH_SIZE = 256   # drop to 128 if you OOM\nNUM_WORKERS = 2    # Kaggle default-safe\n\ntrain_loader = DataLoader(CIFAR10Files(TRAIN_DIR, tr_df, train_tfms),\n                          batch_size=BATCH_SIZE, shuffle=True, drop_last=True,\n                          num_workers=NUM_WORKERS, pin_memory=True)\nvalid_loader = DataLoader(CIFAR10Files(TRAIN_DIR, va_df, valid_tfms),\n                          batch_size=BATCH_SIZE, shuffle=False,\n                          num_workers=NUM_WORKERS, pin_memory=True)\nlen(train_loader), len(valid_loader)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T15:18:51.540625Z","iopub.execute_input":"2025-09-25T15:18:51.541372Z","iopub.status.idle":"2025-09-25T15:18:51.553613Z","shell.execute_reply.started":"2025-09-25T15:18:51.541347Z","shell.execute_reply":"2025-09-25T15:18:51.553073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== model ====\nfrom torchvision.models import resnet34\n\ndef make_cifar_resnet34(num_classes=10):\n    m = resnet34(weights=None)                  # <-- NO pretrained weights (assignment compliant)\n    m.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)  # 3x3, stride 1\n    m.maxpool = nn.Identity()                   # no 7x7/stride-2/maxpool for 32x32\n    m.fc = nn.Linear(512, num_classes)\n    return m\n\nmodel = make_cifar_resnet34(num_classes).to(DEVICE)\nprint(\"Params (M):\", round(sum(p.numel() for p in model.parameters())/1e6, 2))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T15:19:21.033190Z","iopub.execute_input":"2025-09-25T15:19:21.033472Z","iopub.status.idle":"2025-09-25T15:19:21.586381Z","shell.execute_reply.started":"2025-09-25T15:19:21.033454Z","shell.execute_reply":"2025-09-25T15:19:21.585682Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== training setup ====\nfrom contextlib import nullcontext\n\n# MixUp/CutMix helpers\ndef rand_bbox(size, lam):\n    W, H = size[2], size[3]\n    cut_rat = math.sqrt(1. - lam)\n    cut_w, cut_h = int(W*cut_rat), int(H*cut_rat)\n    cx, cy = np.random.randint(W), np.random.randint(H)\n    x1, y1 = np.clip(cx - cut_w//2, 0, W), np.clip(cy - cut_h//2, 0, H)\n    x2, y2 = np.clip(cx + cut_w//2, 0, W), np.clip(cy + cut_h//2, 0, H)\n    return x1, y1, x2, y2\n\ndef mix_criterion(criterion, pred, y_a, y_b, lam):\n    return lam*criterion(pred, y_a) + (1-lam)*criterion(pred, y_b)\n\n# Float-only EMA (avoids dtype crashes)\nclass EMA:\n    def __init__(self, model, decay=0.999):\n        self.decay = decay\n        self.shadow = {k: v.detach().clone() for k,v in model.state_dict().items()}\n        self.backup = None\n    def update(self, model):\n        with torch.no_grad():\n            for k,v in model.state_dict().items():\n                if v.is_floating_point():\n                    self.shadow[k].mul_(self.decay).add_(v.detach(), alpha=1.0-self.decay)\n                else:\n                    self.shadow[k] = v.detach().clone()\n    def apply_to(self, model):\n        self.backup = {k: v.detach().clone() for k,v in model.state_dict().items()}\n        model.load_state_dict(self.shadow, strict=True)\n    def restore(self, model):\n        model.load_state_dict(self.backup, strict=True); self.backup=None\n\ndef accuracy(logits, y): return (logits.argmax(1) == y).float().mean().item()\n\n# Training knobs\nEPOCHS = 80\nBASE_LR = 0.2\nWEIGHT_DECAY = 5e-4\nLABEL_SMOOTH = 0.1\nMIXUP_ALPHA = 0.2\nCUTMIX_ALPHA = 1.0\nMIX_PROB = 0.7\n\noptimizer = torch.optim.SGD(model.parameters(), lr=BASE_LR, momentum=0.9,\n                            nesterov=True, weight_decay=WEIGHT_DECAY)\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\ncriterion = nn.CrossEntropyLoss(label_smoothing=LABEL_SMOOTH)\n\n# New AMP API (no deprecation warnings)\nif DEVICE.type == \"cuda\":\n    scaler = torch.amp.GradScaler(\"cuda\")\n    def autocast_ctx(): return torch.amp.autocast(device_type=\"cuda\")\nelse:\n    scaler = torch.amp.GradScaler(enabled=False)\n    def autocast_ctx(): return nullcontext()\n\nema = EMA(model, decay=0.999)\nbest_acc = 0.0\nBEST_PATH = f\"{WORK}/best_resnet34_cifar.pth\"\nCKPT_DIR = f\"{WORK}/ckpts\"; os.makedirs(CKPT_DIR, exist_ok=True)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T15:19:27.407044Z","iopub.execute_input":"2025-09-25T15:19:27.407345Z","iopub.status.idle":"2025-09-25T15:19:27.426209Z","shell.execute_reply.started":"2025-09-25T15:19:27.407323Z","shell.execute_reply":"2025-09-25T15:19:27.425478Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== train ====\nfor epoch in range(1, EPOCHS+1):\n    model.train()\n    tr_loss, tr_correct, seen = 0.0, 0, 0\n\n    for xb, yb in train_loader:\n        xb, yb = xb.to(DEVICE, non_blocking=True), yb.to(DEVICE, non_blocking=True)\n        optimizer.zero_grad(set_to_none=True)\n\n        use_mix = (np.random.rand() < MIX_PROB)\n        if use_mix:\n            r = np.random.rand()\n            if r < 0.5 and MIXUP_ALPHA > 0:\n                lam = np.random.beta(MIXUP_ALPHA, MIXUP_ALPHA)\n                idx = torch.randperm(xb.size(0), device=xb.device)\n                xb2 = lam*xb + (1-lam)*xb[idx]\n                y_a, y_b = yb, yb[idx]\n                with autocast_ctx():\n                    logits = model(xb2); loss = mix_criterion(criterion, logits, y_a, y_b, lam)\n            else:\n                lam = np.random.beta(CUTMIX_ALPHA, CUTMIX_ALPHA)\n                idx = torch.randperm(xb.size(0), device=xb.device)\n                y_a, y_b = yb, yb[idx]\n                x1,y1,x2,y2 = rand_bbox(xb.size(), lam)\n                xbm = xb.clone(); xbm[:,:,y1:y2,x1:x2] = xb[idx,:,y1:y2,x1:x2]\n                lam = 1 - ((x2-x1)*(y2-y1)/(xb.size(-1)*xb.size(-2)))\n                with autocast_ctx():\n                    logits = model(xbm); loss = mix_criterion(criterion, logits, y_a, y_b, lam)\n        else:\n            with autocast_ctx():\n                logits = model(xb); loss = criterion(logits, yb)\n\n        scaler.scale(loss).backward(); scaler.step(optimizer); scaler.update()\n        ema.update(model)\n\n        tr_loss += loss.item()*xb.size(0)\n        seen += xb.size(0)\n        if not use_mix:\n            tr_correct += (logits.argmax(1) == yb).float().sum().item()\n\n    scheduler.step()\n    tr_loss /= seen; tr_acc = tr_correct / max(seen,1)\n\n    # --- validation (EMA weights) ---\n    model.eval(); ema.apply_to(model)\n    va_loss, va_correct, va_seen = 0.0, 0, 0\n    with torch.no_grad():\n        for xb, yb in valid_loader:\n            xb, yb = xb.to(DEVICE), yb.to(DEVICE)\n            with autocast_ctx():\n                logits = model(xb); loss = criterion(logits, yb)\n            va_loss += loss.item()*xb.size(0)\n            va_correct += (logits.argmax(1)==yb).float().sum().item()\n            va_seen += xb.size(0)\n    ema.restore(model)\n\n    va_loss /= va_seen; va_acc = va_correct / va_seen\n\n    # Save best + rolling ckpts to avoid losing progress\n    if va_acc > best_acc:\n        best_acc = va_acc\n        torch.save({\"model\": model.state_dict(), \"ema\": ema.shadow, \"acc\": best_acc, \"epoch\": epoch}, BEST_PATH)\n    if epoch % 5 == 0:\n        torch.save({\"model\": model.state_dict(), \"ema\": ema.shadow, \"acc\": va_acc, \"epoch\": epoch},\n                   f\"{CKPT_DIR}/ep{epoch:03d}.pth\")\n\n    if epoch % 5 == 0 or epoch == 1:\n        print(f\"Epoch {epoch:02d}/{EPOCHS} | train {tr_loss:.4f} | valid {va_loss:.4f} acc {va_acc:.4f} | best {best_acc:.4f}\")\n\nprint(\"Best val acc:\", best_acc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T15:19:34.859893Z","iopub.execute_input":"2025-09-25T15:19:34.860575Z","iopub.status.idle":"2025-09-25T16:04:31.037797Z","shell.execute_reply.started":"2025-09-25T15:19:34.860549Z","shell.execute_reply":"2025-09-25T16:04:31.036513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==== inference & submission ====\nimport os, pandas as pd, torch, torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision.models import resnet34\nfrom torchvision import transforms\nfrom PIL import Image\n\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nWORK = \"/kaggle/working\"\nSRC  = \"/kaggle/input/cifar-10\"\nTEST_DIR = f\"{WORK}/test\"\nBEST_PATH = f\"{WORK}/best_resnet34_cifar.pth\"\n\n# Rebuild label map (needed for submission)\ntrain_df = pd.read_csv(f\"{SRC}/trainLabels.csv\")\nCLASSES  = sorted(train_df[\"label\"].unique().tolist())\nidx2cls  = {i:c for i,c in enumerate(CLASSES)}\n\nMEAN = (0.4914, 0.4822, 0.4465)\nSTD  = (0.2023, 0.1994, 0.2010)\nvalid_tfms = transforms.Compose([transforms.ToTensor(), transforms.Normalize(MEAN, STD)])\nhflip = transforms.RandomHorizontalFlip(p=1.0)\n\ndef make_cifar_resnet34(num_classes=10):\n    m = resnet34(weights=None)  # no pretrained (assignment-compliant)\n    m.conv1 = nn.Conv2d(3,64,3,1,1,bias=False)\n    m.maxpool = nn.Identity()\n    m.fc = nn.Linear(512, num_classes)\n    return m\n\ndef _strip_module(sd):\n    return { (k.replace(\"module.\",\"\",1) if k.startswith(\"module.\") else k): v for k,v in sd.items() }\n\nclass CIFAR10Test(Dataset):\n    def __init__(self, root):\n        self.root = root\n        self.files = sorted([p.name for p in os.scandir(root) if p.name.endswith(\".png\")],\n                            key=lambda s: int(s.split(\".\")[0]))\n    def __len__(self): return len(self.files)\n    def __getitem__(self, i):\n        f = self.files[i]\n        img = Image.open(f\"{self.root}/{f}\").convert(\"RGB\")\n        return img, f  # return PIL; we'll collate manually\n\n# Build model and load best (EMA applied for stable preds)\nm = make_cifar_resnet34(len(CLASSES)).to(DEVICE)\nckpt = torch.load(BEST_PATH, map_location=\"cpu\")\nm.load_state_dict(_strip_module(ckpt[\"model\"]), strict=True)\nema_shadow = _strip_module(ckpt[\"ema\"])\nbackup = {k: v.detach().clone() for k,v in m.state_dict().items()}\nm.load_state_dict(ema_shadow, strict=True)\n\n# Important: custom collate_fn so PIL images aren't auto-collated\ntest_loader = DataLoader(\n    CIFAR10Test(TEST_DIR),\n    batch_size=512, shuffle=False, num_workers=2, pin_memory=True,\n    collate_fn=lambda batch: batch  # <-- returns list of (PIL, name)\n)\n\nids, preds = [], []\nm.eval()\nwith torch.no_grad():\n    for batch in test_loader:\n        imgs, names = zip(*batch)  # tuples of PIL images and filenames\n        xb  = torch.stack([valid_tfms(im) for im in imgs]).to(DEVICE, non_blocking=True)\n        xbF = torch.stack([valid_tfms(hflip(im)) for im in imgs]).to(DEVICE, non_blocking=True)\n        logits = m(xb) + m(xbF)\n        p = logits.argmax(1).tolist()\n        ids.extend([n.split(\".\")[0] for n in names])\n        preds.extend([idx2cls[i] for i in p])\n\n# restore original weights (optional)\nm.load_state_dict(backup, strict=True)\n\nsub = pd.DataFrame({\"id\": ids, \"label\": preds})\nsub.to_csv(f\"{WORK}/submission.csv\", index=False)\nprint(\"✔ submission.csv\", sub.shape)\nprint(sub.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-25T16:17:31.298705Z","iopub.execute_input":"2025-09-25T16:17:31.298967Z","iopub.status.idle":"2025-09-25T16:24:08.782098Z","shell.execute_reply.started":"2025-09-25T16:17:31.298949Z","shell.execute_reply":"2025-09-25T16:24:08.781292Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}