{"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"}],"dockerImageVersionId":31154,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, glob, shutil, subprocess, copy, random, time, math, datetime\nos.environ.setdefault(\"OMP_NUM_THREADS\", \"4\")\nimport numpy as np\nnp.set_printoptions(suppress=True)\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset\nimport torchvision.transforms as T\n\n#  seeds / device \nrandom.seed(42); np.random.seed(42)\ntorch.manual_seed(42); torch.cuda.manual_seed_all(42)\ntorch.backends.cudnn.benchmark = True\ntry: torch.set_float32_matmul_precision('high')\nexcept Exception: pass\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint('device:', device)\n\n#  knobs \nBATCH_SIZE   = 256    # fast & stable on T4/P100; drop to 128 if OOM\nMAX_EPOCHS   = 120\nWARMUP_EP    = 15\nTARGET_ACC   = 94.2   # early-stop threshold\nTRAIN_MIN    = 480    # training wall-clock cap (minutes) to leave buffer for submission\nWIDTH        = 2.0\nWEIGHT_DECAY = 0.05\nMAX_LR       = 3e-3\nMIN_LR       = 1e-6\nPATIENCE_EP  = 30     # patience after last best before stopping\n\n#  paths / classes \nROOT = \"/kaggle/input/cifar-10\"\nLABELS_CSV = os.path.join(ROOT, \"trainLabels.csv\")\nTRAIN_EXTRACT = \"/kaggle/working/c10_train\"\nTEST_DIR_FALLBACK = \"/kaggle/input/cifar-10/test\"\nTEST_7Z = os.path.join(ROOT, \"test.7z\")\n\nCLASSES = [\"airplane\",\"automobile\",\"bird\",\"cat\",\"deer\",\"dog\",\"frog\",\"horse\",\"ship\",\"truck\"]\ncls2idx = {c:i for i,c in enumerate(CLASSES)}\nMEAN = [0.4914, 0.4822, 0.4465]\nSTD  = [0.2023, 0.1994, 0.2010]\n\n#  extract train only \ndef _extract_train_only():\n    if os.path.isdir(TRAIN_EXTRACT) and len(glob.glob(os.path.join(TRAIN_EXTRACT,'**','*.*'), recursive=True))>1000:\n        return\n    t7 = os.path.join(ROOT, 'train.7z')\n    if not os.path.exists(t7): raise FileNotFoundError(\"train.7z not found in /kaggle/input/cifar-10\")\n    os.makedirs(TRAIN_EXTRACT, exist_ok=True)\n    print('extracting train.7z ->', TRAIN_EXTRACT)\n    subprocess.run(['7z','x','-y', f'-o{TRAIN_EXTRACT}', t7], check=True)\n    nimgs = sum(p.lower().endswith(('.png','.jpg','.jpeg')) for p in glob.glob(os.path.join(TRAIN_EXTRACT,'**','*.*'), recursive=True))\n    print('train images:', nimgs)\n\ndef _find_img_dir(root):\n    for dp, dn, fn in os.walk(root):\n        if any(f.lower().endswith(('.png','.jpg','.jpeg')) for f in fn):\n            return dp\n    return root\n\ndef _build_id_index(img_dir):\n    files = []\n    files += glob.glob(os.path.join(img_dir, '*.png'))\n    files += glob.glob(os.path.join(img_dir, '*.jpg'))\n    files += glob.glob(os.path.join(img_dir, '*.jpeg'))\n    idx = {os.path.splitext(os.path.basename(p))[0]: p for p in files}\n    print(\"indexed files:\", len(idx))\n    return idx\n\n#  model\nclass SEBlock(nn.Module):\n    def __init__(self, ch, r=16):\n        super().__init__()\n        mid = max(8, ch//r)\n        self.se = nn.Sequential(\n            nn.AdaptiveAvgPool2d(1),\n            nn.Conv2d(ch, mid, 1), nn.ReLU(inplace=True),\n            nn.Conv2d(mid, ch, 1), nn.Sigmoid()\n        )\n    def forward(self, x): return x * self.se(x)\n\nclass ResBlock(nn.Module):\n    def __init__(self, in_ch, out_ch, stride=1):\n        super().__init__()\n        self.conv1 = nn.Conv2d(in_ch, out_ch, 3, stride, 1, bias=False)\n        self.bn1 = nn.BatchNorm2d(out_ch)\n        self.conv2 = nn.Conv2d(out_ch, out_ch, 3, 1, 1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_ch)\n        self.se = SEBlock(out_ch, r=16)\n        self.skip = nn.Sequential()\n        if stride != 1 or in_ch != out_ch:\n            self.skip = nn.Sequential(nn.Conv2d(in_ch, out_ch, 1, stride, bias=False),\n                                      nn.BatchNorm2d(out_ch))\n    def forward(self, x):\n        y = F.relu(self.bn1(self.conv1(x)))\n        y = self.bn2(self.conv2(y))\n        y = self.se(y)\n        return F.relu(y + self.skip(x))\n\nclass Net(nn.Module):\n    def __init__(self, n_classes=10, width=2.0):\n        super().__init__()\n        w = lambda c: int(c*width)\n        self.stem = nn.Sequential(nn.Conv2d(3, w(64), 3, 1, 1, bias=False),\n                                  nn.BatchNorm2d(w(64)), nn.ReLU(inplace=True))\n        self.layer1 = self._make_layer(w(64),  w(64),  4, 1)\n        self.layer2 = self._make_layer(w(64),  w(128), 4, 2)\n        self.layer3 = self._make_layer(w(128), w(256), 4, 2)\n        self.pool = nn.AdaptiveAvgPool2d(1)\n        self.drop = nn.Dropout(0.2)\n        self.fc = nn.Linear(w(256), n_classes)\n        self._init_weights()\n    def _make_layer(self, in_ch, out_ch, n_blocks, stride):\n        layers = [ResBlock(in_ch, out_ch, stride)]\n        for _ in range(1, n_blocks): layers.append(ResBlock(out_ch, out_ch, 1))\n        return nn.Sequential(*layers)\n    def _init_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n            elif isinstance(m, nn.BatchNorm2d):\n                nn.init.constant_(m.weight, 1); nn.init.constant_(m.bias, 0)\n    def forward(self, x):\n        x = self.stem(x); x = self.layer1(x); x = self.layer2(x); x = self.layer3(x)\n        x = self.pool(x).flatten(1); x = self.drop(x); return self.fc(x)\n\n#  augs / mix \nclass Cutout:\n    def __init__(self, size=16): self.size = size\n    def __call__(self, img_tensor):\n        h, w = img_tensor.size(1), img_tensor.size(2)\n        mask = np.ones((h,w), np.float32)\n        y = np.random.randint(h); x = np.random.randint(w)\n        y1,y2 = np.clip(y-self.size//2,0,h), np.clip(y+self.size//2,0,h)\n        x1,x2 = np.clip(x-self.size//2,0,w), np.clip(x+self.size//2,0,w)\n        mask[y1:y2, x1:x2] = 0.\n        return img_tensor * torch.from_numpy(mask).to(img_tensor.dtype).to(img_tensor.device).expand_as(img_tensor)\n\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 = 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    return x1, y1, x2, y2\n\ndef mixup_cutmix(x, y, alpha=1.0, cutmix_prob=0.5):\n    if random.random() < cutmix_prob:\n        lam = np.random.beta(alpha, alpha)\n        idx = torch.randperm(x.size(0), device=x.device)\n        x1,y1,x2,y2 = rand_bbox(x.size(), lam)\n        x[:, :, y1:y2, x1:x2] = x[idx, :, y1:y2, x1:x2]\n        lam = 1 - ((x2-x1)*(y2-y1)/(x.size(-1)*x.size(-2)))\n        return x, y, y[idx], lam\n    else:\n        lam = np.random.beta(alpha, alpha)\n        idx = torch.randperm(x.size(0), device=x.device)\n        return lam*x + (1-lam)*x[idx], y, y[idx], lam\n\n#  cosine lr with warmup \nclass CosineLR:\n    def __init__(self, optimizer, max_lr, min_lr, warmup_steps, total_steps):\n        self.opt=optimizer; self.max_lr=max_lr; self.min_lr=min_lr\n        self.warm=max(1,warmup_steps); self.total=max(self.warm+1,total_steps); self.i=0\n    def step(self):\n        self.i += 1\n        if self.i < self.warm:\n            lr = self.max_lr * self.i / self.warm\n        else:\n            prog = (self.i - self.warm) / (self.total - self.warm)\n            lr = self.min_lr + (self.max_lr - self.min_lr) * 0.5 * (1 + math.cos(math.pi*prog))\n        for pg in self.opt.param_groups: pg['lr'] = lr\n        return lr\n\n#  dataset \nclass KaggleCIFARTrain(Dataset):\n    def __init__(self, labels_csv, id2path, transform=None, indices=None):\n        self.tfm = transform\n        df = pd.read_csv(labels_csv)[['id','label']]\n        if indices is not None: df = df.iloc[indices].reset_index(drop=True)\n        self.ids  = df['id'].astype(str).tolist()\n        self.lbls = [cls2idx[l] for l in df['label'].tolist()]\n        self.paths = [id2path.get(i, os.path.join(TRAIN_IMG_DIR, f\"{i}.png\")) for i in self.ids]\n        missing = sum(not os.path.exists(p) for p in self.paths)\n        if missing: raise FileNotFoundError(f\"{missing} train images missing in index/paths\")\n    def __len__(self): return len(self.paths)\n    def __getitem__(self, i):\n        img = Image.open(self.paths[i]).convert('RGB')\n        if self.tfm: img = self.tfm(img)\n        return img, self.lbls[i]\n\n#  submission helpers \nclass _TestFiles(Dataset):\n    def __init__(self, paths, tfm):\n        self.paths, self.tfm = paths, tfm\n    def __len__(self): return len(self.paths)\n    def __getitem__(self, i):\n        p = self.paths[i]\n        img = Image.open(p).convert('RGB')\n        return self.tfm(img), os.path.splitext(os.path.basename(p))[0]\n\ndef _find_attached_test_folder():\n    base=\"/kaggle/input\"\n    try:\n        for d in os.listdir(base):\n            cand = os.path.join(base, d, \"test\")\n            if os.path.isdir(cand):\n                hits = glob.glob(os.path.join(cand,\"*.png\"))\n                if len(hits)>1000: return cand\n    except Exception:\n        pass\n    return None\n\ndef _list_7z_images(archive, cache=\"/kaggle/working/test_names.txt\"):\n    if os.path.exists(cache):\n        with open(cache,'r') as f: return [ln.strip() for ln in f if ln.strip()]\n    out = subprocess.check_output(['7z','l','-slt', archive], text=True)\n    names=[]\n    for ln in out.splitlines():\n        if ln.startswith('Path = '):\n            p = ln[7:]\n            if p.lower().endswith(('.png','.jpg','.jpeg')): names.append(p)\n    with open(cache,'w') as f: f.write('\\n'.join(names))\n    return names\n\ndef _extract_chunk(archive, names, outdir, threads=4):\n    if os.path.exists(outdir): shutil.rmtree(outdir)\n    os.makedirs(outdir, exist_ok=True)\n    lst = os.path.join(outdir, \"list.txt\")\n    with open(lst,'w') as f:\n        for n in names: f.write(n+'\\n')\n    subprocess.run(['7z','x','-y', f'-mmt={threads}', f'-o{outdir}', archive, f'@{lst}'],\n                   check=True, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)\n\n@torch.no_grad()\ndef make_submission(model, test_dir, device, out_file='/kaggle/working/submission.csv', chunk=6000, bs=2048, tta=2):\n    tfm = T.Compose([T.ToTensor(), T.Normalize(MEAN, STD)])\n    td = _find_attached_test_folder() or test_dir\n    ids_all, preds_all = [], []\n    model.eval()\n\n    def _pred(x):\n        out = model(x)\n        if tta>1: out = (out + model(torch.flip(x, [3]))) / 2.0\n        return out\n\n    if os.path.isdir(td):\n        paths = sorted(glob.glob(os.path.join(td, \"*.png\")), key=lambda p: int(os.path.splitext(os.path.basename(p))[0]))\n        print(f\"[folder] images: {len(paths)}\")\n        ds = _TestFiles(paths, tfm)\n        loader = DataLoader(ds, batch_size=bs, shuffle=False, num_workers=2, pin_memory=True)\n        for x, ids in tqdm(loader, desc='predict (folder)'):\n            x = x.to(device, non_blocking=True)\n            preds = _pred(x).argmax(1).cpu().numpy()\n            ids_all += list(ids); preds_all += list(preds)\n    else:\n        if not os.path.exists(TEST_7Z): raise FileNotFoundError(\"No test folder and no test.7z\")\n        names = _list_7z_images(TEST_7Z)\n        print(f\"[7z] entries: {len(names)}\")\n        TMP = \"/kaggle/working/test_chunks\"; os.makedirs(TMP, exist_ok=True)\n        for s in tqdm(range(0, len(names), chunk), desc=\"predict (7z chunks)\"):\n            cur = os.path.join(TMP, \"cur\")\n            subnames = names[s:s+chunk]\n            _extract_chunk(TEST_7Z, subnames, cur, threads=4)\n            paths = [os.path.join(cur, n) for n in subnames]\n            paths.sort(key=lambda p: int(os.path.splitext(os.path.basename(p))[0]))\n            ds = _TestFiles(paths, tfm)\n            loader = DataLoader(ds, batch_size=bs, shuffle=False, num_workers=2, pin_memory=True)\n            for x, ids in loader:\n                x = x.to(device, non_blocking=True)\n                preds = _pred(x).argmax(1).cpu().numpy()\n                ids_all += list(ids); preds_all += list(preds)\n            shutil.rmtree(cur, ignore_errors=True)\n\n    labels = [CLASSES[i] for i in preds_all]\n    df = pd.DataFrame({'id': ids_all, 'label': labels})\n    try: df['id'] = df['id'].astype(int); df = df.sort_values('id')\n    except: pass\n    df.to_csv(out_file, index=False)\n    print('saved ->', out_file, '| abs:', os.path.abspath(out_file))\n    return out_file\n\n#  train / eval \ndef train_one_epoch(model, loader, opt, criterion, scheduler, device, use_mix=False, scaler=None):\n    model.train()\n    total_loss, correct, total = 0.0, 0, 0\n    pbar = tqdm(loader, desc='train')\n    for x, y in pbar:\n        x = x.to(device, non_blocking=True)\n        y = y.to(device, non_blocking=True)\n        opt.zero_grad(set_to_none=True)\n        with torch.amp.autocast('cuda', enabled=(device.type=='cuda')):\n            if use_mix:\n                xmix, ya, yb, lam = mixup_cutmix(x, y, alpha=1.0, cutmix_prob=0.5)\n                out = model(xmix); loss = lam*criterion(out, ya) + (1-lam)*criterion(out, yb)\n            else:\n                out = model(x); loss = criterion(out, y)\n        if scaler is not None:\n            scaler.scale(loss).backward()\n            scaler.unscale_(opt)\n            nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n            scaler.step(opt); scaler.update()\n        else:\n            loss.backward(); nn.utils.clip_grad_norm_(model.parameters(), 1.0); opt.step()\n        scheduler.step()\n        total_loss += loss.item()\n        _, pred = out.max(1); total += y.size(0); correct += pred.eq(y).sum().item()\n        pbar.set_postfix({'loss': f'{total_loss/(pbar.n+1):.3f}', 'acc': f'{100.*correct/total:.2f}%'})\n    return total_loss/len(loader), 100.*correct/total\n\n@torch.no_grad()\ndef evaluate(model, loader, device, tta_levels=1):\n    model.eval()\n    correct, total = 0, 0\n    for x, y in tqdm(loader, desc='eval'):\n        x = x.to(device, non_blocking=True)\n        y = y.to(device, non_blocking=True)\n        out = model(x)\n        if tta_levels>1: out = (out + model(torch.flip(x,[3]))) / 2.0\n        correct += (out.argmax(1)==y).sum().item(); total += y.size(0)\n    return 100.*correct/total\n\n#  robust checkpoint I/O \ndef try_load_checkpoint(path):\n    if not os.path.exists(path): return None\n    try:\n        return torch.load(path, map_location='cpu', weights_only=False)\n    except TypeError:\n        return torch.load(path, map_location='cpu')\n    except Exception as e:\n        print('bad checkpoint, ignoring:', e); return None\n\ndef safe_save_ckpt(epoch, model, opt, best_acc, best_state, path='last_checkpoint.pth'):\n    tmp = path + '.tmp'\n    torch.save({'epoch': int(epoch),\n                'model': model.state_dict(),\n                'optimizer': opt.state_dict(),\n                'best_acc': float(best_acc),\n                'best_state': best_state}, tmp)\n    os.replace(tmp, path)\n\n#  main \ndef main():\n    wall0 = time.time()\n\n    # data\n    _extract_train_only()\n    global TRAIN_IMG_DIR\n    TRAIN_IMG_DIR = _find_img_dir(TRAIN_EXTRACT)\n    print(\"train_img_dir:\", TRAIN_IMG_DIR)\n\n    ID2PATH = _build_id_index(TRAIN_IMG_DIR)\n\n    train_tfm = T.Compose([\n        T.RandomCrop(32, padding=4),\n        T.RandomHorizontalFlip(),\n        T.AutoAugment(T.AutoAugmentPolicy.CIFAR10),\n        T.ToTensor(),\n        T.Normalize(MEAN, STD),\n        Cutout(16)\n    ])\n    val_tfm = T.Compose([T.ToTensor(), T.Normalize(MEAN, STD)])\n\n    df = pd.read_csv(LABELS_CSV)\n    n = len(df); idx = np.arange(n); rng = np.random.RandomState(42); rng.shuffle(idx)\n    v = 5000; tr_idx, va_idx = idx[:-v], idx[-v:]\n\n    train_ds = KaggleCIFARTrain(LABELS_CSV, ID2PATH, transform=train_tfm, indices=tr_idx)\n    val_ds   = KaggleCIFARTrain(LABELS_CSV, ID2PATH, transform=val_tfm,   indices=va_idx)\n\n    nw = 2\n    train_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True,  num_workers=nw, pin_memory=True, persistent_workers=False)\n    val_loader   = DataLoader(val_ds,   batch_size=BATCH_SIZE*2, shuffle=False, num_workers=nw, pin_memory=True, persistent_workers=False)\n\n    xb, yb = next(iter(train_loader))\n    print(f\"[smoke] batch {tuple(xb.shape)} labels {tuple(yb.shape)}\")\n    print(\"using batch_size=\", BATCH_SIZE)\n\n    # model / opt / sched\n    model = Net(n_classes=10, width=WIDTH).to(device)\n    print('params:', f'{sum(p.numel() for p in model.parameters()):,}')\n    opt = torch.optim.AdamW(model.parameters(), lr=MAX_LR, weight_decay=WEIGHT_DECAY, betas=(0.9,0.999))\n    criterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n    scaler = torch.amp.GradScaler('cuda', enabled=(device.type=='cuda'))\n\n    total_steps = len(train_loader) * MAX_EPOCHS\n    warmup_steps = len(train_loader) * WARMUP_EP\n    scheduler = CosineLR(opt, MAX_LR, MIN_LR, warmup_steps, total_steps)\n\n    best_acc, best_state, start_epoch = 0.0, None, 0\n\n    # resume if possible\n    ckpt = try_load_checkpoint('last_checkpoint.pth')\n    if ckpt is not None:\n        try:\n            model.load_state_dict(ckpt['model'])\n            opt.load_state_dict(ckpt['optimizer'])\n            best_acc   = float(ckpt.get('best_acc', 0.0))\n            best_state = ckpt.get('best_state', None)\n            start_epoch = int(ckpt.get('epoch', -1)) + 1\n            print(f'resumed at epoch {start_epoch}, best {best_acc:.2f}%')\n        except Exception as e:\n            print('resume failed, starting fresh:', e)\n\n    # train\n    epoch_times=[]; best_epoch=start_epoch-1\n    for ep in range(start_epoch, MAX_EPOCHS):\n        print(f'\\nepoch {ep+1}/{MAX_EPOCHS}')\n        t0=time.time()\n        loss, tr_acc = train_one_epoch(model, train_loader, opt, criterion, scheduler, device,\n                                       use_mix=(ep>=5), scaler=scaler)\n        t1=time.time(); epoch_times.append(t1-t0)\n        avg = sum(epoch_times[-3:]) / min(3, len(epoch_times))\n        left = MAX_EPOCHS-(ep+1); eta = datetime.datetime.now()+datetime.timedelta(seconds=avg*left)\n        print(f\"[timing] last={t1-t0:.1f}s  avg(3)={avg:.1f}s  left≈{left}  ETA~{eta:%H:%M:%S}\")\n\n        do_val = (ep < 3) or ((ep+1) % 5 == 0) or (ep == MAX_EPOCHS-1)\n        if do_val:\n            acc = evaluate(model, val_loader, device, tta_levels=1)\n            print(f'val acc: {acc:.2f}%')\n            if acc > best_acc:\n                best_acc = acc; best_state = copy.deepcopy(model.state_dict()); best_epoch = ep\n                torch.save(model.state_dict(), 'best_model_live.pth')\n                print(f'*** new best: {best_acc:.2f}% ***')\n            safe_save_ckpt(ep, model, opt, best_acc, best_state, 'last_checkpoint.pth')\n            if acc >= TARGET_ACC:\n                print(f'early stop: reached target {TARGET_ACC}%'); break\n            if ep - best_epoch >= PATIENCE_EP:\n                print(f'early stop: no improvement for {PATIENCE_EP} epochs'); break\n\n        if (time.time()-wall0)/60.0 > TRAIN_MIN:\n            print('training time cap reached; stopping training'); break\n\n    if best_state is not None:\n        model.load_state_dict(best_state)\n    torch.save(model.state_dict(), 'best_model.pth'); print('model saved -> best_model.pth')\n\n    final = evaluate(model, val_loader, device, tta_levels=2)\n    print('\\nfinal (tta=2) val acc:', f'{final:.2f}%')\n\n    print('generating submission...')\n    out_csv = make_submission(model, TEST_DIR_FALLBACK, device,\n                              out_file='/kaggle/working/submission.csv',\n                              chunk=6000, bs=2048, tta=2)\n    print(\"Exists?\", os.path.exists(out_csv))\n    try: print(pd.read_csv(out_csv).head())\n    except: pass\n        \nif __name__ == '__main__':\n    _extract_train_only()\n    main()\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-15T11:23:47.157854Z","iopub.execute_input":"2025-10-15T11:23:47.158196Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, glob, shutil, subprocess, random, time, math, json\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset\nimport torchvision.transforms as T\n\n#  device \ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint('device:', device)\n\n#  constants \nROOT = \"/kaggle/input/cifar-10\"\nTEST_DIR_FALLBACK = \"/kaggle/input/cifar-10/test\"\nTEST_7Z = os.path.join(ROOT, \"test.7z\")\nWORK = \"/kaggle/working\"\nPART_DIR = os.path.join(WORK, \"sub_parts\")\nos.makedirs(PART_DIR, exist_ok=True)\n\nCLASSES = [\"airplane\",\"automobile\",\"bird\",\"cat\",\"deer\",\"dog\",\"frog\",\"horse\",\"ship\",\"truck\"]\nMEAN = [0.4914, 0.4822, 0.4465]\nSTD  = [0.2023, 0.1994, 0.2010]\n\n#  model \nclass SEBlock(nn.Module):\n    def __init__(self, ch, r=16):\n        super().__init__()\n        mid = max(8, ch//r)\n        self.se = nn.Sequential(\n            nn.AdaptiveAvgPool2d(1),\n            nn.Conv2d(ch, mid, 1), nn.ReLU(inplace=True),\n            nn.Conv2d(mid, ch, 1), nn.Sigmoid()\n        )\n    def forward(self, x): return x * self.se(x)\n\nclass ResBlock(nn.Module):\n    def __init__(self, in_ch, out_ch, stride=1):\n        super().__init__()\n        self.conv1 = nn.Conv2d(in_ch, out_ch, 3, stride, 1, bias=False)\n        self.bn1 = nn.BatchNorm2d(out_ch)\n        self.conv2 = nn.Conv2d(out_ch, out_ch, 3, 1, 1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_ch)\n        self.se = SEBlock(out_ch, r=16)\n        self.skip = nn.Sequential()\n        if stride != 1 or in_ch != out_ch:\n            self.skip = nn.Sequential(nn.Conv2d(in_ch, out_ch, 1, stride, bias=False),\n                                      nn.BatchNorm2d(out_ch))\n    def forward(self, x):\n        y = F.relu(self.bn1(self.conv1(x)))\n        y = self.bn2(self.conv2(y))\n        y = self.se(y)\n        return F.relu(y + self.skip(x))\n\nclass Net(nn.Module):\n    def __init__(self, n_classes=10, width=2.0):\n        super().__init__()\n        w = lambda c: int(c*width)\n        self.stem = nn.Sequential(nn.Conv2d(3, w(64), 3, 1, 1, bias=False),\n                                  nn.BatchNorm2d(w(64)), nn.ReLU(inplace=True))\n        self.layer1 = self._make_layer(w(64),  w(64),  4, 1)\n        self.layer2 = self._make_layer(w(64),  w(128), 4, 2)\n        self.layer3 = self._make_layer(w(128), w(256), 4, 2)\n        self.pool = nn.AdaptiveAvgPool2d(1)\n        self.drop = nn.Dropout(0.2)\n        self.fc = nn.Linear(w(256), n_classes)\n        self._init_weights()\n    def _make_layer(self, in_ch, out_ch, n_blocks, stride):\n        layers = [ResBlock(in_ch, out_ch, stride)]\n        for _ in range(1, n_blocks): layers.append(ResBlock(out_ch, out_ch, 1))\n        return nn.Sequential(*layers)\n    def _init_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')\n            elif isinstance(m, nn.BatchNorm2d):\n                nn.init.constant_(m.weight, 1); nn.init.constant_(m.bias, 0)\n    def forward(self, x):\n        x = self.stem(x); x = self.layer1(x); x = self.layer2(x); x = self.layer3(x)\n        x = self.pool(x).flatten(1); x = self.drop(x); return self.fc(x)\n\n#  load best weights \ndef load_weights(model):\n    tried = []\n    for p in [\"best_model.pth\", \"best_model_live.pth\"]:\n        if os.path.exists(p):\n            model.load_state_dict(torch.load(p, map_location='cpu'))\n            print(\"loaded weights ->\", p); return True\n        tried.append(p)\n    # checkpoint best_state fallback\n    if os.path.exists(\"last_checkpoint.pth\"):\n        try:\n            ckpt = torch.load(\"last_checkpoint.pth\", map_location='cpu')\n            best_state = ckpt.get('best_state', None)\n            if best_state:\n                model.load_state_dict(best_state); print(\"loaded best_state from last_checkpoint.pth\"); return True\n        except Exception as e:\n            print(\"ckpt load failed:\", e)\n        tried.append(\"last_checkpoint.pth(best_state)\")\n    print(\"No weights found. Looked for:\", tried)\n    return False\n\n# ---------- helpers ----------\ndef _find_attached_test_folder():\n    base=\"/kaggle/input\"\n    try:\n        for d in os.listdir(base):\n            cand = os.path.join(base, d, \"test\")\n            if os.path.isdir(cand):\n                hits = glob.glob(os.path.join(cand,\"*.png\"))\n                if len(hits)>1000: return cand\n    except Exception:\n        pass\n    return None\n\ndef _list_7z_images(archive, cache=\"/kaggle/working/test_names.txt\"):\n    if os.path.exists(cache):\n        with open(cache,'r') as f: return [ln.strip() for ln in f if ln.strip()]\n    out = subprocess.check_output(['7z','l','-slt', archive], text=True)\n    names=[]\n    for ln in out.splitlines():\n        if ln.startswith('Path = '):\n            p = ln[7:]\n            if p.lower().endswith(('.png','.jpg','.jpeg')): names.append(p)\n    with open(cache,'w') as f: f.write('\\n'.join(names))\n    return names\n\ndef _extract_chunk_with_timeout(archive, names, outdir, threads=2, timeout_s=180):\n    if os.path.exists(outdir): shutil.rmtree(outdir)\n    os.makedirs(outdir, exist_ok=True)\n    lst = os.path.join(outdir, \"list.txt\")\n    with open(lst,'w') as f:\n        for n in names: f.write(n+'\\n')\n    cmd = ['7z','x','-y', f'-mmt={threads}', '-bb0','-bsp0','-bso0', f'-o{outdir}', archive, f'@{lst}']\n    try:\n        subprocess.run(cmd, check=True, timeout=timeout_s,\n                       stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)\n        return True\n    except subprocess.TimeoutExpired:\n        print(f\"[timeout] 7z took >{timeout_s}s; will retry smaller/fewer-threads\")\n        try:\n            # retry single-thread, longer timeout\n            subprocess.run(['7z','x','-y','-mmt=1','-bb0','-bsp0','-bso0', f'-o{outdir}', archive, f'@{lst}'],\n                           check=True, timeout=timeout_s*2,\n                           stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT)\n            return True\n        except Exception as e:\n            print(\"[fail] 7z retry failed:\", e)\n            return False\n\nclass _TestFiles(Dataset):\n    def __init__(self, paths, tfm): self.paths, self.tfm = paths, tfm\n    def __len__(self): return len(self.paths)\n    def __getitem__(self, i):\n        p = self.paths[i]\n        img = Image.open(p).convert('RGB')\n        return self.tfm(img), os.path.splitext(os.path.basename(p))[0]\n\ndef _already_done_ranges():\n    done = []\n    for p in glob.glob(os.path.join(PART_DIR, \"part_*.csv\")):\n        base = os.path.basename(p)\n        try:\n            a,b = base.replace(\"part_\",\"\").replace(\".csv\",\"\").split(\"_\")\n            done.append((int(a), int(b)))\n        except: pass\n    return sorted(done)\n\ndef _write_part(ids, labels, start_idx, end_idx):\n    df = pd.DataFrame({'id': ids, 'label': labels})\n    try: df['id'] = df['id'].astype(int); df = df.sort_values('id')\n    except: pass\n    outp = os.path.join(PART_DIR, f\"part_{start_idx}_{end_idx}.csv\")\n    df.to_csv(outp, index=False)\n    print(\"[part] saved ->\", outp)\n\ndef _merge_parts(out_file=\"/kaggle/working/submission.csv\"):\n    parts = sorted(glob.glob(os.path.join(PART_DIR, \"part_*.csv\")))\n    if not parts:\n        print(\"[merge] no parts found\"); return None\n    dfs = [pd.read_csv(p) for p in parts]\n    df = pd.concat(dfs, ignore_index=True)\n    try: df['id'] = df['id'].astype(int); df = df.sort_values('id')\n    except: pass\n    df.to_csv(out_file, index=False)\n    print(\"saved ->\", out_file, \"| abs:\", os.path.abspath(out_file))\n    return out_file\n\n#  predict \n@torch.no_grad()\ndef predict_folder(model, test_dir, out_file=\"/kaggle/working/submission.csv\", bs=1536, tta=2):\n    tfm = T.Compose([T.ToTensor(), T.Normalize(MEAN, STD)])\n    paths = sorted(glob.glob(os.path.join(test_dir, \"*.png\")), key=lambda p: int(os.path.splitext(os.path.basename(p))[0]))\n    print(f\"[folder] images: {len(paths)}\")\n    ds = _TestFiles(paths, tfm)\n    loader = DataLoader(ds, batch_size=bs, shuffle=False, num_workers=2, pin_memory=True)\n    ids_all, preds_all = [], []\n    model.eval()\n    for x, ids in tqdm(loader, desc='predict (folder)'):\n        x = x.to(device, non_blocking=True)\n        out = model(x)\n        if tta>1: out = (out + model(torch.flip(x,[3]))) / 2.0\n        preds = out.argmax(1).cpu().numpy()\n        ids_all += list(ids); preds_all += list(preds)\n    labels = [CLASSES[i] for i in preds_all]\n    df = pd.DataFrame({'id': ids_all, 'label': labels})\n    try: df['id'] = df['id'].astype(int); df = df.sort_values('id')\n    except: pass\n    df.to_csv(out_file, index=False)\n    print(\"saved ->\", out_file, \"| abs:\", os.path.abspath(out_file))\n    return out_file\n\n@torch.no_grad()\ndef predict_7z(model, archive, out_file=\"/kaggle/working/submission.csv\",\n               base_chunk=6000, bs=1536, tta=2):\n    tfm = T.Compose([T.ToTensor(), T.Normalize(MEAN, STD)])\n    names = _list_7z_images(archive)\n    print(f\"[7z] entries: {len(names)}\")\n    model.eval()\n\n    # resume support\n    done_ranges = _already_done_ranges()\n    covered = set()\n    for a,b in done_ranges: covered.update(range(a,b))\n\n    s = 0\n    while s < len(names):\n        if s in covered:\n            # skip entire prior window if fully covered\n            s += base_chunk\n            continue\n\n        # dynamically shrink chunk if needed\n        for cur_chunk in [base_chunk, 4000, 3000, 2000, 1000, 500]:\n            start_idx = s\n            end_idx = min(s+cur_chunk, len(names))\n            # if this span already done as a whole, skip\n            if all(i in covered for i in range(start_idx, end_idx)):\n                s = end_idx\n                break\n\n            cur = os.path.join(WORK, \"test_cur\")\n            ok = _extract_chunk_with_timeout(archive, names[start_idx:end_idx], cur, threads=2, timeout_s=180)\n            if not ok:\n                print(f\"[retry] extraction failed for {start_idx}-{end_idx} at chunk={cur_chunk}, trying smaller…\")\n                if cur_chunk == 500:\n                    raise RuntimeError(\"Extraction keeps timing out; try attaching a dataset with extracted /test folder.\")\n                continue\n\n            paths = [os.path.join(cur, n) for n in names[start_idx:end_idx]]\n            # keep numeric order\n            try:\n                paths.sort(key=lambda p: int(os.path.splitext(os.path.basename(p))[0]))\n            except:\n                paths.sort()\n\n            ds = _TestFiles(paths, tfm)\n            loader = DataLoader(ds, batch_size=bs, shuffle=False, num_workers=2, pin_memory=True)\n\n            ids_all, preds_all = [], []\n            for x, ids in tqdm(loader, desc=f'predict [{start_idx}-{end_idx})'):\n                x = x.to(device, non_blocking=True)\n                out = model(x)\n                if tta>1: out = (out + model(torch.flip(x,[3]))) / 2.0\n                preds = out.argmax(1).cpu().numpy()\n                ids_all += list(ids); preds_all += list(preds)\n\n            labels = [CLASSES[i] for i in preds_all]\n            _write_part(ids_all, labels, start_idx, end_idx)\n            shutil.rmtree(cur, ignore_errors=True)\n            s = end_idx\n            break  # move to next base window\n\n    # merge all parts\n    return _merge_parts(out_file)\n\n#  run \nmodel = Net(n_classes=10, width=2.0).to(device)\nok = load_weights(model)\nassert ok, \"No saved weights found. Expected best_model.pth / best_model_live.pth or a checkpoint with best_state.\"\n\ntest_dir = _find_attached_test_folder() or TEST_DIR_FALLBACK\nif os.path.isdir(test_dir) and len(glob.glob(os.path.join(test_dir, \"*.png\"))) > 1000:\n    print(\"Using extracted test folder:\", test_dir)\n    out_csv = predict_folder(model, test_dir, out_file=\"/kaggle/working/submission.csv\", bs=1536, tta=2)\nelse:\n    print(\"Streaming from test.7z with resume/timeout…\")\n    out_csv = predict_7z(model, TEST_7Z, out_file=\"/kaggle/working/submission.csv\",\n                         base_chunk=6000, bs=1536, tta=2)\n\nprint(\"Done. Exists?\", os.path.exists(out_csv))\ntry:\n    print(pd.read_csv(out_csv).head())\nexcept Exception as e:\n    print(\"read head failed:\", e)\nprint(\"➡️  Upload /kaggle/working/submission.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-15T11:55:36.172053Z","iopub.execute_input":"2025-10-15T11:55:36.172533Z","iopub.status.idle":"2025-10-15T12:48:45.831152Z","shell.execute_reply.started":"2025-10-15T11:55:36.172507Z","shell.execute_reply":"2025-10-15T12:48:45.830142Z"}},"outputs":[],"execution_count":null}]}