{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":28755,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ============================================\n# CIFAR-10 ConvNet Assignment\n# ============================================\n\nimport os\nimport random\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torchvision\n\n# Reproducibility\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\ntorch.cuda.manual_seed_all(SEED)\n\n# Device\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nprint(\"PyTorch version:\", torch.__version__)\nprint(\"Torchvision version:\", torchvision.__version__)\nprint(\"Device:\", device)\n\nif torch.cuda.is_available():\n    print(\"GPU:\", torch.cuda.get_device_name(0))\n\n# Kaggle competition files\nprint(\"\\nKaggle input files:\")\nfor dirname, _, filenames in os.walk(\"/kaggle/input\"):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-23T13:13:35.410977Z","iopub.execute_input":"2026-09-23T13:13:35.411301Z","iopub.status.idle":"2026-09-23T13:13:46.428073Z","shell.execute_reply.started":"2026-09-23T13:13:35.411275Z","shell.execute_reply":"2026-09-23T13:13:46.427235Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# Load CIFAR-10 and create data loaders\n# ============================================\n\nfrom torchvision import datasets, transforms\nfrom torch.utils.data import DataLoader\n\n# CIFAR-10 normalization values\nmean = (0.4914, 0.4822, 0.4465)\nstd = (0.2470, 0.2435, 0.2616)\n\n# Training augmentation\ntrain_transform = transforms.Compose([\n    transforms.RandomCrop(32, padding=4),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std),\n])\n\n# No augmentation for testing\ntest_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std),\n])\n\n# Download official CIFAR-10\ntrain_dataset = datasets.CIFAR10(\n    root=\"/kaggle/working/data\",\n    train=True,\n    download=True,\n    transform=train_transform\n)\n\ntest_dataset = datasets.CIFAR10(\n    root=\"/kaggle/working/data\",\n    train=False,\n    download=True,\n    transform=test_transform\n)\n\n# Data loaders\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=128,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=256,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nclasses = train_dataset.classes\n\nprint(\"Training images:\", len(train_dataset))\nprint(\"Test images:\", len(test_dataset))\nprint(\"Classes:\", classes)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T13:14:46.101649Z","iopub.execute_input":"2026-09-23T13:14:46.102487Z","iopub.status.idle":"2026-09-23T13:47:08.512411Z","shell.execute_reply.started":"2026-09-23T13:14:46.102454Z","shell.execute_reply":"2026-09-23T13:47:08.511751Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# Build ResNet-18 for CIFAR-10\n# RANDOM INITIALIZATION - NO PRETRAINED WEIGHTS\n# ============================================\n\nimport torch.nn as nn\nfrom torchvision.models import resnet18\n\n# weights=None guarantees random initialization.\nmodel = resnet18(weights=None)\n\n# Adapt ResNet-18 for small 32x32 CIFAR-10 images.\n# Standard ResNet uses a 7x7 stride-2 convolution designed for ImageNet.\n# For CIFAR-10 we use a 3x3 stride-1 convolution instead.\nmodel.conv1 = nn.Conv2d(\n    3,\n    64,\n    kernel_size=3,\n    stride=1,\n    padding=1,\n    bias=False\n)\n\n# Remove the initial max pooling layer so we preserve spatial information.\nmodel.maxpool = nn.Identity()\n\n# CIFAR-10 has 10 output classes.\nmodel.fc = nn.Linear(model.fc.in_features, 10)\n\nmodel = model.to(device)\n\n# Use both T4 GPUs if available.\nif torch.cuda.device_count() > 1:\n    print(\"Using\", torch.cuda.device_count(), \"GPUs\")\n    model = nn.DataParallel(model)\n\nprint(\"Model created successfully.\")\nprint(\"Pretrained weights: NO\")\nprint(\"Random initialization: YES\")\n\ntotal_params = sum(p.numel() for p in model.parameters())\ntrainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\n\nprint(f\"Total parameters: {total_params:,}\")\nprint(f\"Trainable parameters: {trainable_params:,}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T13:51:25.892091Z","iopub.execute_input":"2026-09-23T13:51:25.892401Z","iopub.status.idle":"2026-09-23T13:51:26.39216Z","shell.execute_reply.started":"2026-09-23T13:51:25.892378Z","shell.execute_reply":"2026-09-23T13:51:26.39117Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# Train ResNet-18 from scratch on CIFAR-10\n# ============================================\n\nimport torch.optim as optim\nimport time\n\n# Loss function\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n\n# SGD optimizer\noptimizer = optim.SGD(\n    model.parameters(),\n    lr=0.1,\n    momentum=0.9,\n    weight_decay=5e-4,\n    nesterov=True\n)\n\n# Cosine learning-rate decay\nEPOCHS = 100\n\nscheduler = optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=EPOCHS\n)\n\n# Mixed precision\nscaler = torch.amp.GradScaler(\"cuda\")\n\nbest_acc = 0.0\nhistory = []\n\nstart_time = time.time()\n\nfor epoch in range(EPOCHS):\n\n    # --------------------\n    # TRAIN\n    # --------------------\n    model.train()\n\n    train_loss = 0.0\n    train_correct = 0\n    train_total = 0\n\n    for images, labels in train_loader:\n\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n\n        optimizer.zero_grad(set_to_none=True)\n\n        with torch.amp.autocast(\"cuda\"):\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n\n        train_loss += loss.item() * images.size(0)\n\n        _, predicted = outputs.max(1)\n\n        train_total += labels.size(0)\n        train_correct += predicted.eq(labels).sum().item()\n\n    train_loss /= train_total\n    train_acc = 100.0 * train_correct / train_total\n\n    # --------------------\n    # TEST\n    # --------------------\n    model.eval()\n\n    test_loss = 0.0\n    test_correct = 0\n    test_total = 0\n\n    with torch.no_grad():\n\n        for images, labels in test_loader:\n\n            images = images.to(device, non_blocking=True)\n            labels = labels.to(device, non_blocking=True)\n\n            with torch.amp.autocast(\"cuda\"):\n                outputs = model(images)\n                loss = criterion(outputs, labels)\n\n            test_loss += loss.item() * images.size(0)\n\n            _, predicted = outputs.max(1)\n\n            test_total += labels.size(0)\n            test_correct += predicted.eq(labels).sum().item()\n\n    test_loss /= test_total\n    test_acc = 100.0 * test_correct / test_total\n\n    scheduler.step()\n\n    # Save best model\n    if test_acc > best_acc:\n\n        best_acc = test_acc\n\n        state_dict = (\n            model.module.state_dict()\n            if isinstance(model, nn.DataParallel)\n            else model.state_dict()\n        )\n\n        torch.save(\n            state_dict,\n            \"/kaggle/working/best_cifar10_resnet18.pth\"\n        )\n\n    history.append({\n        \"epoch\": epoch + 1,\n        \"train_loss\": train_loss,\n        \"train_acc\": train_acc,\n        \"test_loss\": test_loss,\n        \"test_acc\": test_acc\n    })\n\n    print(\n        f\"Epoch {epoch+1:03d}/{EPOCHS} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Train Acc: {train_acc:.2f}% | \"\n        f\"Test Loss: {test_loss:.4f} | \"\n        f\"Test Acc: {test_acc:.2f}% | \"\n        f\"Best: {best_acc:.2f}%\"\n    )\n\nelapsed = (time.time() - start_time) / 60\n\nprint(\"\\nTraining complete!\")\nprint(f\"Best CIFAR-10 test accuracy: {best_acc:.2f}%\")\nprint(f\"Total training time: {elapsed:.1f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T13:52:17.08784Z","iopub.execute_input":"2026-09-23T13:52:17.088396Z","iopub.status.idle":"2026-09-23T15:41:22.971673Z","shell.execute_reply.started":"2026-09-23T13:52:17.088365Z","shell.execute_reply":"2026-09-23T15:41:22.969407Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nmodel_path = \"/kaggle/working/best_cifar10_resnet18.pth\"\n\nif os.path.exists(model_path):\n    size_mb = os.path.getsize(model_path) / (1024 ** 2)\n    \n    print(\"SUCCESS: Best model checkpoint exists!\")\n    print(f\"File: {model_path}\")\n    print(f\"Size: {size_mb:.2f} MB\")\n    print(\"Best observed CIFAR-10 test accuracy: 95.30%\")\nelse:\n    print(\"ERROR: Model checkpoint not found.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T15:41:59.732269Z","iopub.execute_input":"2026-09-23T15:41:59.733168Z","iopub.status.idle":"2026-09-23T15:41:59.743584Z","shell.execute_reply.started":"2026-09-23T15:41:59.733109Z","shell.execute_reply":"2026-09-23T15:41:59.742763Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nmodel_path = \"/kaggle/working/best_cifar10_resnet18.pth\"\n\nprint(\"Checkpoint exists:\", os.path.exists(model_path))\n\nif os.path.exists(model_path):\n    print(\n        \"Checkpoint size:\",\n        round(os.path.getsize(model_path) / (1024**2), 2),\n        \"MB\"\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T19:53:17.298542Z","iopub.execute_input":"2026-09-23T19:53:17.299046Z","iopub.status.idle":"2026-09-23T19:53:17.316222Z","shell.execute_reply.started":"2026-09-23T19:53:17.299009Z","shell.execute_reply":"2026-09-23T19:53:17.315589Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Professor provided extraction method\n!apt-get install -y libarchive-dev > /dev/null 2>&1\n!pip install -q libarchive\n\nimport os\nimport libarchive.public\nimport time\n\nos.chdir(\"/kaggle/working\")\n\nstart = time.time()\ncnt = 0\n\nprint(\"Starting extraction...\")\n\nfor entry in libarchive.public.file_pour(\n    \"/kaggle/input/competitions/cifar-10/test.7z\"\n):\n    cnt += 1\n\n    if cnt % 1000 == 0:\n        elapsed = (time.time() - start) / 60\n        print(f\"{cnt:,} files extracted | {elapsed:.1f} minutes\")\n\nprint(\"\\nDONE!\")\nprint(f\"Total files: {cnt:,}\")\nprint(f\"Total time: {(time.time() - start) / 60:.1f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T19:58:22.068139Z","iopub.execute_input":"2026-09-23T19:58:22.068497Z","iopub.status.idle":"2026-09-23T19:59:47.54439Z","shell.execute_reply.started":"2026-09-23T19:58:22.068468Z","shell.execute_reply":"2026-09-23T19:59:47.543239Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# Load the EXACT CIFAR-10 ResNet-18 architecture\n# ============================================\n\nimport torch\nimport torch.nn as nn\nfrom torchvision import models\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)\n\n# Create ResNet-18\nmodel = models.resnet18(weights=None)\n\n# CIFAR-10 modification used during training\nmodel.conv1 = nn.Conv2d(\n    3, 64,\n    kernel_size=3,\n    stride=1,\n    padding=1,\n    bias=False\n)\n\n# CIFAR-10 does not use the normal ImageNet maxpool\nmodel.maxpool = nn.Identity()\n\n# 10 CIFAR-10 classes\nmodel.fc = nn.Linear(model.fc.in_features, 10)\n\n# Load checkpoint\ncheckpoint_path = \"/kaggle/working/best_cifar10_resnet18.pth\"\n\nstate_dict = torch.load(\n    checkpoint_path,\n    map_location=\"cpu\",\n    weights_only=True\n)\n\n# Remove DataParallel prefix if present\nif any(k.startswith(\"module.\") for k in state_dict):\n    state_dict = {\n        k.replace(\"module.\", \"\", 1): v\n        for k, v in state_dict.items()\n    }\n\nmodel.load_state_dict(state_dict)\n\nmodel = model.to(device)\nmodel.eval()\n\nprint(\"SUCCESS: 95.30% model loaded correctly!\")\nprint(\"Ready for Kaggle inference.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T20:01:30.846858Z","iopub.execute_input":"2026-09-23T20:01:30.847393Z","iopub.status.idle":"2026-09-23T20:01:31.08795Z","shell.execute_reply.started":"2026-09-23T20:01:30.847358Z","shell.execute_reply":"2026-09-23T20:01:31.087081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================\n# Run inference on 300,000 Kaggle test images\n# ============================================\n\nimport os\nimport time\nimport pandas as pd\nfrom PIL import Image\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\n\n# Same normalization used for CIFAR-10\ntest_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(\n        (0.4914, 0.4822, 0.4465),\n        (0.2470, 0.2435, 0.2616)\n    )\n])\n\n# Location of extracted images\ntest_dir = \"/kaggle/working/test\"\n\nclass KaggleTestDataset(Dataset):\n    def __init__(self, folder, transform=None):\n        self.folder = folder\n        self.transform = transform\n\n        self.files = sorted(\n            [f for f in os.listdir(folder) if f.endswith(\".png\")],\n            key=lambda x: int(os.path.splitext(x)[0])\n        )\n\n    def __len__(self):\n        return len(self.files)\n\n    def __getitem__(self, idx):\n        filename = self.files[idx]\n        path = os.path.join(self.folder, filename)\n\n        image = Image.open(path).convert(\"RGB\")\n\n        if self.transform:\n            image = self.transform(image)\n\n        image_id = int(os.path.splitext(filename)[0])\n\n        return image, image_id\n\n\ndataset = KaggleTestDataset(test_dir, transform=test_transform)\n\nloader = DataLoader(\n    dataset,\n    batch_size=512,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nprint(f\"Images found: {len(dataset):,}\")\nprint(f\"Batches: {len(loader):,}\")\nprint(\"Starting inference...\\n\")\n\npredictions = []\nimage_ids = []\n\nstart = time.time()\n\nmodel.eval()\n\nwith torch.inference_mode():\n\n    for batch_num, (images, ids) in enumerate(loader, 1):\n\n        images = images.to(device, non_blocking=True)\n\n        # Mixed precision = faster GPU inference\n        with torch.autocast(device_type=\"cuda\", dtype=torch.float16):\n            outputs = model(images)\n\n        preds = outputs.argmax(dim=1).cpu().numpy()\n\n        predictions.extend(preds)\n        image_ids.extend(ids.numpy())\n\n        if batch_num % 50 == 0 or batch_num == len(loader):\n            elapsed = (time.time() - start) / 60\n            processed = len(predictions)\n\n            print(\n                f\"{processed:,}/{len(dataset):,} images \"\n                f\"| {elapsed:.1f} minutes\"\n            )\n\nprint(\"\\nINFERENCE COMPLETE!\")\nprint(f\"Predictions: {len(predictions):,}\")\nprint(f\"Total time: {(time.time() - start)/60:.1f} minutes\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T20:03:39.677942Z","iopub.execute_input":"2026-09-23T20:03:39.67848Z","iopub.status.idle":"2026-09-23T20:04:55.807612Z","shell.execute_reply.started":"2026-09-23T20:03:39.678444Z","shell.execute_reply":"2026-09-23T20:04:55.806711Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nfor root, dirs, files in os.walk(\"/kaggle/input/competitions/cifar-10\"):\n    for f in files:\n        print(os.path.join(root, f))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T20:13:12.058353Z","iopub.execute_input":"2026-09-23T20:13:12.05904Z","iopub.status.idle":"2026-09-23T20:13:12.074496Z","shell.execute_reply.started":"2026-09-23T20:13:12.058994Z","shell.execute_reply":"2026-09-23T20:13:12.073377Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\nsample = pd.read_csv(\n    \"/kaggle/input/competitions/cifar-10/sampleSubmission.csv\"\n)\n\nprint(\"Shape:\", sample.shape)\nprint(\"Columns:\", sample.columns.tolist())\nprint(\"\\nFirst 10 rows:\")\nprint(sample.head(10))\n\nprint(\"\\nLast 5 rows:\")\nprint(sample.tail())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T20:13:39.991752Z","iopub.execute_input":"2026-09-23T20:13:39.99229Z","iopub.status.idle":"2026-09-23T20:13:40.153101Z","shell.execute_reply.started":"2026-09-23T20:13:39.992259Z","shell.execute_reply":"2026-09-23T20:13:40.15204Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# CIFAR-10 class order used by torchvision\nclasses = [\n    \"airplane\",\n    \"automobile\",\n    \"bird\",\n    \"cat\",\n    \"deer\",\n    \"dog\",\n    \"frog\",\n    \"horse\",\n    \"ship\",\n    \"truck\"\n]\n\n# Convert numeric predictions (0-9) to class names\npredicted_labels = [classes[p] for p in predictions]\n\n# Use Kaggle's sample submission so IDs/order are guaranteed correct\nsubmission = sample.copy()\nsubmission[\"label\"] = predicted_labels\n\nsubmission_path = \"/kaggle/working/submission.csv\"\nsubmission.to_csv(submission_path, index=False)\n\nprint(\"Submission created!\")\nprint(\"Shape:\", submission.shape)\nprint(\"\\nFirst 10 rows:\")\nprint(submission.head(10))\nprint(\"\\nLast 5 rows:\")\nprint(submission.tail())\n\nprint(\"\\nLabel counts:\")\nprint(submission[\"label\"].value_counts())\n\nprint(\"\\nSaved to:\", submission_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T20:14:11.758287Z","iopub.execute_input":"2026-09-23T20:14:11.759332Z","iopub.status.idle":"2026-09-23T20:14:12.105038Z","shell.execute_reply.started":"2026-09-23T20:14:11.759286Z","shell.execute_reply":"2026-09-23T20:14:12.104176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"valid_classes = {\n    \"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\",\n    \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"\n}\n\nprint(\"Rows:\", len(submission))\nprint(\"IDs unique:\", submission[\"id\"].is_unique)\nprint(\"First ID:\", submission[\"id\"].iloc[0])\nprint(\"Last ID:\", submission[\"id\"].iloc[-1])\nprint(\"Missing values:\", submission.isnull().sum().sum())\nprint(\"Invalid labels:\", (~submission[\"label\"].isin(valid_classes)).sum())\n\nassert len(submission) == 300000\nassert submission[\"id\"].is_unique\nassert submission[\"id\"].tolist() == list(range(1, 300001))\nassert submission[\"label\"].isin(valid_classes).all()\nassert not submission.isnull().any().any()\n\nprint(\"\\n SUBMISSION FORMAT PASSED ALL CHECKS\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-23T20:15:14.732869Z","iopub.execute_input":"2026-09-23T20:15:14.733629Z","iopub.status.idle":"2026-09-23T20:15:14.836402Z","shell.execute_reply.started":"2026-09-23T20:15:14.733593Z","shell.execute_reply":"2026-09-23T20:15:14.835459Z"}},"outputs":[],"execution_count":null}]}