{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# =========================\n# Cell 1: Config & Setup\n# =========================\nimport os, io, re, time, math, glob, random, shutil, copy\nfrom dataclasses import dataclass\n\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset\nfrom torch.amp import GradScaler, autocast          # ← new AMP API\nimport torchvision\nimport torchvision.transforms as T\nfrom torchvision.models import resnet18\nfrom torchvision.transforms import RandAugment, RandomErasing\n\n@dataclass\nclass Cfg:\n    work_dir: str = \"./work\"\n    seed: int = 1337\n    device: str = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\n    # Data\n    img_size: int = 32\n    batch_size: int = 256\n    num_workers: int = 4\n\n    # Train\n    epochs: int = 150                      # ↑ longer schedule for SGD+cosine\n    base_lr: float = 0.20                  # SGD base LR\n    weight_decay: float = 5e-4\n    momentum: float = 0.9\n    nesterov: bool = True\n\n    # Aug & regularization\n    use_mixup: bool = True\n    use_cutmix: bool = True                # enable both\n    mix_alpha: float = 0.2\n    cut_alpha: float = 1.0                 # stronger CutMix\n\n    # EMA & AMP\n    use_ema: bool = True\n    ema_decay: float = 0.999\n    use_amp: bool = True\n\n    # Submission\n    submit_stream: bool = True             # True = fast (no extraction)\n    tta_hflip: bool = True                 # TTA on submit\n    submit_batch: int = 1024\n\ncfg = Cfg()\nos.makedirs(cfg.work_dir, exist_ok=True)\n\n# Repro & device\ndef set_seed(seed=1337):\n    random.seed(seed); np.random.seed(seed)\n    torch.manual_seed(seed); torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = False\n    torch.backends.cudnn.benchmark = True\n\nset_seed(cfg.seed)\ndevice = torch.device(cfg.device)\nprint(\"Device:\", device)\n\n# CIFAR-10 normalization & classes\nCIFAR_MEAN = (0.4914, 0.4822, 0.4465)\nCIFAR_STD  = (0.2470, 0.2435, 0.2616)\nCLASS_NAMES = [\"airplane\",\"automobile\",\"bird\",\"cat\",\"deer\",\"dog\",\"frog\",\"horse\",\"ship\",\"truck\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-26T05:59:54.486860Z","iopub.execute_input":"2025-09-26T05:59:54.487086Z","iopub.status.idle":"2025-09-26T06:00:06.850113Z","shell.execute_reply.started":"2025-09-26T05:59:54.487068Z","shell.execute_reply":"2025-09-26T06:00:06.849301Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# Cell 2: Data & Dataloaders\n# =========================\n# Strong but cheap augmentations\ntrain_tfms = T.Compose([\n    T.RandomCrop(cfg.img_size, padding=4),\n    T.RandomHorizontalFlip(),\n    RandAugment(num_ops=2, magnitude=9),                 # NEW\n    T.ToTensor(),\n    T.Normalize(CIFAR_MEAN, CIFAR_STD),\n    RandomErasing(p=0.25, scale=(0.02, 0.2), ratio=(0.3, 3.3), value='random'),  # NEW\n])\ntest_tfms  = T.Compose([\n    T.ToTensor(),\n    T.Normalize(CIFAR_MEAN, CIFAR_STD),\n])\n\ntrain_set = torchvision.datasets.CIFAR10(root=\"./data\", train=True,  download=True, transform=train_tfms)\ntest_set  = torchvision.datasets.CIFAR10(root=\"./data\", train=False, download=True, transform=test_tfms)\n\ndef to_onehot(y, num_classes=10):\n    return F.one_hot(y, num_classes=num_classes).float()\n\ndef mixup_cutmix_collate(batch):\n    images, labels = zip(*batch)\n    x = torch.stack(images, dim=0)\n    y = torch.tensor(labels, dtype=torch.long)\n    y_oh = to_onehot(y, 10)\n\n    if cfg.use_cutmix and random.random() < 0.5:\n        lam = np.random.beta(cfg.cut_alpha, cfg.cut_alpha)  # α=1.0\n        B, C, H, W = x.size()\n        idx = torch.randperm(B)\n        cut_w = int(W * math.sqrt(1 - lam))\n        cut_h = int(H * math.sqrt(1 - lam))\n        cx = np.random.randint(W); cy = np.random.randint(H)\n        x1 = np.clip(cx - cut_w // 2, 0, W); x2 = np.clip(cx + cut_w // 2, 0, W)\n        y1 = np.clip(cy - cut_h // 2, 0, H); y2 = np.clip(cy + cut_h // 2, 0, H)\n        x[:, :, y1:y2, x1:x2] = x[idx, :, y1:y2, x1:x2]\n        lam_adj = 1 - ((x2 - x1) * (y2 - y1)) / (W * H)\n        y_oh = lam_adj * y_oh + (1 - lam_adj) * y_oh[idx]\n    elif cfg.use_mixup:\n        lam = np.random.beta(cfg.mix_alpha, cfg.mix_alpha)  # α=0.2\n        B = x.size(0); idx = torch.randperm(B)\n        x = lam * x + (1 - lam) * x[idx]\n        y_oh = lam * y_oh + (1 - lam) * y_oh[idx]\n\n    return x, y_oh\n\ntrain_loader = DataLoader(\n    train_set, batch_size=cfg.batch_size, shuffle=True, drop_last=True,\n    num_workers=cfg.num_workers, pin_memory=True,\n    collate_fn=mixup_cutmix_collate if (cfg.use_mixup or cfg.use_cutmix) else None,\n)\nval_loader = DataLoader(\n    test_set, batch_size=1024, shuffle=False,\n    num_workers=cfg.num_workers, pin_memory=True\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-26T06:00:23.006252Z","iopub.execute_input":"2025-09-26T06:00:23.006422Z","iopub.status.idle":"2025-09-26T06:00:24.505525Z","shell.execute_reply.started":"2025-09-26T06:00:23.006408Z","shell.execute_reply":"2025-09-26T06:00:24.504992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# Cell 3: Model, Optimizer, LR, EMA, AMP\n# =========================\n# Model — NO PRETRAIN (random init)\nmodel = resnet18(weights=None, num_classes=10).to(device)\n# (optional) re-init head explicitly\nnn.init.kaiming_normal_(model.fc.weight, mode='fan_out', nonlinearity='relu')\nnn.init.zeros_(model.fc.bias)\n\n# Weight decay: apply to weights only (not BN/bias)\ndef split_weights(m):\n    decay, no_decay = [], []\n    for name, p in m.named_parameters():\n        if not p.requires_grad: \n            continue\n        if p.ndim == 1 or name.endswith(\".bias\"):  # BN/bias\n            no_decay.append(p)\n        else:\n            decay.append(p)\n    return [\n        {\"params\": decay,    \"weight_decay\": cfg.weight_decay},\n        {\"params\": no_decay, \"weight_decay\": 0.0},\n    ]\n\noptimizer = torch.optim.SGD(\n    split_weights(model), lr=cfg.base_lr,\n    momentum=cfg.momentum, nesterov=cfg.nesterov\n)\n\n# 5-epoch warmup + cosine\ndef cosine_with_warmup_lambda(epoch):\n    warmup = 5\n    if epoch < warmup:\n        return float(epoch + 1) / warmup\n    progress = (epoch - warmup) / max(1, (cfg.epochs - warmup))\n    return 0.5 * (1.0 + math.cos(math.pi * progress))\n\nscheduler = torch.optim.lr_scheduler.LambdaLR(optimizer, lr_lambda=cosine_with_warmup_lambda)\n\n# Loss (supports soft labels)\ndef soft_ce_loss(logits, targets):\n    log_probs = F.log_softmax(logits, dim=1)\n    return -(targets * log_probs).sum(dim=1).mean()\n\n# AMP (new API)\nscaler = GradScaler('cuda', enabled=cfg.use_amp)\n\n# EMA (safe deepcopy-based)\nclass ModelEMA:\n    def __init__(self, model, decay=cfg.ema_decay):\n        self.decay = decay\n        self.ema = copy.deepcopy(model).to(next(model.parameters()).device)\n        self.ema.eval()\n        for p in self.ema.parameters():\n            p.requires_grad_(False)\n        self._backup = None\n\n    @torch.no_grad()\n    def update(self, model):\n        msd = model.state_dict()\n        esd = self.ema.state_dict()\n        for k in esd.keys():\n            if esd[k].dtype.is_floating_point:\n                esd[k].mul_(self.decay).add_(msd[k].detach(), alpha=1.0 - self.decay)\n            else:\n                esd[k].copy_(msd[k])\n\n    def store(self, model):\n        self._backup = copy.deepcopy(model.state_dict())\n\n    def copy_to(self, model):\n        model.load_state_dict(self.ema.state_dict(), strict=True)\n\n    def restore(self, model):\n        if self._backup is not None:\n            model.load_state_dict(self._backup, strict=True)\n            self._backup = None\n\nema = ModelEMA(model) if cfg.use_ema else None\nce = nn.CrossEntropyLoss()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-26T06:00:33.112557Z","iopub.execute_input":"2025-09-26T06:00:33.113501Z","iopub.status.idle":"2025-09-26T06:00:33.508587Z","shell.execute_reply.started":"2025-09-26T06:00:33.113467Z","shell.execute_reply":"2025-09-26T06:00:33.508021Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# Cell 4: Train / Eval / Loop\n# =========================\ndef _train_acc_from_logits(logits, targets):\n    preds = logits.argmax(1)\n    tgt = targets.argmax(1) if targets.ndim == 2 else targets\n    return (preds == tgt).float().mean().item()\n\ndef train_one_epoch(epoch):\n    model.train()\n    running_loss, running_acc, n = 0.0, 0.0, 0\n    t0 = time.time()\n    for images, targets in train_loader:\n        images = images.to(device, non_blocking=True)\n        targets = targets.to(device, non_blocking=True)\n        optimizer.zero_grad(set_to_none=True)\n\n        with autocast(device_type='cuda', enabled=cfg.use_amp):\n            logits = model(images)\n            loss = soft_ce_loss(logits, targets) if targets.ndim == 2 else ce(logits, targets)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        if ema: ema.update(model)\n\n        bs = images.size(0)\n        running_loss += loss.item() * bs\n        running_acc  += _train_acc_from_logits(logits.detach(), targets) * bs\n        n += bs\n\n    scheduler.step()\n    return running_loss / n, running_acc / n, time.time() - t0\n\n@torch.no_grad()\ndef evaluate(use_ema=True):\n    if use_ema and ema:\n        ema.store(model); ema.copy_to(model)\n    model.eval()\n    total_loss, total_acc, n = 0.0, 0.0, 0\n    for images, labels in val_loader:\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n        logits = model(images)\n        loss = ce(logits, labels)\n        acc = (logits.argmax(1) == labels).float().mean().item()\n        bs = images.size(0)\n        total_loss += loss.item() * bs\n        total_acc  += acc * bs\n        n += bs\n    if use_ema and ema:\n        ema.restore(model)\n    return total_loss / n, total_acc / n\n\n# Training loop\nbest_acc = 0.0\nbest_path = os.path.join(cfg.work_dir, \"best_model.pth\")\n\nfor epoch in range(cfg.epochs):\n    tr_loss, tr_acc, dt = train_one_epoch(epoch)\n    val_loss, val_acc = evaluate(use_ema=True)\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save((ema.ema if (cfg.use_ema and ema) else model).state_dict(), best_path)\n\n    lr_now = optimizer.param_groups[0]['lr']\n    print(f\"Epoch {epoch+1:03d}/{cfg.epochs} | lr {lr_now:.5f} | \"\n          f\"train_loss {tr_loss:.4f} acc {tr_acc:.4f} | \"\n          f\"val_loss {val_loss:.4f} acc {val_acc:.4f} | {dt:.1f}s\")\n\nprint(\"Best Val Acc:\", best_acc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-26T06:00:36.540884Z","iopub.execute_input":"2025-09-26T06:00:36.541393Z","iopub.status.idle":"2025-09-26T06:46:38.460204Z","shell.execute_reply.started":"2025-09-26T06:00:36.541366Z","shell.execute_reply":"2025-09-26T06:46:38.459213Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# Cell 5: Offline test accuracy (same split as val_loader)\n# =========================\nassert os.path.exists(best_path), f\"Checkpoint not found: {best_path}\"\ntest_loader_off = DataLoader(test_set, batch_size=1024, shuffle=False,\n                             num_workers=cfg.num_workers, pin_memory=True)\n\neval_model = resnet18(weights=None, num_classes=10).to(device).eval()\nstate = torch.load(best_path, map_location=device)\neval_model.load_state_dict(state, strict=True)\neval_model.eval()\n\ncorrect, total = 0, 0\nwith torch.inference_mode():\n    for xb, yb in test_loader_off:\n        xb, yb = xb.to(device, non_blocking=True), yb.to(device, non_blocking=True)\n        pred = eval_model(xb).argmax(1)\n        correct += (pred == yb).sum().item()\n        total   += yb.size(0)\nfinal_acc = correct / total\nprint(f\"[OFFLINE] CIFAR-10 test accuracy: {final_acc:.4f} ({final_acc*100:.2f}%)\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-26T06:50:13.513931Z","iopub.execute_input":"2025-09-26T06:50:13.514766Z","iopub.status.idle":"2025-09-26T06:50:14.901752Z","shell.execute_reply.started":"2025-09-26T06:50:13.514733Z","shell.execute_reply":"2025-09-26T06:50:14.900558Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# Cell 6  Extract \n# =========================\n!pip -q install py7zr\nimport py7zr\n\n# Find archive\ncandidates = glob.glob('/kaggle/input/**/test.7z', recursive=True)\narchive_path = candidates[0]\n\nshutil.rmtree('test', ignore_errors=True)\nos.makedirs('test', exist_ok=True)\nwith py7zr.SevenZipFile(archive_path, mode='r') as z:\n    z.extractall(path='test')\n\npng_files = glob.glob('test/**/*.png', recursive=True)\nprint(\"PNG files:\", len(png_files))\nassert len(png_files) == 300000, f\"Expected 300000, got {len(png_files)}\"\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-26T06:50:19.679731Z","iopub.execute_input":"2025-09-26T06:50:19.680275Z","iopub.status.idle":"2025-09-26T06:52:29.684570Z","shell.execute_reply.started":"2025-09-26T06:50:19.680243Z","shell.execute_reply":"2025-09-26T06:52:29.683865Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================\n# Cell 7 : submit\n# =========================\n# Build dataset/loader\nval_tfms = T.Compose([\n    T.Resize((cfg.img_size, cfg.img_size), antialias=True),\n    T.ToTensor(),\n    T.Normalize(CIFAR_MEAN, CIFAR_STD),\n])\n\nclass KaggleTestDataset(Dataset):\n    def __init__(self, root=\"test\", transform=None):\n        self.transform = transform\n        files = glob.glob(os.path.join(root, \"**\", \"*.png\"), recursive=True)\n        def keyfn(p):\n            b = os.path.basename(p)\n            m = re.match(r\"(\\d+)\", os.path.splitext(b)[0]); \n            return int(m.group(1)) if m else 10**12\n        files.sort(key=keyfn)\n        self.paths = files\n        self.ids = [int(os.path.splitext(os.path.basename(p))[0]) for p in self.paths]\n    def __len__(self): return len(self.paths)\n    def __getitem__(self, i):\n        x = self.transform(Image.open(self.paths[i]).convert(\"RGB\"))\n        return x, self.ids[i]\n\ntest_ds = KaggleTestDataset(transform=val_tfms)\ntest_loader_submit = DataLoader(test_ds, batch_size=cfg.submit_batch, shuffle=False,\n                                num_workers=cfg.num_workers, pin_memory=True)\n\n\n# Predict\nsubmit_model = resnet18(weights=None, num_classes=10).to(device).eval()\nsubmit_model.load_state_dict(torch.load(best_path, map_location=device), strict=True)\nsubmit_model.eval()\n\nall_ids, all_preds = [], []\nwith torch.inference_mode():\n    for xb, idb in test_loader_submit:\n        xb = xb.to(device, non_blocking=True)\n        logits = submit_model(xb)\n        if cfg.tta_hflip:\n            logits = (logits + submit_model(torch.flip(xb, dims=[3]))) / 2.0\n        preds = logits.argmax(1).cpu().numpy()\n        all_ids.append(idb.numpy()); all_preds.append(preds)\n\nids = np.concatenate(all_ids)\nlabels_idx = np.concatenate(all_preds)\nlabels = [CLASS_NAMES[i] for i in labels_idx]\nsub = pd.DataFrame({\"id\": ids, \"label\": labels}).sort_values(\"id\")\n\nassert sub.shape[0] == 300000\nassert sub[\"id\"].min() == 1 and sub[\"id\"].max() == 300000\nassert sub[\"id\"].nunique() == 300000\n\nout_csv = os.path.join(cfg.work_dir, \"submission.csv\")\nsub.to_csv(out_csv, index=False)\nprint(sub.head(), \"\\nWrote:\", out_csv)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-26T06:53:02.498483Z","iopub.execute_input":"2025-09-26T06:53:02.499104Z","iopub.status.idle":"2025-09-26T06:53:50.738929Z","shell.execute_reply.started":"2025-09-26T06:53:02.499075Z","shell.execute_reply":"2025-09-26T06:53:50.738070Z"}},"outputs":[],"execution_count":null}]}