{"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":"# 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\n\n# Use the kagglehub client library to attach Kaggle resources like competitions, datasets, and models to your session\n# Learn more about kagglehub: https://github.com/Kaggle/kagglehub/blob/main/README.md\n\nimport kagglehub\n# kagglehub.dataset_download('<owner>/<dataset-slug>')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:52:39.692334Z","iopub.execute_input":"2026-09-21T17:52:39.693057Z","iopub.status.idle":"2026-09-21T17:52:40.807724Z","shell.execute_reply.started":"2026-09-21T17:52:39.693024Z","shell.execute_reply":"2026-09-21T17:52:40.80693Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport random\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nimport torchvision\nimport torchvision.transforms as transforms\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))\nelse:\n    print(\"WARNING: GPU is not enabled.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T17:52:44.290904Z","iopub.execute_input":"2026-09-21T17:52:44.29162Z","iopub.status.idle":"2026-09-21T17:52:47.898813Z","shell.execute_reply.started":"2026-09-21T17:52:44.291591Z","shell.execute_reply":"2026-09-21T17:52:47.897889Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CIFAR-10 normalization values\nmean = (0.4914, 0.4822, 0.4465)\nstd = (0.2470, 0.2435, 0.2616)\n\n# Training transformations\n# Augmentation helps the model generalize better\ntrain_transform = transforms.Compose([\n    transforms.RandomCrop(32, padding=4),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std),\n])\n\n# Test transformations - NO augmentation\ntest_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std),\n])\n\n# Download official CIFAR-10 dataset\ntrain_dataset = torchvision.datasets.CIFAR10(\n    root=\"./data\",\n    train=True,\n    download=True,\n    transform=train_transform\n)\n\ntest_dataset = torchvision.datasets.CIFAR10(\n    root=\"./data\",\n    train=False,\n    download=True,\n    transform=test_transform\n)\n\n# DataLoaders\ntrain_loader = torch.utils.data.DataLoader(\n    train_dataset,\n    batch_size=128,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\ntest_loader = torch.utils.data.DataLoader(\n    test_dataset,\n    batch_size=256,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n\nclasses = (\n    \"airplane\", \"automobile\", \"bird\", \"cat\", \"deer\",\n    \"dog\", \"frog\", \"horse\", \"ship\", \"truck\"\n)\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-21T17:53:02.819Z","iopub.execute_input":"2026-09-21T17:53:02.819664Z","iopub.status.idle":"2026-09-21T18:26:33.623561Z","shell.execute_reply.started":"2026-09-21T17:53:02.819634Z","shell.execute_reply":"2026-09-21T18:26:33.622846Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Residual block used by the ResNet\nclass BasicBlock(nn.Module):\n    expansion = 1\n\n    def __init__(self, in_channels, out_channels, stride=1):\n        super().__init__()\n\n        self.conv1 = nn.Conv2d(\n            in_channels, out_channels,\n            kernel_size=3, stride=stride,\n            padding=1, bias=False\n        )\n        self.bn1 = nn.BatchNorm2d(out_channels)\n\n        self.conv2 = nn.Conv2d(\n            out_channels, out_channels,\n            kernel_size=3, stride=1,\n            padding=1, bias=False\n        )\n        self.bn2 = nn.BatchNorm2d(out_channels)\n\n        # Shortcut connection\n        if stride != 1 or in_channels != out_channels:\n            self.shortcut = nn.Sequential(\n                nn.Conv2d(\n                    in_channels, out_channels,\n                    kernel_size=1, stride=stride,\n                    bias=False\n                ),\n                nn.BatchNorm2d(out_channels)\n            )\n        else:\n            self.shortcut = nn.Identity()\n\n    def forward(self, x):\n        identity = self.shortcut(x)\n\n        out = torch.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n\n        out += identity\n        out = torch.relu(out)\n\n        return out\n\n\nclass CIFARResNet(nn.Module):\n    def __init__(self, num_classes=10):\n        super().__init__()\n\n        self.in_channels = 64\n\n        # CIFAR-10 uses 32x32 images, so we start with a 3x3 convolution\n        self.conv1 = nn.Conv2d(\n            3, 64,\n            kernel_size=3,\n            stride=1,\n            padding=1,\n            bias=False\n        )\n        self.bn1 = nn.BatchNorm2d(64)\n\n        self.layer1 = self._make_layer(64,  2, stride=1)\n        self.layer2 = self._make_layer(128, 2, stride=2)\n        self.layer3 = self._make_layer(256, 2, stride=2)\n        self.layer4 = self._make_layer(512, 2, stride=2)\n\n        self.avgpool = nn.AdaptiveAvgPool2d((1, 1))\n        self.fc = nn.Linear(512, num_classes)\n\n    def _make_layer(self, out_channels, blocks, stride):\n        layers = [\n            BasicBlock(\n                self.in_channels,\n                out_channels,\n                stride\n            )\n        ]\n\n        self.in_channels = out_channels\n\n        for _ in range(1, blocks):\n            layers.append(\n                BasicBlock(\n                    self.in_channels,\n                    out_channels,\n                    stride=1\n                )\n            )\n\n        return nn.Sequential(*layers)\n\n    def forward(self, x):\n        x = torch.relu(self.bn1(self.conv1(x)))\n\n        x = self.layer1(x)\n        x = self.layer2(x)\n        x = self.layer3(x)\n        x = self.layer4(x)\n\n        x = self.avgpool(x)\n        x = torch.flatten(x, 1)\n        x = self.fc(x)\n\n        return x\n\n\n# Create model from scratch\n# No pretrained model and no pretrained weights are loaded.\nmodel = CIFARResNet(num_classes=10).to(device)\n\ntotal_params = sum(p.numel() for p in model.parameters())\ntrainable_params = sum(\n    p.numel() for p in model.parameters() if p.requires_grad\n)\n\nprint(model)\nprint(f\"\\nTotal parameters: {total_params:,}\")\nprint(f\"Trainable parameters: {trainable_params:,}\")\nprint(\"\\nModel created with randomly initialized weights.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T18:28:48.859375Z","iopub.execute_input":"2026-09-21T18:28:48.859832Z","iopub.status.idle":"2026-09-21T18:28:49.238685Z","shell.execute_reply.started":"2026-09-21T18:28:48.859792Z","shell.execute_reply":"2026-09-21T18:28:49.237647Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Loss function\ncriterion = nn.CrossEntropyLoss()\n\n# SGD works very well for CIFAR-10 ResNet training\noptimizer = optim.SGD(\n    model.parameters(),\n    lr=0.1,\n    momentum=0.9,\n    weight_decay=5e-4\n)\n\n# Gradually reduce learning rate during training\nEPOCHS = 100\n\nscheduler = optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=EPOCHS\n)\n\nprint(\"Loss function:\", criterion)\nprint(\"Optimizer:\", optimizer)\nprint(\"Scheduler:\", scheduler)\nprint(\"Epochs:\", EPOCHS)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T18:28:58.112632Z","iopub.execute_input":"2026-09-21T18:28:58.113708Z","iopub.status.idle":"2026-09-21T18:28:58.119788Z","shell.execute_reply.started":"2026-09-21T18:28:58.113662Z","shell.execute_reply":"2026-09-21T18:28:58.119114Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(model, loader, optimizer, criterion):\n    model.train()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in loader:\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n\n        optimizer.zero_grad()\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item() * images.size(0)\n\n        _, predicted = outputs.max(1)\n        total += labels.size(0)\n        correct += predicted.eq(labels).sum().item()\n\n    epoch_loss = running_loss / total\n    epoch_acc = 100.0 * correct / total\n\n    return epoch_loss, epoch_acc\n\n\n@torch.no_grad()\ndef evaluate(model, loader, criterion):\n    model.eval()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    for images, labels in loader:\n        images = images.to(device, non_blocking=True)\n        labels = labels.to(device, non_blocking=True)\n\n        outputs = model(images)\n        loss = criterion(outputs, labels)\n\n        running_loss += loss.item() * images.size(0)\n\n        _, predicted = outputs.max(1)\n        total += labels.size(0)\n        correct += predicted.eq(labels).sum().item()\n\n    epoch_loss = running_loss / total\n    epoch_acc = 100.0 * correct / total\n\n    return epoch_loss, epoch_acc\n\n\nprint(\"Training and evaluation functions ready.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T18:29:07.79159Z","iopub.execute_input":"2026-09-21T18:29:07.792434Z","iopub.status.idle":"2026-09-21T18:29:07.800452Z","shell.execute_reply.started":"2026-09-21T18:29:07.792401Z","shell.execute_reply":"2026-09-21T18:29:07.799541Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\nimport os\n\n# --------------------------------------------------\n# TRAINING SETTINGS\n# --------------------------------------------------\nEPOCHS = 50\n\n# Re-create optimizer/scheduler for this 50-epoch run\noptimizer = optim.SGD(\n    model.parameters(),\n    lr=0.1,\n    momentum=0.9,\n    weight_decay=5e-4\n)\n\nscheduler = optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=EPOCHS\n)\n\nbest_acc = 0.0\nbest_epoch = 0\n\ntrain_losses = []\ntrain_accuracies = []\ntest_losses = []\ntest_accuracies = []\n\nlatest_checkpoint = \"/kaggle/working/latest_checkpoint.pth\"\nbest_model_path = \"/kaggle/working/best_cifar10_resnet.pth\"\n\nprint(f\"Starting training for {EPOCHS} epochs\")\nprint(\"=\" * 80)\n\nstart_time = time.time()\n\nfor epoch in range(EPOCHS):\n\n    epoch_start = time.time()\n\n    # ---------------- TRAIN ----------------\n    train_loss, train_acc = train_one_epoch(\n        model,\n        train_loader,\n        optimizer,\n        criterion\n    )\n\n    # ---------------- TEST -----------------\n    test_loss, test_acc = evaluate(\n        model,\n        test_loader,\n        criterion\n    )\n\n    scheduler.step()\n\n    train_losses.append(train_loss)\n    train_accuracies.append(train_acc)\n    test_losses.append(test_loss)\n    test_accuracies.append(test_acc)\n\n    # Save best model\n    marker = \"\"\n\n    if test_acc > best_acc:\n        best_acc = test_acc\n        best_epoch = epoch + 1\n\n        torch.save(\n            model.state_dict(),\n            best_model_path\n        )\n\n        marker = \" <-- BEST\"\n\n    # Save FULL checkpoint after EVERY epoch\n    torch.save({\n        \"epoch\": epoch + 1,\n        \"model_state_dict\": model.state_dict(),\n        \"optimizer_state_dict\": optimizer.state_dict(),\n        \"scheduler_state_dict\": scheduler.state_dict(),\n        \"best_acc\": best_acc,\n        \"best_epoch\": best_epoch,\n        \"train_losses\": train_losses,\n        \"train_accuracies\": train_accuracies,\n        \"test_losses\": test_losses,\n        \"test_accuracies\": test_accuracies\n    }, latest_checkpoint)\n\n    elapsed = time.time() - epoch_start\n    current_lr = optimizer.param_groups[0][\"lr\"]\n\n    print(\n        f\"Epoch {epoch+1:2d}/{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\"LR: {current_lr:.5f} | \"\n        f\"{elapsed:.1f}s{marker}\"\n    )\n\ntotal_time = (time.time() - start_time) / 60\n\nprint(\"\\n\" + \"=\" * 80)\nprint(\"TRAINING COMPLETE\")\nprint(f\"Best Test Accuracy: {best_acc:.2f}%\")\nprint(f\"Best Epoch: {best_epoch}\")\nprint(f\"Training Time: {total_time:.1f} minutes\")\nprint(f\"Latest checkpoint: {latest_checkpoint}\")\nprint(f\"Best model: {best_model_path}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T18:31:18.592529Z","iopub.execute_input":"2026-09-21T18:31:18.593247Z","iopub.status.idle":"2026-09-21T18:54:21.742428Z","shell.execute_reply.started":"2026-09-21T18:31:18.593214Z","shell.execute_reply":"2026-09-21T18:54:21.741276Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(\"Best model exists:\",\n      os.path.exists(\"/kaggle/working/best_cifar10_resnet.pth\"))\n\nprint(\"Latest checkpoint exists:\",\n      os.path.exists(\"/kaggle/working/latest_checkpoint.pth\"))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T18:54:33.479139Z","iopub.execute_input":"2026-09-21T18:54:33.479437Z","iopub.status.idle":"2026-09-21T18:54:33.484743Z","shell.execute_reply.started":"2026-09-21T18:54:33.479407Z","shell.execute_reply":"2026-09-21T18:54:33.484175Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nprint(\"Best model:\",\n      os.path.exists(\"/kaggle/working/best_cifar10_resnet.pth\"))\n\nprint(\"Checkpoint:\",\n      os.path.exists(\"/kaggle/working/latest_checkpoint.pth\"))\n\nprint(\"\\nKaggle input folders:\")\nfor root, dirs, files in os.walk(\"/kaggle/input\"):\n    print(root)\n    if len(files) > 0:\n        print(\"  Example files:\", files[:5])\n    if root.count(os.sep) > 4:\n        dirs[:] = []","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T18:57:06.194676Z","iopub.execute_input":"2026-09-21T18:57:06.195408Z","iopub.status.idle":"2026-09-21T18:57:06.204962Z","shell.execute_reply.started":"2026-09-21T18:57:06.195378Z","shell.execute_reply":"2026-09-21T18:57:06.204141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport subprocess\n\ntest_archive = \"/kaggle/input/competitions/cifar-10/test.7z\"\ntest_dir = \"/kaggle/working/test\"\n\nos.makedirs(test_dir, exist_ok=True)\n\nprint(\"Extracting Kaggle test images...\")\n\nsubprocess.run(\n    [\"7z\", \"x\", test_archive, \"-o/kaggle/working\", \"-y\"],\n    check=True\n)\n\n# Verify extraction\ntest_files = [\n    f for f in os.listdir(test_dir)\n    if f.lower().endswith(\".png\")\n]\n\nprint(\"Extraction complete.\")\nprint(\"Number of test images:\", len(test_files))\nprint(\"First few files:\", test_files[:5])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T18:59:01.334811Z","iopub.execute_input":"2026-09-21T18:59:01.33549Z","iopub.status.idle":"2026-09-21T19:56:15.607303Z","shell.execute_reply.started":"2026-09-21T18:59:01.335458Z","shell.execute_reply":"2026-09-21T19:56:15.606115Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import glob\n\nfiles = glob.glob(\"/kaggle/working/test/*.png\")\nprint(\"Images already extracted:\", len(files))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-21T19:56:35.386969Z","iopub.execute_input":"2026-09-21T19:56:35.387636Z","iopub.status.idle":"2026-09-21T19:56:35.647939Z","shell.execute_reply.started":"2026-09-21T19:56:35.387605Z","shell.execute_reply":"2026-09-21T19:56:35.647099Z"}},"outputs":[],"execution_count":null}]}