{"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":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip -q install py7zr > /dev/null\n\nimport os, sys, math, time, glob, random\nfrom pathlib import Path\nimport pandas as pd\nimport numpy as np\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader, Subset\nfrom torchvision import transforms, models, datasets\n\nfrom torch.amp import GradScaler, autocast\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\n\n# ---------- config ----------\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nSEED = 777\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED); torch.cuda.manual_seed_all(SEED)\ntorch.backends.cudnn.benchmark = False; torch.backends.cudnn.deterministic = True\n\nCIFAR_MEAN = (0.4914, 0.4822, 0.4465)\nCIFAR_STD  = (0.2470, 0.2435, 0.2616)\nCLASSES = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']\nNUM_CLASSES = 10\n\nEPOCHS = int(os.environ.get(\"EPOCHS\", \"120\"))\nBATCH_SIZE = int(os.environ.get(\"BATCH_SIZE\", \"128\"))\nLR = float(os.environ.get(\"LR\", \"0.1\"))\nWEIGHT_DECAY = float(os.environ.get(\"WD\", \"5e-4\"))\nLABEL_SMOOTH = float(os.environ.get(\"LS\", \"0.05\"))\nWARMUP_EPOCHS = int(os.environ.get(\"WARMUP\", \"5\"))\nNUM_WORKERS = int(os.environ.get(\"WORKERS\", \"2\"))\nUSE_TTA = True  #\n\nDATA_DIR_KAGGLE = Path(\"/kaggle/input/cifar-10\")\nHAVE_KAGGLE_DATA = DATA_DIR_KAGGLE.exists()\n\n# ---------- model (random init, no pretrained) ----------\ndef resnet18_cifar10(num_classes=NUM_CLASSES):\n    m = models.resnet18(weights=None)  # <-- NO PRETRAINED WEIGHTS\n    m.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)\n    m.maxpool = nn.Identity()\n    m.fc = nn.Linear(512, num_classes)\n    # Kaiming/random init for safety\n    for mod in m.modules():\n        if isinstance(mod, nn.Conv2d):\n            nn.init.kaiming_normal_(mod.weight, mode='fan_out', nonlinearity='relu')\n        elif isinstance(mod, nn.BatchNorm2d):\n            nn.init.constant_(mod.weight, 1); nn.init.constant_(mod.bias, 0)\n        elif isinstance(mod, nn.Linear):\n            nn.init.normal_(mod.weight, 0, 0.01); nn.init.constant_(mod.bias, 0)\n    return m\n\n# ---------- datasets ----------\ntrain_tfms = transforms.Compose([\n    transforms.RandomCrop(32, padding=4),\n    transforms.RandomHorizontalFlip(),\n    transforms.AutoAugment(transforms.AutoAugmentPolicy.CIFAR10),\n    transforms.ToTensor(),\n    transforms.Normalize(CIFAR_MEAN, CIFAR_STD),\n    transforms.RandomErasing(p=0.2)\n])\ntest_tfms = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(CIFAR_MEAN, CIFAR_STD),\n])\n\ndef load_official_cifar10_or_kaggle_train():\n    try:\n        train_full = datasets.CIFAR10(root=\"./data\", train=True, download=True, transform=train_tfms)\n        n_val = 5000\n        n_train = len(train_full) - n_val\n        g = torch.Generator().manual_seed(SEED)\n        train_set, val_set = torch.utils.data.random_split(train_full, [n_train, n_val], generator=g)\n        return train_set, val_set, \"official\"\n    except Exception as e:\n        # Fallback: Kaggle train.7z + trainLabels.csv\n        assert HAVE_KAGGLE_DATA, \"Kaggle data not found and official download failed.\"\n        # Extract train if needed\n        train_dir = Path(\"./train\")\n        if not (train_dir.exists() and len(list(train_dir.glob(\"*\"))) >= 50000):\n            import py7zr\n            src = DATA_DIR_KAGGLE / \"train.7z\"\n            assert src.exists(), f\"Missing {src}\"\n            print(\"Extracting\", src)\n            with py7zr.SevenZipFile(src, mode='r') as z:\n                z.extractall(path='./')\n        # Build dataset from train dir + CSV\n        labels_csv = DATA_DIR_KAGGLE / \"trainLabels.csv\"\n        assert labels_csv.exists(), f\"Missing {labels_csv}\"\n        df = pd.read_csv(labels_csv)  # id,label\n        id_to_label = dict(zip(df['id'].astype(int).values, df['label'].values))\n        class_to_idx = {c:i for i,c in enumerate(CLASSES)}\n\n        class KaggleTrain(Dataset):\n            def __init__(self, ids, tfm):\n                self.ids = ids\n                self.tfm = tfm\n            def __len__(self): return len(self.ids)\n            def __getitem__(self, i):\n                idx = self.ids[i]\n                p = Path(\"train\") / f\"{idx}.png\"\n                img = Image.open(p).convert(\"RGB\")\n                x = self.tfm(img)\n                y = class_to_idx[id_to_label[idx]]\n                return x, y\n\n        all_ids = sorted(id_to_label.keys())\n        # 45k train / 5k val split\n        rng = np.random.default_rng(SEED)\n        rng.shuffle(all_ids)\n        val_ids = all_ids[:5000]\n        train_ids = all_ids[5000:]\n        train_set = KaggleTrain(train_ids, train_tfms)\n        val_set   = KaggleTrain(val_ids,   test_tfms) \n        return train_set, val_set, \"kaggle\"\n\ndef optional_official_test_loader():\n    \"\"\"Try to get official CIFAR-10 test; return loader or None if unavailable.\"\"\"\n    try:\n        cifar10_test = datasets.CIFAR10(root=\"./data\", train=False, download=True, transform=test_tfms)\n        return DataLoader(cifar10_test, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True)\n    except Exception:\n        return None\n\n# ---------- training utils ----------\ndef accuracy(logits, targets):\n    return (logits.argmax(1) == targets).float().mean().item()\n\ndef train_one_epoch(model, loader, criterion, optimizer, scaler, epoch):\n    model.train()\n    # LR warmup\n    if epoch < WARMUP_EPOCHS:\n        warmup_lr = LR * float(epoch + 1) / WARMUP_EPOCHS\n        for g in optimizer.param_groups: g['lr'] = warmup_lr\n\n    total_loss = total_acc = n = 0\n    for images, targets in loader:\n        images, targets = images.to(DEVICE, non_blocking=True), targets.to(DEVICE, non_blocking=True)\n        optimizer.zero_grad(set_to_none=True)\n        with autocast('cuda', enabled=(DEVICE=='cuda')):\n            logits = model(images)\n            loss = criterion(logits, targets)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        total_loss += loss.item() * images.size(0)\n        total_acc  += (logits.argmax(1) == targets).float().sum().item()\n        n += images.size(0)\n    return total_loss / n, total_acc / n\n\n@torch.no_grad()\ndef evaluate(model, loader, criterion):\n    model.eval()\n    total_loss = total_acc = n = 0\n    for images, targets in loader:\n        images, targets = images.to(DEVICE, non_blocking=True), targets.to(DEVICE, non_blocking=True)\n        with autocast('cuda', enabled=(DEVICE=='cuda')):\n            logits = model(images)\n            loss = criterion(logits, targets)\n        total_loss += loss.item() * images.size(0)\n        total_acc  += (logits.argmax(1) == targets).float().sum().item()\n        n += images.size(0)\n    return total_loss / n, total_acc / n\n\n# ---------- load datasets ----------\ntrain_set, val_set, train_source = load_official_cifar10_or_kaggle_train()\ntrain_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True,  num_workers=NUM_WORKERS, pin_memory=True)\nval_loader   = DataLoader(val_set,   batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True)\nprint(f\"Training source: {train_source} | Train batches: {len(train_loader)} | Val batches: {len(val_loader)}\")\n\n# Optional official CIFAR-10 test (proxy for Kaggle score)\ntest_loader_official = optional_official_test_loader()\nif test_loader_official is None:\n    print(\"Official CIFAR-10 test not available (likely no internet). We'll skip offline test accuracy.\")\n\n# ---------- init model/optimizer ----------\nmodel = resnet18_cifar10().to(DEVICE)\ncriterion = nn.CrossEntropyLoss(label_smoothing=LABEL_SMOOTH)\noptimizer = optim.SGD(model.parameters(), lr=LR, momentum=0.9, weight_decay=WEIGHT_DECAY, nesterov=True)\nscheduler = CosineAnnealingLR(optimizer, T_max=EPOCHS)\nscaler = GradScaler('cuda', enabled=(DEVICE=='cuda'))\n\n# ---------- train ----------\nbest_val_acc = 0.0\nPath(\"checkpoints\").mkdir(exist_ok=True)\n\nfor epoch in range(EPOCHS):\n    t0 = time.time()\n    tr_loss, tr_acc = train_one_epoch(model, train_loader, criterion, optimizer, scaler, epoch)\n    va_loss, va_acc = evaluate(model, val_loader, criterion)\n    if epoch >= WARMUP_EPOCHS:\n        scheduler.step()\n    dt = time.time() - t0\n    print(f\"Epoch {epoch+1:03d}/{EPOCHS} | train loss {tr_loss:.4f} acc {tr_acc:.4f} | val loss {va_loss:.4f} acc {va_acc:.4f} | {dt:.1f}s\")\n\n    if va_acc > best_val_acc:\n        best_val_acc = va_acc\n        torch.save({\"model\": model.state_dict(), \"val_acc\": va_acc}, \"checkpoints/best.pth\")\n\n# ---------- evaluate on official CIFAR-10 test if available ----------\nif test_loader_official is not None:\n    ckpt = torch.load(\"checkpoints/best.pth\", map_location=DEVICE)\n    model.load_state_dict(ckpt[\"model\"])\n    test_loss, test_acc = evaluate(model, test_loader_official, criterion)\n    print(f\"Official CIFAR-10 test accuracy (proxy for Kaggle): {test_acc:.4f}\")\nelse:\n    print(\"Skipped official test accuracy.\")\n\n# ---------- extract Kaggle test.7z ----------\ndef ensure_test_extracted():\n    if Path(\"test\").exists() and len(list(Path(\"test\").glob(\"*\"))) >= 300000:\n        print(\"Found existing ./test with\", len(list(Path(\"test\").glob(\"*\"))), \"files\"); return\n    # Prefer Kaggle path\n    candidates = [\n        DATA_DIR_KAGGLE / \"test.7z\",\n        Path(\"./test.7z\"),\n    ]\n    src = next((p for p in candidates if p.exists()), None)\n    if src is None:\n        raise FileNotFoundError(\"Couldn't find test.7z. On Kaggle it is /kaggle/input/cifar-10/test.7z.\")\n    import py7zr\n    print(\"Extracting:\", src)\n    with py7zr.SevenZipFile(src, mode='r') as z:\n        z.extractall(path='./')\n    nfiles = len(glob.glob('test/*'))\n    print(\"Files in ./test:\", nfiles)\n    assert nfiles == 300000, f\"Expected 300,000 test images, got {nfiles}\"\n\nensure_test_extracted()\n\n# ---------- test loader over ./test ----------\nclass TestFolder(Dataset):\n    def __init__(self, folder, tfm):\n        self.paths = sorted([p for p in Path(folder).glob(\"*\") if p.suffix.lower() in (\".png\",\".jpg\",\".jpeg\")],\n                            key=lambda p: int(p.stem))\n        self.tfm = tfm\n    def __len__(self): return len(self.paths)\n    def __getitem__(self, idx):\n        p = self.paths[idx]\n        img = Image.open(p).convert(\"RGB\")\n        return self.tfm(img), int(p.stem)\n\ntest_ds = TestFolder(\"test\", test_tfms)\ntest_loader = DataLoader(test_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True)\n\n# ---------- reload best and predict (TTA) ----------\nckpt = torch.load(\"checkpoints/best.pth\", map_location=DEVICE)\nmodel.load_state_dict(ckpt[\"model\"])\nmodel.eval()\n\n@torch.no_grad()\ndef predict(loader, tta=False):\n    ids, preds = [], []\n    for images, file_ids in loader:\n        images = images.to(DEVICE, non_blocking=True)\n        with autocast('cuda', enabled=(DEVICE=='cuda')):\n            logits = model(images)\n            if tta:\n                logits_flip = model(torch.flip(images, dims=[3]))\n                logits = (logits + logits_flip) / 2\n            y = logits.argmax(1).cpu().numpy()\n        ids.extend(file_ids.numpy().tolist())\n        preds.extend(y.tolist())\n    return ids, preds\n\nprint(\"Running inference...\", \"(TTA on)\" if USE_TTA else \"(no TTA)\")\nids, pred_idx = predict(test_loader, tta=USE_TTA)\nlabels = [CLASSES[i] for i in pred_idx]\n\n# ---------- submission ----------\nsub = pd.DataFrame({\"id\": ids, \"label\": labels}).sort_values(\"id\")\nsub.to_csv(\"submission.csv\", index=False)\nprint(\"Saved submission.csv:\", sub.shape)\nprint(sub.head(), \"\\n...\\n\", sub.tail())","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-23T07:52:55.117744Z","iopub.execute_input":"2025-09-23T07:52:55.117987Z","iopub.status.idle":"2025-09-23T08:50:44.955554Z","shell.execute_reply.started":"2025-09-23T07:52:55.117967Z","shell.execute_reply":"2025-09-23T08:50:44.954742Z"}},"outputs":[],"execution_count":null}]}