{"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":"gpu","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"}],"dockerImageVersionId":31153,"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-10-11T10:10:28.116741Z","iopub.execute_input":"2025-10-11T10:10:28.117029Z","iopub.status.idle":"2025-10-11T10:10:28.125329Z","shell.execute_reply.started":"2025-10-11T10:10:28.117005Z","shell.execute_reply":"2025-10-11T10:10:28.124567Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================\n# Ensure CIFAR-10 data is extracted with py7zr (idempotent)\n# Target layout after this cell:\n#   /kaggle/working/cifar-10/\n#       ├─ train/  (50k PNGs)\n#       ├─ test/   (~300k PNGs)\n#       ├─ trainLabels.csv\n#       └─ sampleSubmission.csv\n# ==============================================================\n\n# install/import py7zr\ntry:\n    import py7zr  # noqa\nexcept Exception:\n    !pip -q install py7zr\n    import py7zr\n\nimport os, shutil, time\nfrom pathlib import Path\n\nDEST_ROOT = Path(\"/kaggle/working/cifar-10\")\nTRAIN_DIR = DEST_ROOT / \"train\"\nTEST_DIR  = DEST_ROOT / \"test\"\nDEST_ROOT.mkdir(parents=True, exist_ok=True)\n\ndef count_pngs(d: Path):\n    if not d.exists(): return 0\n    return sum(1 for f in os.scandir(d) if f.is_file() and f.name.lower().endswith(\".png\"))\n\ndef find_cifar10_source_root() -> Path:\n    # look for archives + CSVs under /kaggle/input\n    candidates = [Path(\"/kaggle/input/cifar-10\"), Path(\"/kaggle/input/cifar10\"), Path(\"/kaggle/input\")]\n    if Path(\"/kaggle/input\").exists():\n        for p in Path(\"/kaggle/input\").iterdir():\n            if p.is_dir() and \"cifar\" in p.name.lower() and \"10\" in p.name.lower():\n                candidates.append(p)\n    for root in candidates:\n        tr = list(root.rglob(\"train.7z\")) + list(root.rglob(\"train.7z.001\"))\n        te = list(root.rglob(\"test.7z\"))  + list(root.rglob(\"test.7z.001\"))\n        c1 = list(root.rglob(\"trainLabels.csv\"))\n        c2 = list(root.rglob(\"sampleSubmission.csv\"))\n        if tr and te and c1 and c2:\n            return root\n    raise FileNotFoundError(\"CIFAR-10 archives not found under /kaggle/input.\")\n\ndef first_archive(root: Path, base: str) -> Path:\n    mp = list(root.rglob(f\"{base}.7z.001\"))\n    if mp: return mp[0]\n    sp = list(root.rglob(f\"{base}.7z\"))\n    if sp: return sp[0]\n    raise FileNotFoundError(f\"Missing {base}.7z(.001) under {root}\")\n\ndef extract_7z(archive_path: Path, out_dir: Path):\n    out_dir.mkdir(parents=True, exist_ok=True)\n    t0 = time.time()\n    print(f\"→ Extracting {archive_path} → {out_dir} ...\")\n    with py7zr.SevenZipFile(archive_path, mode='r') as z:\n        z.extractall(path=out_dir)\n    print(f\"   done in {time.time()-t0:.1f}s\")\n\ndef normalize_extraction(parent: Path, target_subdir: str):\n    tgt = parent / target_subdir\n    tgt.mkdir(exist_ok=True)\n    # move content from any sibling dir named like target\n    sibs = [d for d in parent.iterdir() if d.is_dir() and target_subdir in d.name.lower() and d != tgt]\n    if sibs and count_pngs(tgt) == 0:\n        for p in sibs[0].iterdir():\n            shutil.move(str(p), str(tgt))\n        shutil.rmtree(sibs[0], ignore_errors=True)\n    # move PNGs that landed directly under parent\n    for f in list(parent.iterdir()):\n        if f.is_file() and f.suffix.lower()==\".png\":\n            shutil.move(str(f), str(tgt / f.name))\n\ndef ensure_csvs(src_root: Path, dest_root: Path):\n    for name in [\"trainLabels.csv\", \"sampleSubmission.csv\"]:\n        src = next(iter(src_root.rglob(name)))\n        dst = dest_root / name\n        if not dst.exists():\n            shutil.copy2(src, dst)\n            print(f\"→ Copied {name} to {dst}\")\n\nSRC_ROOT = find_cifar10_source_root()\nprint(\"Source root:\", SRC_ROOT)\n\nneed_train = not TRAIN_DIR.exists() or count_pngs(TRAIN_DIR) < 50000\nneed_test  = not TEST_DIR.exists()  or count_pngs(TEST_DIR)  < 300000  # soft threshold\n\nif not need_train and not need_test:\n    print(\"✅ train/ and test/ already present. Skipping extraction.\")\nelse:\n    if need_train:\n        extract_7z(first_archive(SRC_ROOT, \"train\"), DEST_ROOT)\n        normalize_extraction(DEST_ROOT, \"train\")\n        print(\"   train PNGs:\", count_pngs(TRAIN_DIR))\n    if need_test:\n        extract_7z(first_archive(SRC_ROOT, \"test\"), DEST_ROOT)\n        normalize_extraction(DEST_ROOT, \"test\")\n        print(\"   test  PNGs:\", count_pngs(TEST_DIR))\n\nensure_csvs(SRC_ROOT, DEST_ROOT)\n\n# Export explicit paths for the rest of the notebook\nfrom pathlib import Path\nDATA_ROOT = DEST_ROOT\nTRAIN_CSV = DATA_ROOT / \"trainLabels.csv\"\nSAMPLE_SUB = DATA_ROOT / \"sampleSubmission.csv\"\nprint(\"\\n✅ Ready.\")\nprint(f\"DATA_ROOT = {DATA_ROOT}\\nTRAIN_DIR = {TRAIN_DIR}\\nTEST_DIR = {TEST_DIR}\\nTRAIN_CSV = {TRAIN_CSV}\\nSAMPLE_SUB = {SAMPLE_SUB}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T10:10:28.126464Z","iopub.execute_input":"2025-10-11T10:10:28.126700Z","iopub.status.idle":"2025-10-11T10:10:34.177903Z","shell.execute_reply.started":"2025-10-11T10:10:28.126683Z","shell.execute_reply":"2025-10-11T10:10:34.177081Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Import & confi","metadata":{}},{"cell_type":"code","source":"# ==============================\n# Imports & config\n# ==============================\nimport os, math, random, csv, time\nfrom pathlib import Path\nimport numpy as np\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nfrom torch.optim import AdamW\nfrom torch.optim.lr_scheduler import LambdaLR\nfrom torch.utils.data import Dataset, DataLoader\nimport torchvision.transforms as T\nfrom sklearn.model_selection import StratifiedShuffleSplit\n\nSEED = 1337\ndef set_seed(s=SEED):\n    random.seed(s); np.random.seed(s)\n    torch.manual_seed(s); torch.cuda.manual_seed_all(s)\nset_seed()\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\ntorch.backends.cudnn.benchmark = True\nprint(\"Device:\", DEVICE)\n\nCFG = {\n    \"epochs\": 50,\n    \"batch_size\": 512,                 # lower if OOM\n    \"num_workers\": max(2, (os.cpu_count() or 4)//2),\n    \"base_lr\": 2e-3,\n    \"weight_decay\": 5e-4,\n    \"warmup_pct\": 0.05,\n    \"cutmix_alpha\": 1.0,\n    \"cutmix_prob\": 0.5,\n    \"label_smoothing\": 0.1,\n    \"ema_decay\": 0.999,\n    \"val_split\": 5000,\n}\n\n# ---- stronger-but-safe defaults ----\nCFG[\"epochs\"]        = 120          # more cosine budget\nCFG[\"base_lr\"]       = 3e-3        # a bit higher works well with cosine\nCFG[\"weight_decay\"]  = 1e-3        # slightly stronger regularization\nCFG[\"label_smoothing\"]= 0.05       # less smoothing → slightly higher ceiling\nCFG[\"cutmix_prob\"]   = 0.6         # we’ll ramp to this below\n\nCLASS_NAMES = [\"airplane\",\"automobile\",\"bird\",\"cat\",\"deer\",\"dog\",\"frog\",\"horse\",\"ship\",\"truck\"]\nCLASS2IDX = {c:i for i,c in enumerate(CLASS_NAMES)}\nIDX2CLASS = {i:c for c,i in CLASS2IDX.items()}\n\nCIFAR_MEAN = (0.4914, 0.4822, 0.4465)\nCIFAR_STD  = (0.2023, 0.1994, 0.2010)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T10:10:34.178821Z","iopub.execute_input":"2025-10-11T10:10:34.179324Z","iopub.status.idle":"2025-10-11T10:10:40.179486Z","shell.execute_reply.started":"2025-10-11T10:10:34.179299Z","shell.execute_reply":"2025-10-11T10:10:40.178624Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Datasets & Transforms","metadata":{}},{"cell_type":"code","source":"# ==============================\n# Datasets & transforms  (PATCHED: force .png)\n# ==============================\nimport csv\nfrom pathlib import Path\nfrom PIL import Image\nimport torchvision.transforms as T\n\ndef _with_png(s: str) -> str:\n    s = str(s)\n    return s if s.lower().endswith(\".png\") else (s + \".png\")\n\nclass Cifar10PNGs(torch.utils.data.Dataset):\n    def __init__(self, root: Path, csv_path: Path, transform=None, indices=None):\n        self.root = Path(root)\n        self.transform = transform\n        rows = []\n        with open(csv_path, \"r\") as f:\n            reader = csv.DictReader(f)\n            for r in reader:\n                # r[\"id\"] may be like \"40895\" -> make it \"40895.png\"\n                fid = _with_png(r[\"id\"])\n                rows.append((fid, r[\"label\"]))\n        # sort numerically on stem to keep stable order\n        rows.sort(key=lambda x: int(Path(x[0]).stem))\n\n        self.ids = [r[0] for r in rows]\n        self.labels = [CLASS2IDX[r[1]] for r in rows]\n\n        if indices is not None:\n            self.ids    = [self.ids[i] for i in indices]\n            self.labels = [self.labels[i] for i in indices]\n\n    def __len__(self): return len(self.ids)\n\n    def __getitem__(self, i):\n        img_path = self.root / self.ids[i]          # now guaranteed to end with .png\n        img = Image.open(img_path).convert(\"RGB\")\n        if self.transform: img = self.transform(img)\n        return img, self.labels[i]\n\nclass Cifar10TestPNGs(torch.utils.data.Dataset):\n    def __init__(self, root: Path, sample_csv: Path, transform=None):\n        self.root = Path(root)\n        self.transform = transform\n        self.ids = []\n        with open(sample_csv, \"r\") as f:\n            reader = csv.DictReader(f)\n            for r in reader:\n                self.ids.append(_with_png(r[\"id\"]))  # ensure .png\n        if not self.ids:  # fallback (unlikely)\n            files = sorted(self.root.glob(\"*.png\"), key=lambda p: int(p.stem))\n            self.ids = [p.name for p in files]\n\n    def __len__(self): return len(self.ids)\n\n    def __getitem__(self, i):\n        img_path = self.root / self.ids[i]\n        img = Image.open(img_path).convert(\"RGB\")\n        if self.transform: img = self.transform(img)\n        return img, self.ids[i]\n\nfrom torchvision.transforms import RandAugment\n\ntrain_tf = T.Compose([\n    T.RandomCrop(32, padding=4),\n    T.RandomHorizontalFlip(),\n    RandAugment(num_ops=2, magnitude=12),   # stronger than TrivialAugmentWide here\n    T.ToTensor(),\n    T.Normalize(CIFAR_MEAN, CIFAR_STD),\n    T.RandomErasing(p=0.30, scale=(0.02, 0.2), ratio=(0.3, 3.3), value='random'),\n])\n\nvalid_tf = T.Compose([\n    T.ToTensor(),\n    T.Normalize(CIFAR_MEAN, CIFAR_STD),\n])\n\n# Should print an existing file, e.g., \".../train/1.png\"\nprint(\"Sample train path:\", (TRAIN_DIR / _with_png(\"1\")))\nassert (TRAIN_DIR / _with_png(\"1\")).exists(), \"train image missing?\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T10:10:40.180925Z","iopub.execute_input":"2025-10-11T10:10:40.181269Z","iopub.status.idle":"2025-10-11T10:10:40.380200Z","shell.execute_reply.started":"2025-10-11T10:10:40.181252Z","shell.execute_reply":"2025-10-11T10:10:40.379418Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Split train/val and build loaders","metadata":{}},{"cell_type":"code","source":"# ==============================\n# Cell 3 (PATCHED): Split train/val in *dataset order* & build loaders\n# ==============================\nfrom sklearn.model_selection import StratifiedShuffleSplit\nimport numpy as np\nimport csv\n\n# Build a temporary dataset (no indices) to get the labels in the SAME\n# order as the dataset uses (i.e., sorted by numeric id with \".png\" added)\n_all_ds = Cifar10PNGs(TRAIN_DIR, TRAIN_CSV, transform=None, indices=None)\nlabels_ordered = np.array(_all_ds.labels)\nn_total = len(_all_ds)\n\n# Stratified split on these ordered labels\nsss = StratifiedShuffleSplit(n_splits=1, test_size=CFG[\"val_split\"], random_state=SEED)\ntrain_idx, val_idx = next(sss.split(np.zeros(n_total), labels_ordered))\n\n# Real train/val datasets & loaders\ntrain_ds = Cifar10PNGs(TRAIN_DIR, TRAIN_CSV, transform=train_tf, indices=train_idx)\nval_ds   = Cifar10PNGs(TRAIN_DIR, TRAIN_CSV, transform=valid_tf, indices=val_idx)\n\ntrain_loader = DataLoader(train_ds,\n                          batch_size=CFG[\"batch_size\"], shuffle=True,\n                          num_workers=CFG[\"num_workers\"], pin_memory=True, drop_last=True)\n\nval_loader   = DataLoader(val_ds,\n                          batch_size=1024, shuffle=False,\n                          num_workers=CFG[\"num_workers\"], pin_memory=True)\n\n# Test loader unchanged\ntest_ds  = Cifar10TestPNGs(TEST_DIR, SAMPLE_SUB, transform=valid_tf)\ntest_loader = DataLoader(test_ds,\n                         batch_size=1024, shuffle=False,\n                         num_workers=CFG[\"num_workers\"], pin_memory=True)\n\nprint(f\"Train: {len(train_ds)}  Val: {len(val_ds)}  Test: {len(test_ds)}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T10:10:40.381043Z","iopub.execute_input":"2025-10-11T10:10:40.381291Z","iopub.status.idle":"2025-10-11T10:10:41.456346Z","shell.execute_reply.started":"2025-10-11T10:10:40.381265Z","shell.execute_reply":"2025-10-11T10:10:41.455547Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model: ResNet inspired model","metadata":{}},{"cell_type":"code","source":"# ==============================\n# Model: ResNet inspired model (from scratch)\n# ==============================\ndef conv_bn(i,o,ks=3,s=1,p=1):\n    return nn.Sequential(\n        nn.Conv2d(i,o,ks,s,p,bias=False),\n        nn.BatchNorm2d(o),\n        nn.ReLU(inplace=True),\n    )\n\nclass Residual(nn.Module):\n    def __init__(self, c): \n        super().__init__()\n        self.block = nn.Sequential(\n            nn.Conv2d(c,c,3,padding=1,bias=False), nn.BatchNorm2d(c), nn.ReLU(inplace=True),\n            nn.Conv2d(c,c,3,padding=1,bias=False), nn.BatchNorm2d(c)\n        )\n        self.act = nn.ReLU(inplace=True)\n    def forward(self,x): return self.act(self.block(x)+x)\n\nclass ResNet9(nn.Module):\n    def __init__(self, num_classes=10, drop=0.1):\n        super().__init__()\n        self.stem = nn.Sequential(conv_bn(3,64), conv_bn(64,128), nn.MaxPool2d(2))\n        self.res1 = Residual(128)\n        self.mid  = nn.Sequential(conv_bn(128,256), nn.MaxPool2d(2), conv_bn(256,512), nn.MaxPool2d(2))\n        self.res2 = Residual(512)\n        self.head = nn.Sequential(nn.AdaptiveAvgPool2d(1), nn.Flatten(), nn.Dropout(drop), nn.Linear(512,num_classes))\n        self.apply(self._init)\n    @staticmethod\n    def _init(m):\n        if isinstance(m, nn.Conv2d): nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n        if isinstance(m, nn.BatchNorm2d): nn.init.constant_(m.weight,1.0); nn.init.constant_(m.bias,0.0)\n        if isinstance(m, nn.Linear): nn.init.normal_(m.weight,0,0.01); nn.init.constant_(m.bias,0.0)\n    def forward(self,x):\n        x=self.stem(x); x=self.res1(x); x=self.mid(x); x=self.res2(x); return self.head(x)\n\n\nmodel = ResNet9().to(DEVICE)\n\ndef maybe_torch_compile(m):\n    if DEVICE != \"cuda\":\n        print(\"CPU run: torch.compile skipped.\")\n        return m\n    try:\n        major, minor = torch.cuda.get_device_capability()\n        name = torch.cuda.get_device_name(0)\n    except Exception:\n        major, minor, name = 0, 0, \"Unknown CUDA device\"\n    if major >= 7:  # Volta/Turing/Ampere+ only\n        try:\n            m = torch.compile(m, dynamic=True)  # default inductor backend\n            print(\"✅ torch.compile enabled (Inductor).\")\n        except Exception as e:\n            print(\"⚠️ torch.compile failed, running eager:\", e)\n    else:\n        print(f\"⚠️ Skipping torch.compile on {name} (compute capability {major}.{minor} < 7.0).\")\n    return m\n\nmodel = maybe_torch_compile(model)\n\n# (optional) print params\nprint(f\"Params: {sum(p.numel() for p in model.parameters())/1e6:.2f}M\")\nsum(p.numel() for p in model.parameters())/1e6, \"M params\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T10:10:41.457113Z","iopub.execute_input":"2025-10-11T10:10:41.457350Z","iopub.status.idle":"2025-10-11T10:10:41.754245Z","shell.execute_reply.started":"2025-10-11T10:10:41.457332Z","shell.execute_reply":"2025-10-11T10:10:41.753478Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Optimizer, warmup-cosine, EMA, loss","metadata":{}},{"cell_type":"code","source":"# ==============================\n# Optimizer, LR schedule, EMA, loss (patched for torch.compile)\n# ==============================\nimport math\nfrom torch.optim import AdamW\nfrom torch.optim.lr_scheduler import LambdaLR\n\n# --- helper: unwrap compiled models (no-op if not compiled) ---\ndef unwrap_model(m):\n    return getattr(m, \"_orig_mod\", m)\n\noptimizer = AdamW(model.parameters(), lr=CFG[\"base_lr\"], weight_decay=CFG[\"weight_decay\"])\n\ntotal_steps  = CFG[\"epochs\"] * len(train_loader)\nwarmup_steps = int(CFG[\"warmup_pct\"] * total_steps)\n\ndef lr_lambda(step):\n    if step < warmup_steps:\n        return float(step) / float(max(1, warmup_steps))\n    progress = (step - warmup_steps) / float(max(1, total_steps - warmup_steps))\n    return 0.5 * (1.0 + math.cos(math.pi * progress))\n\nscheduler = LambdaLR(optimizer, lr_lambda=lr_lambda)\ncriterion = nn.CrossEntropyLoss(label_smoothing=CFG[\"label_smoothing\"])\n\nclass ModelEMA:\n    def __init__(self, model, decay=0.999):\n        # create same arch on same device\n        self.ema = ResNet9().to(DEVICE)\n        # initialize from the UNWRAPPED model (handles torch.compile)\n        self.ema.load_state_dict(unwrap_model(model).state_dict())\n        for p in self.ema.parameters():\n            p.requires_grad_(False)\n        self.decay = decay\n\n    @torch.no_grad()\n    def update(self, model):\n        src = unwrap_model(model)  # robust to compiled/non-compiled\n        # update parameters\n        for ema_p, p in zip(self.ema.parameters(), src.parameters()):\n            ema_p.mul_(self.decay).add_(p.data, alpha=(1.0 - self.decay))\n        # keep BN buffers (running_mean/var, num_batches_tracked) in sync\n        for ema_b, b in zip(self.ema.buffers(), src.buffers()):\n            ema_b.copy_(b)\n\nema = ModelEMA(model, decay=CFG[\"ema_decay\"])\nscaler = torch.cuda.amp.GradScaler(enabled=(DEVICE==\"cuda\"))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T10:10:41.754966Z","iopub.execute_input":"2025-10-11T10:10:41.755170Z","iopub.status.idle":"2025-10-11T10:10:41.882881Z","shell.execute_reply.started":"2025-10-11T10:10:41.755154Z","shell.execute_reply":"2025-10-11T10:10:41.882004Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## CutMix helpers","metadata":{}},{"cell_type":"code","source":"# ==============================\n# CutMix helpers\n# ==============================\nimport numpy as np\n\ndef rand_bbox(W, H, lam):\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 = np.clip(cx - cut_w//2, 0, W); y1 = np.clip(cy - cut_h//2, 0, H)\n    x2 = np.clip(cx + cut_w//2, 0, W); y2 = np.clip(cy + cut_h//2, 0, H)\n    return x1, y1, x2, y2\n\ndef apply_cutmix(x, y, alpha=1.0):\n    if alpha <= 0: return x, y, y, 1.0\n    lam = np.random.beta(alpha, alpha)\n    bs, c, h, w = x.size()\n    index = torch.randperm(bs, device=x.device)\n    x1, y1, x2, y2 = rand_bbox(w, h, lam)\n    x[:, :, y1:y2, x1:x2] = x[index, :, y1:y2, x1:x2]\n    y_a, y_b = y, y[index]\n    lam = 1 - ((x2 - x1)*(y2 - y1) / (w*h))\n    return x, y_a, y_b, lam\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T10:10:41.883620Z","iopub.execute_input":"2025-10-11T10:10:41.883843Z","iopub.status.idle":"2025-10-11T10:10:41.890718Z","shell.execute_reply.started":"2025-10-11T10:10:41.883827Z","shell.execute_reply":"2025-10-11T10:10:41.889993Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ---- Force eager mode on old GPUs (P100 etc.) ----\nimport torch, torch._dynamo\n\n# If anything tries to compile, just fall back to eager (no exception)\ntorch._dynamo.config.suppress_errors = True\n# Clear any cached compiled graphs from earlier cells\ntorch._dynamo.reset()\nprint(\"Dynamo set to suppress errors; will run eager if compile is attempted.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T10:10:41.892623Z","iopub.execute_input":"2025-10-11T10:10:41.892960Z","iopub.status.idle":"2025-10-11T10:10:41.913200Z","shell.execute_reply.started":"2025-10-11T10:10:41.892934Z","shell.execute_reply":"2025-10-11T10:10:41.912493Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Train / Validate","metadata":{}},{"cell_type":"code","source":"# ==============================\n# Train & validate\n# ==============================\ndef validate(model_eval, loader):\n    model_eval.eval()\n    loss_meter, acc_meter = 0.0, 0.0\n    with torch.no_grad():\n        for x, y in loader:\n            x, y = x.to(DEVICE, non_blocking=True), y.to(DEVICE, non_blocking=True)\n            with torch.cuda.amp.autocast(enabled=(DEVICE==\"cuda\")):\n                logits = model_eval(x)\n                loss = criterion(logits, y)\n            loss_meter += loss.item() * x.size(0)\n            acc_meter  += (logits.argmax(1) == y).sum().item()\n    n = len(loader.dataset)\n    return loss_meter/n, acc_meter/n\n\nbest_acc, best_path = 0.0, \"best_resnet9.pth\"\nglobal_step = 0\n\nfor epoch in range(1, CFG[\"epochs\"]+1):\n    model.train()\n    running_loss, running_correct = 0.0, 0\n    n_seen = 0\n    t0 = time.time()\n\n    for x, y in train_loader:\n        x, y = x.to(DEVICE, non_blocking=True), y.to(DEVICE, non_blocking=True)\n        use_cm = (np.random.rand() < CFG[\"cutmix_prob\"])\n        if use_cm:\n            x, y_a, y_b, lam = apply_cutmix(x, y, CFG[\"cutmix_alpha\"])\n\n        with torch.cuda.amp.autocast(enabled=(DEVICE==\"cuda\")):\n            logits = model(x)\n            loss = lam*criterion(logits, y_a) + (1-lam)*criterion(logits, y_b) if use_cm else criterion(logits, y)\n\n        optimizer.zero_grad(set_to_none=True)\n        scaler.scale(loss).backward()\n        nn.utils.clip_grad_norm_(model.parameters(), 2.0)\n        scaler.step(optimizer); scaler.update()\n        scheduler.step()\n        ema.update(model)\n\n        running_loss += loss.item() * x.size(0)\n        running_correct += (logits.argmax(1) == (y if not use_cm else y_a)).sum().item()\n        n_seen += x.size(0)\n        global_step += 1\n\n    train_loss = running_loss / n_seen\n    train_acc  = running_correct / n_seen\n    val_loss, val_acc = validate(ema.ema, val_loader)\n    dt = time.time() - t0\n    print(f\"Epoch {epoch:02d}/{CFG['epochs']} | train {train_loss:.4f}/{train_acc:.4f} | \"\n          f\"val {val_loss:.4f}/{val_acc:.4f} | {dt:.1f}s\")\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save(ema.ema.state_dict(), best_path)\n        print(f\"  ✅ Saved new best ({best_acc:.4f})\")\n\nprint(\"Best val_acc:\", best_acc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T10:10:41.913908Z","iopub.execute_input":"2025-10-11T10:10:41.914181Z","execution_failed":"2025-10-11T11:25:38.772Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print('hi')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T11:03:34.153894Z","iopub.execute_input":"2025-10-11T11:03:34.154551Z","iopub.status.idle":"2025-10-11T11:03:34.158767Z","shell.execute_reply.started":"2025-10-11T11:03:34.154526Z","shell.execute_reply":"2025-10-11T11:03:34.158053Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Inference & submission","metadata":{}},{"cell_type":"code","source":"# ==============================\n# Inference & Kaggle submission\n# ==============================\ndef predict_logits(xb, model_eval):\n    with torch.no_grad(), torch.cuda.amp.autocast(enabled=(DEVICE==\"cuda\")):\n        logits = model_eval(xb)\n        logits += model_eval(torch.flip(xb, dims=[3]))  # simple TTA\n        logits *= 0.5\n    return logits\n\nmodel_infer = ResNet9().to(DEVICE)\nmodel_infer.load_state_dict(torch.load(\"best_resnet9.pth\", map_location=DEVICE))\nmodel_infer.eval()\n\n# quick sanity on val\nvl, va = (lambda l: (l[0], l[1]))( (lambda: validate(model_infer, val_loader))() )\nprint(f\"Sanity val_acc (no EMA, with TTA in test only): {va:.4f}\")\n\nids, preds = [], []\nwith torch.no_grad():\n    for xb, idb in test_loader:\n        xb = xb.to(DEVICE, non_blocking=True)\n        logits = predict_logits(xb, model_infer)\n        top = logits.argmax(1).cpu().numpy()\n        preds.extend([IDX2CLASS[int(t)] for t in top])\n        ids.extend(list(idb))\n# --- after you've built the `preds` list (same as before) ---\n\n# Read the ORIGINAL ids exactly as they appear in sampleSubmission.csv\norig_ids = []\nwith open(SAMPLE_SUB, \"r\") as f:\n    reader = csv.DictReader(f)\n    for r in reader:\n        orig_ids.append(r[\"id\"])\n\nassert len(orig_ids) == len(preds), \"Length mismatch between sample ids and predictions!\"\n\n\n############# deleting all files ########\n!rm -rf /kaggle/working/*\n############# deleting all files ########\n\n\n# Write submission using the original ids (with or without .png as required)\nout_path = \"submission.csv\"\nwith open(out_path, \"w\", newline=\"\") as f:\n    w = csv.writer(f)\n    w.writerow([\"id\",\"label\"])\n    for sid, p in zip(orig_ids, preds):\n        w.writerow([sid, p])\n\nprint(\"Wrote:\", out_path, \"rows:\", len(orig_ids))\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T11:03:38.082499Z","iopub.execute_input":"2025-10-11T11:03:38.083108Z","iopub.status.idle":"2025-10-11T11:04:47.443459Z","shell.execute_reply.started":"2025-10-11T11:03:38.083085Z","shell.execute_reply":"2025-10-11T11:04:47.442505Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\npd.read_csv(\"submission.csv\").head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-11T11:02:53.698916Z","iopub.execute_input":"2025-10-11T11:02:53.699155Z","iopub.status.idle":"2025-10-11T11:02:53.818599Z","shell.execute_reply.started":"2025-10-11T11:02:53.699130Z","shell.execute_reply":"2025-10-11T11:02:53.817870Z"}},"outputs":[],"execution_count":null}]}