{"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"},{"sourceId":583798,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":436040,"modelId":452823}],"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Setup","metadata":{}},{"cell_type":"code","source":"!pip -q install timm==1.0.3 --no-warn-conflicts\n!apt install libarchive-dev\n!pip install libarchive","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T12:28:49.636109Z","iopub.execute_input":"2025-09-19T12:28:49.636399Z","iopub.status.idle":"2025-09-19T12:30:19.478084Z","shell.execute_reply.started":"2025-09-19T12:28:49.636344Z","shell.execute_reply":"2025-09-19T12:30:19.477210Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, math, random, re, json, gc, time, glob, shutil, pathlib, asyncio, csv, glob\nimport libarchive.public\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport torch, torch.nn as nn, torch.nn.functional as F\nfrom PIL import Image\nfrom torch.utils.data import DataLoader\nfrom torchvision import datasets, transforms, models\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\n\nfrom sklearn.metrics import (\n    accuracy_score,\n    precision_recall_fscore_support,\n    classification_report,\n    confusion_matrix,\n    ConfusionMatrixDisplay,\n)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-19T12:30:19.479200Z","iopub.execute_input":"2025-09-19T12:30:19.479440Z","iopub.status.idle":"2025-09-19T12:30:26.228346Z","shell.execute_reply.started":"2025-09-19T12:30:19.479412Z","shell.execute_reply":"2025-09-19T12:30:26.227693Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"SEED = 42\nrandom.seed(SEED); np.random.seed(SEED); torch.manual_seed(SEED); torch.cuda.manual_seed_all(SEED)\ntorch.backends.cudnn.benchmark = True\n\nDEVICE = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nprint(\"Device:\", DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T12:30:26.229793Z","iopub.execute_input":"2025-09-19T12:30:26.230135Z","iopub.status.idle":"2025-09-19T12:30:26.319783Z","shell.execute_reply.started":"2025-09-19T12:30:26.230116Z","shell.execute_reply":"2025-09-19T12:30:26.319026Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data","metadata":{}},{"cell_type":"code","source":"CIFAR10_MEAN = (0.4914, 0.4822, 0.4465)\nCIFAR10_STD  = (0.2470, 0.2435, 0.2616)\n\ntrain_tfms = transforms.Compose([\n    transforms.RandomCrop(32, padding=4, padding_mode=\"reflect\"),\n    # transforms.RandomHorizontalFlip(),\n    # transforms.AutoAugment(transforms.AutoAugmentPolicy.CIFAR10),\n    transforms.ToTensor(),\n    transforms.Normalize(CIFAR10_MEAN, CIFAR10_STD),\n])\n\nval_tfms = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(CIFAR10_MEAN, CIFAR10_STD),\n])\n\nDATA_DIR = \"./data\"\n\ntrain_set = datasets.CIFAR10(DATA_DIR, train=True,  download=True, transform=train_tfms)\nval_set   = datasets.CIFAR10(DATA_DIR, train=False, download=True, transform=val_tfms)\n\ntrain_loader = DataLoader(train_set, batch_size=128, shuffle=True,  num_workers=4, pin_memory=True)\nval_loader   = DataLoader(val_set,   batch_size=256, shuffle=False, num_workers=4, pin_memory=True)\n\nCLASSES = train_set.classes\nclass_to_idx = {c:i for i,c in enumerate(CLASSES)}\n\n\n# total number of samples\nprint(\"Total images:\", len(train_set) + len(val_set))\n\n# training and validation separately\nprint(\"Train set size:\", len(train_set))\nprint(\"Val set size:\", len(val_set))\n\n# classes\nprint(\"Classes:\", CLASSES)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T12:30:26.320480Z","iopub.execute_input":"2025-09-19T12:30:26.320698Z","iopub.status.idle":"2025-09-19T12:30:32.890523Z","shell.execute_reply.started":"2025-09-19T12:30:26.320680Z","shell.execute_reply":"2025-09-19T12:30:32.889851Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualize","metadata":{}},{"cell_type":"code","source":"def imshow(ax, img):\n    # img: CxHxW (tensor)\n    img = img.detach().cpu().numpy().transpose(1, 2, 0)   # → HxWxC\n    img = img * np.array(CIFAR10_STD) + np.array(CIFAR10_MEAN)  # unnormalize\n    img = np.clip(img, 0, 1)\n    ax.imshow(img)\n    ax.axis(\"off\")\n\nimages, labels = next(iter(train_loader))\n\nfig, axes = plt.subplots(2, 4, figsize=(7, 3))\naxes = axes.flatten()\nfor i in range(8):\n    imshow(axes[i], images[i])\n    axes[i].set_title(CLASSES[labels[i]])\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T12:30:32.891240Z","iopub.execute_input":"2025-09-19T12:30:32.891471Z","iopub.status.idle":"2025-09-19T12:30:33.963349Z","shell.execute_reply.started":"2025-09-19T12:30:32.891447Z","shell.execute_reply":"2025-09-19T12:30:33.962325Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model (Random Init)","metadata":{}},{"cell_type":"code","source":"def resnet18_cifar10(num_classes=10):\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, bias=True)\n    return m\n\nmodel = resnet18_cifar10().to(DEVICE)\nno_params = sum(p.numel() for p in model.parameters())/1e6\nprint(f\"Number of Parameters: {no_params:.2f} M\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T12:30:33.964398Z","iopub.execute_input":"2025-09-19T12:30:33.964634Z","iopub.status.idle":"2025-09-19T12:30:34.174103Z","shell.execute_reply.started":"2025-09-19T12:30:33.964610Z","shell.execute_reply":"2025-09-19T12:30:34.173273Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Training","metadata":{}},{"cell_type":"code","source":"EPOCHS = 100\nLR = 0.1\nWEIGHT_DECAY = 5e-4\n\noptimizer = torch.optim.SGD(model.parameters(), lr=LR, momentum=0.9, weight_decay=WEIGHT_DECAY, nesterov=True)\nscheduler = CosineAnnealingLR(optimizer, T_max=EPOCHS)\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n\ndef accuracy(logits, targets):\n    return (logits.argmax(1) == targets).float().mean().item()\n\nbest_acc = 0.0\nbest_path = \"best_model.pth\"\nbest_path_final = best_path.replace(\".pth\", \"_final.pth\")\n\ntrain_losses, val_losses = [], []\ntrain_accs, val_accs = [], []\nfor epoch in range(1, EPOCHS+1):\n    # train\n    model.train()\n    tr_loss, tr_acc, n = 0.0, 0.0, 0\n    for x,y in train_loader:\n        x,y = x.to(DEVICE, non_blocking=True), y.to(DEVICE, non_blocking=True)\n        optimizer.zero_grad(set_to_none=True)\n        logits = model(x)\n        loss = criterion(logits, y)\n        loss.backward()\n        optimizer.step()\n\n        bs = x.size(0)\n        tr_loss += loss.item()*bs\n        tr_acc  += accuracy(logits, y)*bs\n        n += bs\n\n    scheduler.step()\n\n    # validate\n    model.eval()\n    va_loss, va_acc, n_val = 0.0, 0.0, 0\n    with torch.no_grad():\n        for x,y in val_loader:\n            x,y = x.to(DEVICE, non_blocking=True), y.to(DEVICE, non_blocking=True)\n            logits = model(x)\n            loss = criterion(logits, y)\n            bs = x.size(0)\n            va_loss += loss.item()*bs\n            va_acc  += accuracy(logits, y)*bs\n            n_val += bs\n\n    tr_loss/=n; tr_acc/=n\n    va_loss/=n_val; va_acc/=n_val\n\n    train_losses.append(tr_loss)\n    val_losses.append(va_loss)\n    train_accs.append(tr_acc)\n    val_accs.append(va_acc)\n\n    print(f\"Epoch {epoch:03d}/{EPOCHS} | train loss {tr_loss:.4f} acc {tr_acc:.4f} | val loss {va_loss:.4f} acc {va_acc:.4f}\")\n\n    if va_acc > best_acc:\n        best_acc = va_acc\n        torch.save({\"model\": model.state_dict(), \"classes\": CLASSES}, best_path)\n    torch.save({\"model\": model.state_dict(), \"classes\": CLASSES}, best_path_final)\n\nprint(\"Best val acc:\", best_acc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T12:30:34.175103Z","iopub.execute_input":"2025-09-19T12:30:34.175820Z","iopub.status.idle":"2025-09-19T13:06:57.126372Z","shell.execute_reply.started":"2025-09-19T12:30:34.175789Z","shell.execute_reply":"2025-09-19T13:06:57.125182Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualize Training Characteristics","metadata":{}},{"cell_type":"code","source":"fig, axes = plt.subplots(1, 2, figsize=(12,5))\n\n# Loss subplot\naxes[0].plot(range(1, EPOCHS+1), train_losses, label=\"Train Loss\")\naxes[0].plot(range(1, EPOCHS+1), val_losses, label=\"Val Loss\")\naxes[0].set_xlabel(\"Epoch\")\naxes[0].set_ylabel(\"Loss\")\naxes[0].set_title(\"Training vs Validation Loss\")\naxes[0].legend()\naxes[0].grid(True)\n\n# Accuracy subplot\naxes[1].plot(range(1, EPOCHS+1), train_accs, label=\"Train Acc\")\naxes[1].plot(range(1, EPOCHS+1), val_accs, label=\"Val Acc\")\naxes[1].set_xlabel(\"Epoch\")\naxes[1].set_ylabel(\"Accuracy\")\naxes[1].set_title(\"Training vs Validation Accuracy\")\naxes[1].legend()\naxes[1].grid(True)\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T13:06:57.127597Z","iopub.execute_input":"2025-09-19T13:06:57.127917Z","iopub.status.idle":"2025-09-19T13:06:57.578357Z","shell.execute_reply.started":"2025-09-19T13:06:57.127885Z","shell.execute_reply":"2025-09-19T13:06:57.577463Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Load Model","metadata":{}},{"cell_type":"code","source":"# model = torch.load(\"/kaggle/input/resnet18-ass4/pytorch/default/1/best_model.pth\", map_location=DEVICE)\n# Load checkpoint\ncheckpoint = torch.load(\"/kaggle/working/best_model.pth\", map_location=DEVICE)\n\n# Rebuild model\nmodel = resnet18_cifar10(num_classes=len(checkpoint[\"classes\"]))\nmodel.load_state_dict(checkpoint[\"model\"])\nmodel.to(DEVICE)\nmodel.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T13:06:57.581055Z","iopub.execute_input":"2025-09-19T13:06:57.581523Z","iopub.status.idle":"2025-09-19T13:06:57.884283Z","shell.execute_reply.started":"2025-09-19T13:06:57.581502Z","shell.execute_reply":"2025-09-19T13:06:57.883332Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference On CIFAR-10 Test Set","metadata":{}},{"cell_type":"code","source":"all_preds, all_targets = [], []\n\nwith torch.no_grad():\n    for x, y in val_loader:\n        x = x.to(DEVICE, non_blocking=True)\n        logits = model(x)\n        preds = logits.argmax(1).cpu().numpy()\n        all_preds.append(preds)\n        all_targets.append(y.numpy())\n\n# concat to 1D arrays\ny_true = np.concatenate(all_targets)\ny_pred = np.concatenate(all_preds)\n\n# --- Metrics ---\nacc = accuracy_score(y_true, y_pred)\nprec_macro, rec_macro, f1_macro, _ = precision_recall_fscore_support(\n    y_true, y_pred, average=\"macro\", zero_division=0\n)\nprec_weighted, rec_weighted, f1_weighted, _ = precision_recall_fscore_support(\n    y_true, y_pred, average=\"weighted\", zero_division=0\n)\n\nprint(f\"Accuracy: {acc:.4f}\")\nprint(f\"Macro  - Precision: {prec_macro:.4f}  Recall: {rec_macro:.4f}  F1: {f1_macro:.4f}\")\nprint(f\"Weighted - Precision: {prec_weighted:.4f}  Recall: {rec_weighted:.4f}  F1: {f1_weighted:.4f}\")\n\n# Per-class breakdown\nprint(\"\\nPer-class report:\")\nprint(classification_report(y_true, y_pred, target_names=CLASSES, zero_division=0))\n\n# --- Confusion Matrix ---\ncm = confusion_matrix(y_true, y_pred, labels=range(len(CLASSES)))\n\nfig, ax = plt.subplots(1, 2, figsize=(14, 5))\n\n# raw counts\ndisp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=CLASSES)\ndisp.plot(ax=ax[0], xticks_rotation=45, colorbar=False)\nax[0].set_title(\"Confusion Matrix (Counts)\")\n\n# normalized by true class (row-normalized)\ncm_norm = cm.astype(np.float64) / cm.sum(axis=1, keepdims=True)\ndisp_norm = ConfusionMatrixDisplay(confusion_matrix=cm_norm, display_labels=CLASSES)\ndisp_norm.plot(ax=ax[1], xticks_rotation=45, colorbar=False, values_format=\".2f\")\nax[1].set_title(\"Confusion Matrix (Row-Normalized)\")\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T13:06:57.885213Z","iopub.execute_input":"2025-09-19T13:06:57.885500Z","iopub.status.idle":"2025-09-19T13:07:00.213156Z","shell.execute_reply.started":"2025-09-19T13:06:57.885472Z","shell.execute_reply":"2025-09-19T13:07:00.212137Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Decompress Kaggle Test Set","metadata":{}},{"cell_type":"code","source":"cnt = 0\n\nfor entry in libarchive.public.file_pour('/kaggle/input/cifar-10/test.7z'):\n    cnt += 1\n    if cnt % 75000 == 0: print(cnt)\n\n\n# len(glob.glob('test/*'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T13:07:00.214425Z","iopub.execute_input":"2025-09-19T13:07:00.215418Z","iopub.status.idle":"2025-09-19T13:08:11.640945Z","shell.execute_reply.started":"2025-09-19T13:07:00.215384Z","shell.execute_reply":"2025-09-19T13:08:11.640043Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference on Kaggle Test set","metadata":{}},{"cell_type":"code","source":"test_tfms = val_tfms\n\ndef load_image(path):\n    return test_tfms(Image.open(path).convert(\"RGB\"))\n\n# IMPORTANT: iterate ids in numeric order\ntest_dir = \"test\"\ntest_ids = sorted([int(os.path.splitext(os.path.basename(p))[0]) for p in glob.glob(os.path.join(test_dir, \"*.png\"))])\nprint(\"Test images:\", len(test_ids))\n\nBATCH = 512\npred_labels = []\n\nwith torch.no_grad():\n    batch = []\n    current_ids = []\n    for i in test_ids:\n        x = load_image(os.path.join(test_dir, f\"{i}.png\"))\n        batch.append(x)\n        current_ids.append(i)\n        if len(batch) == BATCH:\n            xb = torch.stack(batch).to(DEVICE)\n            probs = model(xb).softmax(1)\n            preds = probs.argmax(1).cpu().numpy()\n            pred_labels += [(j, CLASSES[p]) for j,p in zip(current_ids, preds)]\n            batch, current_ids = [], []\n    if batch:\n        xb = torch.stack(batch).to(DEVICE)\n        probs = model(xb).softmax(1)\n        preds = probs.argmax(1).cpu().numpy()\n        pred_labels += [(j, CLASSES[p]) for j,p in zip(current_ids, preds)]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T13:08:11.641981Z","iopub.execute_input":"2025-09-19T13:08:11.642300Z","iopub.status.idle":"2025-09-19T13:11:08.875501Z","shell.execute_reply.started":"2025-09-19T13:08:11.642274Z","shell.execute_reply":"2025-09-19T13:11:08.874435Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Build Submission CSV","metadata":{}},{"cell_type":"code","source":"with open(\"submission.csv\",\"w\", newline=\"\") as f:\n    w = csv.writer(f)\n    w.writerow([\"id\",\"label\"])\n    for i,lab in sorted(pred_labels, key=lambda t:t[0]):\n        w.writerow([i, lab])\n\nprint(\"Wrote submission.csv\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-19T13:11:08.876552Z","iopub.execute_input":"2025-09-19T13:11:08.876884Z","iopub.status.idle":"2025-09-19T13:11:09.219150Z","shell.execute_reply.started":"2025-09-19T13:11:08.876858Z","shell.execute_reply":"2025-09-19T13:11:09.218394Z"}},"outputs":[],"execution_count":null}]}