{"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":31091,"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},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==============================================================\n# CIFAR-10 (Kaggle) — ResNet18 from scratch (no pre-trained)\n# Trains 100 epochs, evaluates, and writes /kaggle/working/submission.csv\n# Framework: PyTorch\n# ==============================================================\n\nimport os, re, glob, math, time, csv, random, sys\nfrom pathlib import Path\nimport numpy as np\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\nimport torchvision\nimport torchvision.transforms as T\n\n# ----------------------------\n# Repro & device\n# ----------------------------\nSEED = 1337\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED)\ntorch.backends.cudnn.benchmark = True\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(\"Device:\", DEVICE)\n\n# ----------------------------\n# Config\n# ----------------------------\nEPOCHS = 100\nBATCH_SIZE = 128\nBASE_LR = 0.1\nWEIGHT_DECAY = 5e-4\nMOMENTUM = 0.9\nLABEL_SMOOTH = 0.1\nNUM_WORKERS = 2\n\nCIFAR10_CLASSES = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']\n\n# ----------------------------\n# Data (official CIFAR-10 train/test for offline validation)\n# ----------------------------\nMEAN = (0.4914, 0.4822, 0.4465)\nSTD  = (0.2470, 0.2435, 0.2616)\n\ntrain_tfms = T.Compose([\n    T.RandomCrop(32, padding=4),\n    T.RandomHorizontalFlip(),\n    T.TrivialAugmentWide(),\n    T.ToTensor(),\n    T.Normalize(MEAN, STD),\n])\n\ntest_tfms = T.Compose([\n    T.ToTensor(),\n    T.Normalize(MEAN, STD),\n])\n\ntrain_set = torchvision.datasets.CIFAR10(root=\"./data\", train=True,  download=True, transform=train_tfms)\ntest_official = torchvision.datasets.CIFAR10(root=\"./data\", train=False, download=True, transform=test_tfms)\n\ntrain_loader = DataLoader(train_set, batch_size=BATCH_SIZE, shuffle=True,  num_workers=NUM_WORKERS, pin_memory=True)\nval_loader   = DataLoader(test_official, batch_size=256, shuffle=False, num_workers=NUM_WORKERS, pin_memory=True)\n\n# ----------------------------\n# Model (ResNet18 adapted to 32x32, no pretrain)\n# ----------------------------\ndef resnet18_cifar(num_classes=10):\n    model = torchvision.models.resnet18(weights=None)  # NO PRETRAIN\n    model.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)\n    model.maxpool = nn.Identity()\n    model.fc = nn.Linear(model.fc.in_features, num_classes)\n    return model\n\nmodel = resnet18_cifar().to(DEVICE)\n\n# ----------------------------\n# Optimizer / LR / Loss (Label smoothing)\n# ----------------------------\noptimizer = torch.optim.SGD(model.parameters(), lr=BASE_LR, momentum=MOMENTUM,\n                            weight_decay=WEIGHT_DECAY, nesterov=True)\n\ndef cosine_lr(step, total_steps, base_lr, final_lr=0.0):\n    step = min(step, total_steps)\n    cos = (1 + math.cos(math.pi * step / total_steps)) / 2\n    return final_lr + (base_lr - final_lr) * cos\n\nclass SmoothCE(nn.Module):\n    def __init__(self, smoothing=0.1): super().__init__(); self.smoothing = smoothing\n    def forward(self, logits, targets):\n        n = logits.size(-1)\n        log_probs = F.log_softmax(logits, dim=-1)\n        with torch.no_grad():\n            true_dist = torch.zeros_like(log_probs)\n            true_dist.fill_(self.smoothing / (n - 1))\n            true_dist.scatter_(1, targets.unsqueeze(1), 1 - self.smoothing)\n        return torch.mean(torch.sum(-true_dist * log_probs, dim=-1))\n\ncriterion = SmoothCE(LABEL_SMOOTH)\n\n# New AMP API\nscaler = torch.amp.GradScaler(\"cuda\", enabled=(DEVICE==\"cuda\"))\n\n# ----------------------------\n# Train / Evaluate\n# ----------------------------\ndef evaluate(loader):\n    model.eval()\n    correct, total, loss_sum = 0, 0, 0.0\n    with torch.no_grad(), torch.amp.autocast(\"cuda\", enabled=(DEVICE==\"cuda\")):\n        for x, y in loader:\n            x, y = x.to(DEVICE), y.to(DEVICE)\n            logits = model(x)\n            loss = criterion(logits, y)\n            pred = logits.argmax(1)\n            correct += (pred == y).sum().item()\n            total += y.size(0)\n            loss_sum += loss.item() * y.size(0)\n    return loss_sum/total, correct/total\n\nbest_acc, global_step = 0.0, 0\ntotal_steps = EPOCHS * len(train_loader)\n\nfor epoch in range(1, EPOCHS+1):\n    model.train()\n    run_loss = run_correct = run_total = 0\n\n    for x, y in train_loader:\n        x, y = x.to(DEVICE), y.to(DEVICE)\n\n        lr = cosine_lr(global_step, total_steps, BASE_LR)\n        for pg in optimizer.param_groups: pg['lr'] = lr\n\n        optimizer.zero_grad(set_to_none=True)\n        with torch.amp.autocast(\"cuda\", enabled=(DEVICE==\"cuda\")):\n            logits = model(x)\n            loss = criterion(logits, y)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        run_loss += loss.item() * y.size(0)\n        run_correct += (logits.argmax(1) == y).sum().item()\n        run_total += y.size(0)\n        global_step += 1\n\n    train_loss = run_loss / run_total\n    train_acc  = run_correct / run_total\n    val_loss, val_acc = evaluate(val_loader)\n\n    print(f\"[Epoch {epoch:03d}] train_loss={train_loss:.4f} train_acc={train_acc:.4f} | \"\n          f\"val_loss={val_loss:.4f} val_acc={val_acc:.4f}\")\n\n    if val_acc > best_acc:\n        best_acc = val_acc\n        torch.save({\"model\": model.state_dict()}, \"best_resnet18_cifar10.pth\")\n\nprint(f\"Best est. Kaggle score (official test acc): {best_acc:.4f}\")\n\n# Reload best\nckpt = torch.load(\"best_resnet18_cifar10.pth\", map_location=DEVICE)\nmodel.load_state_dict(ckpt[\"model\"])\nmodel.eval().to(DEVICE)\n\n# ==============================================================\n# Kaggle test-set → predictions → /kaggle/working/submission.csv\n# ==============================================================\n\nimport subprocess\n\nWORK_DIR = Path(\"/kaggle/working\")\nINPUT_DIR = Path(\"/kaggle/input\")\nTEST_DIR  = Path(\"./test\")  # we'll normalize to have images here\n\ndef find_test7z():\n    cands = list(INPUT_DIR.rglob(\"test.7z\"))\n    return cands[0] if cands else None\n\n# Extract if needed (pure Python, no apt)\nif not TEST_DIR.exists() or not any(TEST_DIR.iterdir()):\n    TEST_DIR.mkdir(parents=True, exist_ok=True)\n    test7z = find_test7z()\n    if test7z is None:\n        raise FileNotFoundError(\"Could not find test.7z under /kaggle/input. \"\n                                \"Attach the competition dataset to your notebook.\")\n    try:\n        import py7zr\n    except ImportError:\n        print(\"Installing py7zr...\")\n        subprocess.check_call([sys.executable, \"-m\", \"pip\", \"install\", \"-q\", \"py7zr\"])\n        import py7zr\n\n    print(f\"Extracting {test7z} -> {TEST_DIR} ...\")\n    with py7zr.SevenZipFile(str(test7z), mode='r') as z:\n        z.extractall(path=str(TEST_DIR))\n    print(\"Done extracting.\")\n\n# Some archives create ./test/test; normalize\ninner = TEST_DIR / \"test\"\nif inner.exists() and any(inner.iterdir()):\n    TEST_DIR = inner\n\n# Build file list\ndef list_test_files(d: Path):\n    files = []\n    for pat in (\"*.png\",\"*.jpg\",\"*.jpeg\"):\n        files.extend(sorted(d.glob(pat)))\n    return files\n\nfiles = list_test_files(TEST_DIR)\nif len(files) == 0:\n    raise RuntimeError(f\"No images found under {TEST_DIR}. Check extraction.\")\nprint(\"Found test images:\", len(files), \"Sample:\", [p.name for p in files[:5]])\n\n# Filename → numeric id\nid_regex = re.compile(r\"(\\d+)\")\ndef num_id(p: Path):\n    m = id_regex.search(p.stem)\n    if not m:\n        raise ValueError(f\"No numeric id in filename: {p.name}\")\n    return int(m.group(1))\n\n# Dataset / loader for Kaggle test\nclass KaggleTest(Dataset):\n    def __init__(self, paths, transform=None):\n        self.paths = sorted(paths, key=num_id)\n        self.t = transform\n    def __len__(self): return len(self.paths)\n    def __getitem__(self, i):\n        p = self.paths[i]\n        x = Image.open(p).convert(\"RGB\")\n        return (self.t(x) if self.t else x), num_id(p)\n\nkaggle_loader = DataLoader(KaggleTest(files, test_tfms),\n                           batch_size=256, shuffle=False,\n                           num_workers=NUM_WORKERS, pin_memory=True)\n\n# Predict (AMP new API)\nrows = []\nwith torch.no_grad(), torch.amp.autocast(\"cuda\", enabled=(DEVICE==\"cuda\")):\n    for x, ids in kaggle_loader:\n        x = x.to(DEVICE)\n        logits = model(x)\n        preds = logits.argmax(1).cpu().numpy()\n        rows.extend([(int(ids[k]), CIFAR10_CLASSES[int(preds[k])]) for k in range(len(ids))])\n\n# Write CSV in working dir\nrows.sort(key=lambda t: t[0])\nsub_path = WORK_DIR / \"submission.csv\"\nwith open(sub_path, \"w\", newline=\"\") as f:\n    w = csv.writer(f); w.writerow([\"id\",\"label\"]); w.writerows(rows)\n\nprint(f\"Wrote {sub_path} with {len(rows)} rows.\")\nassert len(rows) == len(files), \"Row count mismatch (CSV rows != #images).\"\n\n# ----------------------------\n# Short printable report snippet\n# ----------------------------\nprint(\"\\n=== REPORT SNIPPET ===\")\nprint(\"Title: CIFAR-10 Classification — ResNet-18 (from scratch)\")\nprint(\"Framework: PyTorch (assignment allows Keras/PyTorch/TensorFlow; I used PyTorch).\")\nprint(\"Training: 100 epochs, SGD(m=0.9, wd=5e-4), cosine LR 0.1→0, label smoothing 0.1, batch 128, TrivialAugment.\")\nprint(f\"Validation (official CIFAR-10 test) accuracy: ~{best_acc:.4f} (proxy for Kaggle score).\")\nprint(\"Submission: Created /kaggle/working/submission.csv with id,label for the 300k Kaggle test images.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-21T13:12:19.463623Z","iopub.execute_input":"2025-09-21T13:12:19.463830Z","iopub.status.idle":"2025-09-21T13:54:46.829972Z","shell.execute_reply.started":"2025-09-21T13:12:19.463811Z","shell.execute_reply":"2025-09-21T13:54:46.828871Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CIFAR-10 Residual Network from basic layers (no torchvision models)\n# - Residual connections implemented explicitly in ResidualBlock.\n# - Built from Conv2d/BatchNorm2d/ReLU/AvgPool/Linear — no ResNet18 import.\n# - Randomly initialized weights (default PyTorch init).\n\nimport os, math, time, argparse\nfrom typing import Tuple\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader\nimport torchvision\nimport torchvision.transforms as T\n\n# -----------------------------\n# Model\n# -----------------------------\n\nclass ResidualBlock(nn.Module):\n    \"\"\"Basic residual block: conv3x3 -> BN -> ReLU -> conv3x3 -> BN, with skip.\n       If in/out channels differ or stride>1, uses 1x1 projection for skip.\n    \"\"\"\n    def __init__(self, in_ch, out_ch, stride=1):\n        super().__init__()\n        self.conv1 = nn.Conv2d(in_ch, out_ch, kernel_size=3, stride=stride, padding=1, bias=False)\n        self.bn1   = nn.BatchNorm2d(out_ch)\n        self.conv2 = nn.Conv2d(out_ch, out_ch, kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn2   = nn.BatchNorm2d(out_ch)\n        self.proj  = None\n        if stride != 1 or in_ch != out_ch:\n            self.proj = nn.Sequential(\n                nn.Conv2d(in_ch, out_ch, kernel_size=1, stride=stride, bias=False),\n                nn.BatchNorm2d(out_ch)\n            )\n\n    def forward(self, x):\n        identity = x\n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = F.relu(out, inplace=True)\n        out = self.conv2(out)\n        out = self.bn2(out)\n        if self.proj is not None:\n            identity = self.proj(x)\n        out += identity\n        out = F.relu(out, inplace=True)\n        return out\n\n\nclass SimpleResNet(nn.Module):\n    \"\"\"A small ResNet-style network for CIFAR-10 (32x32).\n       Stem: 3x3 conv (no maxpool). Stages: [2,2,2] blocks with channels [64,128,256].\n       Global average pool -> Linear(256,10).\n    \"\"\"\n    def __init__(self, num_classes=10):\n        super().__init__()\n        self.stem = nn.Sequential(\n            nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False),\n            nn.BatchNorm2d(64),\n            nn.ReLU(inplace=True),\n        )\n        self.stage1 = nn.Sequential(\n            ResidualBlock(64, 64, stride=1),\n            ResidualBlock(64, 64, stride=1),\n        )\n        self.stage2 = nn.Sequential(\n            ResidualBlock(64, 128, stride=2),  # downsample to 16x16\n            ResidualBlock(128, 128, stride=1),\n        )\n        self.stage3 = nn.Sequential(\n            ResidualBlock(128, 256, stride=2), # downsample to 8x8\n            ResidualBlock(256, 256, stride=1),\n        )\n        self.head = nn.Linear(256, num_classes)\n\n        # Weight init (Kaiming for convs, 1 for BN)\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.0)\n                nn.init.constant_(m.bias, 0.0)\n            elif isinstance(m, nn.Linear):\n                nn.init.kaiming_uniform_(m.weight, a=math.sqrt(5))\n                if m.bias is not None:\n                    fan_in, _ = nn.init._calculate_fan_in_and_fan_out(m.weight)\n                    bound = 1 / math.sqrt(fan_in)\n                    nn.init.uniform_(m.bias, -bound, bound)\n\n    def forward(self, x):\n        x = self.stem(x)\n        x = self.stage1(x)\n        x = self.stage2(x)\n        x = self.stage3(x)\n        # global average pooling\n        x = F.adaptive_avg_pool2d(x, (1,1))\n        x = torch.flatten(x, 1)\n        x = self.head(x)\n        return x\n\n# -----------------------------\n# Training / Eval\n# -----------------------------\n\ndef get_loaders(data_root: str, batch: int = 128, workers: int = 2):\n    # CIFAR-10 mean/std\n    mean = (0.4914, 0.4822, 0.4465)\n    std  = (0.2470, 0.2435, 0.2616)\n    train_tfms = T.Compose([\n        T.RandomCrop(32, padding=4),\n        T.RandomHorizontalFlip(),\n        T.TrivialAugmentWide(),\n        T.ToTensor(),\n        T.Normalize(mean, std),\n    ])\n    test_tfms = T.Compose([T.ToTensor(), T.Normalize(mean, std)])\n    trainset = torchvision.datasets.CIFAR10(root=data_root, train=True, download=True, transform=train_tfms)\n    testset  = torchvision.datasets.CIFAR10(root=data_root, train=False, download=True, transform=test_tfms)\n    train_loader = DataLoader(trainset, batch_size=batch, shuffle=True, num_workers=workers, pin_memory=True)\n    test_loader  = DataLoader(testset,  batch_size=batch, shuffle=False, num_workers=workers, pin_memory=True)\n    return train_loader, test_loader\n\ndef accuracy(logits, targets):\n    preds = logits.argmax(1)\n    return (preds == targets).float().mean().item()\n\ndef train_one_epoch(model, loader, opt, scaler, loss_fn, device):\n    model.train()\n    total_loss, total_acc, n = 0.0, 0.0, 0\n    for images, targets in loader:\n        images, targets = images.to(device), targets.to(device)\n        opt.zero_grad(set_to_none=True)\n        with torch.cuda.amp.autocast(enabled=(device.type==\"cuda\")):\n            logits = model(images)\n            loss = loss_fn(logits, targets)\n        scaler.scale(loss).backward()\n        scaler.step(opt)\n        scaler.update()\n        bs = images.size(0)\n        total_loss += loss.item() * bs\n        total_acc  += accuracy(logits, targets) * bs\n        n += bs\n    return total_loss / n, total_acc / n\n\n@torch.no_grad()\ndef evaluate(model, loader, loss_fn, device):\n    model.eval()\n    total_loss, total_acc, n = 0.0, 0.0, 0\n    for images, targets in loader:\n        images, targets = images.to(device), targets.to(device)\n        logits = model(images)\n        loss = loss_fn(logits, targets)\n        bs = images.size(0)\n        total_loss += loss.item() * bs\n        total_acc  += accuracy(logits, targets) * bs\n        n += bs\n    return total_loss / n, total_acc / n\n\ndef main():\n    parser = argparse.ArgumentParser()\n    parser.add_argument(\"--data_root\", type=str, default=\"./data\")\n    parser.add_argument(\"--epochs\", type=int, default=100)\n    parser.add_argument(\"--batch\", type=int, default=128)\n    parser.add_argument(\"--lr\", type=float, default=0.1)\n    parser.add_argument(\"--wd\", type=float, default=5e-4)\n    parser.add_argument(\"--workers\", type=int, default=2)\n    parser.add_argument(\"--label_smoothing\", type=float, default=0.1)\n    \n    # use parse_known_args instead of parse_args\n    args, _ = parser.parse_known_args()\n\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    print(f\"Device: {device}\")\n\n    train_loader, test_loader = get_loaders(args.data_root, args.batch, args.workers)\n\n    model = SimpleResNet(num_classes=10).to(device)\n    print(model)\n    total_params = sum(p.numel() for p in model.parameters())\n    print(f\"Total parameters: {total_params/1e6:.2f}M\")\n\n    opt = torch.optim.SGD(model.parameters(), lr=args.lr, momentum=0.9, weight_decay=args.wd, nesterov=True)\n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=args.epochs)\n    loss_fn = nn.CrossEntropyLoss(label_smoothing=args.label_smoothing)\n    scaler = torch.cuda.amp.GradScaler(enabled=(device.type==\"cuda\"))\n\n    best_acc = 0.0\n    for epoch in range(1, args.epochs+1):\n        tr_loss, tr_acc = train_one_epoch(model, train_loader, opt, scaler, loss_fn, device)\n        te_loss, te_acc = evaluate(model, test_loader, loss_fn, device)\n        scheduler.step()\n        if te_acc > best_acc:\n            best_acc = te_acc\n            torch.save(model.state_dict(), \"simple_resnet_cifar10_best.pth\")\n        print(f\"[Epoch {epoch:03d}] train_loss={tr_loss:.4f} train_acc={tr_acc:.4f} | \"\n              f\"val_loss={te_loss:.4f} val_acc={te_acc:.4f} | best_acc={best_acc:.4f}\")\n    print(\"Training done. Best val_acc:\", best_acc)\n\nif __name__ == \"__main__\":\n    main()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-24T15:31:04.889941Z","iopub.execute_input":"2025-09-24T15:31:04.890645Z","iopub.status.idle":"2025-09-24T16:06:31.934935Z","shell.execute_reply.started":"2025-09-24T15:31:04.890622Z","shell.execute_reply":"2025-09-24T16:06:31.934009Z"}},"outputs":[],"execution_count":null}]}