{"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":"markdown","source":"# Step 1: Environment Setup, GPU Check, & Reproducibility","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Setting up Multi-GPU, Max CPU Cores, & Random Seeds\n# ============================================================\n\nimport os\nimport random\nimport numpy as np\nimport torch\nimport multiprocessing\n\n# 1. Forcing PyTorch / Linear Algebra libraries to use ALL available CPU cores\nnum_cores_available = multiprocessing.cpu_count()\nos.environ[\"OMP_NUM_THREADS\"] = str(num_cores_available)\nos.environ[\"MKL_NUM_THREADS\"] = str(num_cores_available)\ntorch.set_num_threads(num_cores_available)\ntorch.set_num_interop_threads(num_cores_available)\n\nnum_cores_used = torch.get_num_threads()\nprint(f\"1. PyTorch Thread Count Configured to Use: {num_cores_used} threads/cores\")\n\n# 2. Check for Multiple GPUs\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\ngpu_count = torch.cuda.device_count()\n\nif device.type == 'cuda':\n    print(f\"2. Working Device: cuda with {gpu_count} GPU(s) detected.\")\n    for i in range(gpu_count):\n        print(f\"   - GPU {i}: {torch.cuda.get_device_name(i)}\")\nelse:\n    print(\"2. WARNING: GPU is not enabled! Check your Kaggle accelerator settings.\")\n\n# 3. Set random seeds for exact reproducibility across runs\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntorch.manual_seed(SEED)\nif gpu_count > 0:\n    torch.cuda.manual_seed_all(SEED) # Ensure seeds are set across all GPUs\n\nprint(f\"3. Random seed set to {SEED} for consistent weight initialization.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T17:48:13.750307Z","iopub.execute_input":"2026-09-22T17:48:13.750718Z","iopub.status.idle":"2026-09-22T17:48:15.75979Z","shell.execute_reply.started":"2026-09-22T17:48:13.750687Z","shell.execute_reply":"2026-09-22T17:48:15.75905Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 2: Downloading Dataset, Inspecting Classes, and Checking Data Shapes","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Downloading Dataset, Displaying Classes & Checking Tensor Shapes\n# ============================================================\n\nimport torchvision\nimport torchvision.transforms as transforms\n\n# 1. Defining location where dataset will be stored\nDATA_DIR = './data'\nprint(f\"1. Downloading CIFAR-10 training & validation data to: '{DATA_DIR}'...\\n\")\n\n# 2. Basic transformation to convert images to PyTorch Tensors (values scaled from 0-255 to 0.0-1.0)\nbasic_transform = transforms.ToTensor()\n\n# 3. Downloading Official CIFAR-10 Train and Test splits\nraw_train_set = torchvision.datasets.CIFAR10(root=DATA_DIR, train=True, download=True, transform=basic_transform)\nraw_test_set  = torchvision.datasets.CIFAR10(root=DATA_DIR, train=False, download=True, transform=basic_transform)\n\n# 4. Displaying the 10 String Classes (Categories)\nclasses = raw_train_set.classes\nprint(\"\\n2. The 10 CIFAR-10 Classes:\")\nfor idx, class_name in enumerate(classes):\n    print(f\"   Class [{idx}]: {class_name}\")\n\n# 5. Displaying Dataset Sizes and Tensor Dimensions\nnum_train_samples = len(raw_train_set)\nnum_test_samples  = len(raw_test_set)\n\n# Checking tensor shape: (Channels, Height, Width)\nsample_image_tensor, sample_label = raw_train_set[0]\n\nprint(f\"\\n3. Dataset Summary in Tensor Form:\")\nprint(f\"   - Total Training Images: {num_train_samples}\")\nprint(f\"   - Total Validation Images: {num_test_samples}\")\nprint(f\"   - Single Image Tensor Shape: {sample_image_tensor.shape} (C x H x W)\")\nprint(f\"   - Channels: {sample_image_tensor.shape[0]} (Red, Green, Blue)\")\nprint(f\"   - Height x Width: {sample_image_tensor.shape[1]} x {sample_image_tensor.shape[2]} pixels\")\nprint(f\"   - First Image Label Index: {sample_label} ('{classes[sample_label]}')\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T17:48:25.999275Z","iopub.execute_input":"2026-09-22T17:48:26.000008Z","iopub.status.idle":"2026-09-22T18:24:33.271874Z","shell.execute_reply.started":"2026-09-22T17:48:25.999976Z","shell.execute_reply":"2026-09-22T18:24:33.271189Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 3: Configuring DataLoaders & Inspecting Shapes","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Data Augmentations, Normalization & DataLoaders\n# ============================================================\n\nimport torch\nfrom torch.utils.data import DataLoader\nimport torchvision.transforms as transforms\n\n# 1. CIFAR-10 Channel-wise Mean and Standard Deviation for Normalization\nCIFAR_MEAN = (0.4914, 0.4822, 0.4465)\nCIFAR_STD  = (0.2023, 0.1994, 0.2010)\n\n# 2. Training Augmentation Pipeline\n# We apply random crops, flips, and auto-augment ONLY to training images to improve generalization.\ntrain_transform = transforms.Compose([\n    transforms.RandomCrop(32, padding=4),                         # Slightly pad and randomly crop image\n    transforms.RandomHorizontalFlip(),                            # 50% chance to flip horizontally\n    transforms.AutoAugment(transforms.AutoAugmentPolicy.CIFAR10), # CIFAR-10 specific color/angle augmentations\n    transforms.ToTensor(),                                        # Convert PIL Image to PyTorch Tensor\n    transforms.Normalize(CIFAR_MEAN, CIFAR_STD)                   # Center data around 0 with unit variance\n])\n\n# 3. Validation Transform (No augmentations, just converting to Tensor and Normalizing)\nval_transform = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(CIFAR_MEAN, CIFAR_STD)\n])\n\n# 4. Re-apply transformation to the datasets\ntrain_dataset = torchvision.datasets.CIFAR10(root='./data', train=True, download=False, transform=train_transform)\nval_dataset   = torchvision.datasets.CIFAR10(root='./data', train=False, download=False, transform=val_transform)\n\n# 5. Creating DataLoaders to batch images for the GPU\nBATCH_SIZE_TRAIN = 128\nBATCH_SIZE_VAL   = 256\n\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE_TRAIN, shuffle=True,  num_workers=2, pin_memory=True)\nval_loader   = DataLoader(val_dataset,   batch_size=BATCH_SIZE_VAL,   shuffle=False, num_workers=2, pin_memory=True)\n\nprint(\"1. DataLoaders successfully initialized!\")\nprint(f\"   - Training Batches: {len(train_loader)} batches (128 images per batch)\")\nprint(f\"   - Validation Batches: {len(val_loader)} batches (256 images per batch)\")\n\n# 6. Inspecting a single batch from train_loader\nimages_batch, labels_batch = next(iter(train_loader))\nprint(f\"\\n2. Single Training Batch Tensor Shapes:\")\nprint(f\"   - Images Batch Tensor: {images_batch.shape} -> (Batch Size, Channels, Height, Width)\")\nprint(f\"   - Labels Batch Tensor: {labels_batch.shape} -> (Batch Size,)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T18:24:42.606003Z","iopub.execute_input":"2026-09-22T18:24:42.606559Z","iopub.status.idle":"2026-09-22T18:24:44.137259Z","shell.execute_reply.started":"2026-09-22T18:24:42.606526Z","shell.execute_reply":"2026-09-22T18:24:44.136312Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 4: Building ResNet-18 with random initilization of weights","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# ResNet-18 Architecture & Random Initialization\n# ============================================================\n\nimport torch\nimport torch.nn as nn\nimport torchvision.models as models\n\ndef create_cifar_resnet18():\n    # 1. Standard ResNet-18 without pre-trained weights\n    model = models.resnet18(weights=None)\n    \n    # 2. Modifying entry stem for 32x32 images\n    # Default ResNet has a large 7x7 conv with stride 2 designed for 224x224 images.\n    # Replacing it with a 3x3 conv with stride 1 to keep spatial detail.\n    model.conv1 = nn.Conv2d(in_channels=3, out_channels=64, kernel_size=3, stride=1, padding=1, bias=False)\n    \n    # Removing the initial MaxPool layer (Identity pass-through) to prevent premature downsampling\n    model.maxpool = nn.Identity()\n    \n    # Replacing final classification layer for 10 classes\n    model.fc = nn.Linear(model.fc.in_features, 10)\n    \n    # 3. Applying He / Kaiming Normal Weight Initialization from scratch\n    for m in model.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, nn.GroupNorm)):\n            nn.init.constant_(m.weight, 1)\n            nn.init.constant_(m.bias, 0)\n            \n    return model\n\n# 4. Instantiating model and moving to GPU device\nmodel = create_cifar_resnet18().to(device)\n\nprint(\"1. ResNet-18 successfully created and moved to GPU!\")\nprint(f\"   - Model Output Head: {model.fc}\")\n\n# 5. Counting total trainable parameters\ntotal_params = sum(p.numel() for p in model.parameters() if p.requires_grad)\nprint(f\"2. Total Trainable Parameters: {total_params:,}\")\n\n# 6. Sanity check: Passing a single batch through the network to verify output dimensions\ndummy_input = torch.randn(128, 3, 32, 32).to(device)\ndummy_output = model(dummy_input)\nprint(f\"3. Sanity Output Tensor Shape: {dummy_output.shape} -> (Batch Size, 10 Class Logits)\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T18:24:59.093016Z","iopub.execute_input":"2026-09-22T18:24:59.093602Z","iopub.status.idle":"2026-09-22T18:25:00.145158Z","shell.execute_reply.started":"2026-09-22T18:24:59.093563Z","shell.execute_reply":"2026-09-22T18:25:00.14431Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 5: Model Training & Validation","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Loss, Optimizer, LR Scheduler & Training Loop\n# ============================================================\n\nimport time\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\n\n# 1. Total Epochs & Loss Function\nEPOCHS = 50\ncriterion = nn.CrossEntropyLoss()\n\n# 2. SGD Optimizer with Momentum (0.9) and Weight Decay (5e-4) to prevent overfitting\noptimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)\n\n# 3. Cosine Annealing Learning Rate Scheduler\nscheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=EPOCHS)\n\n# 4. Helper Function: Train One Epoch\ndef train_one_epoch(model, dataloader, criterion, optimizer):\n    model.train()\n    running_loss, correct, total = 0.0, 0, 0\n    \n    for inputs, targets in dataloader:\n        inputs, targets = inputs.to(device), targets.to(device)\n        \n        optimizer.zero_grad()\n        outputs = model(inputs)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n        \n        running_loss += loss.item() * inputs.size(0)\n        _, predicted = outputs.max(1)\n        total += targets.size(0)\n        correct += predicted.eq(targets).sum().item()\n        \n    return running_loss / total, correct / total\n\n# 5. Helper Function: Evaluating Validation Accuracy\n@torch.no_grad()\ndef evaluate(model, dataloader, criterion):\n    model.eval()\n    running_loss, correct, total = 0.0, 0, 0\n    \n    for inputs, targets in dataloader:\n        inputs, targets = inputs.to(device), targets.to(device)\n        \n        outputs = model(inputs)\n        loss = criterion(outputs, targets)\n        \n        running_loss += loss.item() * inputs.size(0)\n        _, predicted = outputs.max(1)\n        total += targets.size(0)\n        correct += predicted.eq(targets).sum().item()\n        \n    return running_loss / total, correct / total\n\n# 6. Executing Training Loop\nbest_val_acc = 0.0\nstart_time = time.time()\n\nprint(\"--- Starting Training Loop ---\")\nfor epoch in range(EPOCHS):\n    train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer)\n    val_loss, val_acc     = evaluate(model, val_loader, criterion)\n    \n    # Steping the learning rate scheduler at the end of each epoch\n    scheduler.step()\n    \n    # Saving checkpoint if current epoch gives highest validation accuracy\n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        torch.save(model.state_dict(), 'best_resnet18_cifar10.pth')\n        saved_str = \" -> [Model Checkpoint Saved!]\"\n    else:\n        saved_str = \"\"\n        \n    print(f\"Epoch {epoch+1:02d}/{EPOCHS:02d} | \"\n          f\"Train Loss: {train_loss:.4f} - Train Acc: {train_acc*100:.2f}% | \"\n          f\"Val Loss: {val_loss:.4f} - Val Acc: {val_acc*100:.2f}% | Best: {best_val_acc*100:.2f}%{saved_str}\")\n\ntotal_time_min = (time.time() - start_time) / 60\nprint(f\"\\n--- Training Complete in {total_time_min:.2f} Minutes! ---\")\nprint(f\"Best Validation Accuracy Reached: {best_val_acc*100:.2f}%\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T18:25:10.037284Z","iopub.execute_input":"2026-09-22T18:25:10.037569Z","iopub.status.idle":"2026-09-22T19:04:03.102543Z","shell.execute_reply.started":"2026-09-22T18:25:10.037544Z","shell.execute_reply":"2026-09-22T19:04:03.101701Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Step 6: Test Extraction & Kaggle Submission","metadata":{}},{"cell_type":"code","source":"# ============================================================\n# Downloading the Kaggle test.7z file directly\n# ============================================================\n!pip install -q kagglehub\nimport kagglehub\nimport shutil\nimport os\n\nprint(\"Downloading official CIFAR-10 competition dataset...\")\n# Downloads directly from the official competition page\npath = kagglehub.competition_download('cifar-10')\nprint(f\"Downloaded to: {path}\")\n\n# Copying test.7z to the local working directory\nsrc_test = os.path.join(path, 'test.7z')\nif os.path.exists(src_test):\n    shutil.copy(src_test, './test.7z')\n    print(\"Successfully found and copied test.7z!\")\nelse:\n    print(\"test.7z is already extracted or available at:\", path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T19:04:10.253375Z","iopub.execute_input":"2026-09-22T19:04:10.253819Z","iopub.status.idle":"2026-09-22T19:04:28.134298Z","shell.execute_reply.started":"2026-09-22T19:04:10.253783Z","shell.execute_reply":"2026-09-22T19:04:28.133525Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Extracting CIFAR-10 test.7z using py7zr\n# ============================================================\n\n!pip install -q py7zr\n\nimport os\nimport glob\nimport shutil\nimport time\nimport py7zr\n\narchive_path = \"./test.7z\"\nextract_dir = \"/tmp/cifar10_kaggle\"\n\n# Removing incomplete extraction from the previous attempt (the cell is now removed)\nif os.path.exists(extract_dir):\n    shutil.rmtree(extract_dir)\n\nos.makedirs(extract_dir, exist_ok=True)\n\nprint(\"Archive:\", archive_path)\nprint(\"Archive size:\", os.path.getsize(archive_path) / (1024**3), \"GB\")\n\nprint(\"\\n--- Extracting test.7z ---\")\nstart = time.time()\n\nwith py7zr.SevenZipFile(archive_path, mode=\"r\") as archive:\n    archive.extractall(path=extract_dir)\n\nelapsed = time.time() - start\n\nprint(f\"\\nExtraction finished in {elapsed/60:.2f} minutes.\")\n\n# Finding PNG files\npng_files = glob.glob(\n    os.path.join(extract_dir, \"**\", \"*.png\"),\n    recursive=True\n)\n\nprint(\"PNG files found:\", len(png_files))\n\nif len(png_files) == 300000:\n    print(\"✅ Exactly 300,000 test images found!\")\nelif len(png_files) > 0:\n    print(f\"⚠️ Found {len(png_files)} images.\")\nelse:\n    raise RuntimeError(\"❌ No PNG files were extracted.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T20:30:10.090824Z","iopub.execute_input":"2026-09-22T20:30:10.091481Z","iopub.status.idle":"2026-09-22T20:33:39.848734Z","shell.execute_reply.started":"2026-09-22T20:30:10.091441Z","shell.execute_reply":"2026-09-22T20:33:39.848025Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Locating & Verifying Extracted Kaggle Test Images\n# ============================================================\n\nimport os\nimport glob\n\nprint(\"--- Locating extracted test images ---\")\n\n# Find all PNG files recursively\npng_files = glob.glob(\n    os.path.join(extract_dir, \"**\", \"*.png\"),\n    recursive=True\n)\n\nprint(f\"Total PNG images found: {len(png_files):,}\")\n\n# Checking expected Kaggle CIFAR-10 test size\nassert len(png_files) == 300000, (\n    f\"Expected 300,000 test images, but found {len(png_files):,}. \"\n    \"Extraction may not have completed correctly.\"\n)\n\n# Determining the folder containing the images\ntest_image_folder = os.path.dirname(png_files[0])\n\nprint(f\"Test image folder: {test_image_folder}\")\n\n# Verifying filenames are numeric and cover 1...300000\ntest_ids = []\n\nfor path in png_files[:1000]:\n    filename = os.path.basename(path)\n    image_id = int(os.path.splitext(filename)[0])\n    test_ids.append(image_id)\n\nprint(f\"Sample IDs: {sorted(test_ids)[:10]} ... {sorted(test_ids)[-10:]}\")\n\n# Checking a few actual images\nfrom PIL import Image\n\nsample_paths = png_files[:5]\n\nprint(\"\\nChecking sample images:\")\n\nfor path in sample_paths:\n    img = Image.open(path)\n    print(\n        f\"  {os.path.basename(path)} \"\n        f\"-> size={img.size}, mode={img.mode}\"\n    )\n\nprint(\"\\n✅ Extraction and image structure verified!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T20:39:45.242071Z","iopub.execute_input":"2026-09-22T20:39:45.24254Z","iopub.status.idle":"2026-09-22T20:39:45.942954Z","shell.execute_reply.started":"2026-09-22T20:39:45.242509Z","shell.execute_reply":"2026-09-22T20:39:45.942307Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Preparing Kaggle Test Dataset & DataLoader\n# ============================================================\n\nfrom torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\nimport os\nimport glob\n\nclass KaggleTestDataset(Dataset):\n\n    def __init__(self, folder_path, transform=None):\n        self.image_paths = glob.glob(\n            os.path.join(folder_path, \"*.png\")\n        )\n\n        # Sorting numerically by image ID\n        self.image_paths.sort(\n            key=lambda x: int(\n                os.path.splitext(\n                    os.path.basename(x)\n                )[0]\n            )\n        )\n\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n\n        path = self.image_paths[idx]\n\n        image = Image.open(path).convert(\"RGB\")\n\n        image_id = int(\n            os.path.splitext(\n                os.path.basename(path)\n            )[0]\n        )\n\n        if self.transform is not None:\n            image = self.transform(image)\n\n        return image_id, image\n\n\n# Creating test dataset using the SAME normalization\n# used for validation.\ntest_dataset = KaggleTestDataset(\n    test_image_folder,\n    transform=val_transform\n)\n\n# Larger batch because inference does not require gradients.\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=1024,\n    shuffle=False,\n    num_workers=4,\n    pin_memory=True\n)\n\nprint(\"============================================\")\nprint(\"KAGGLE TEST DATALOADER READY\")\nprint(\"============================================\")\nprint(f\"Test images : {len(test_dataset):,}\")\nprint(f\"Batch size  : 1,024\")\nprint(f\"Batches     : {len(test_loader):,}\")\nprint(f\"Device      : {device}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T20:40:20.381479Z","iopub.execute_input":"2026-09-22T20:40:20.382316Z","iopub.status.idle":"2026-09-22T20:40:21.308349Z","shell.execute_reply.started":"2026-09-22T20:40:20.382276Z","shell.execute_reply":"2026-09-22T20:40:21.307481Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Loading Best Model & Running Kaggle Test Inference\n# ============================================================\n\nimport torch\nimport time\nfrom tqdm.auto import tqdm\n\n# Official CIFAR-10 class names\nclasses = (\n    'airplane',\n    'automobile',\n    'bird',\n    'cat',\n    'deer',\n    'dog',\n    'frog',\n    'horse',\n    'ship',\n    'truck'\n)\n\n# ------------------------------------------------------------\n# 1. Loading best validation checkpoint\n# ------------------------------------------------------------\n\ncheckpoint_path = \"best_resnet18_cifar10.pth\"\n\nprint(\"--- Loading best model ---\")\nprint(f\"Checkpoint: {checkpoint_path}\")\n\ncheckpoint = torch.load(\n    checkpoint_path,\n    map_location=device\n)\n\nmodel.load_state_dict(checkpoint)\nmodel.to(device)\nmodel.eval()\n\nprint(\"✅ Best model loaded successfully\")\nprint(f\"Device: {device}\")\n\n# ------------------------------------------------------------\n# 2. Running inference\n# ------------------------------------------------------------\n\nprint(\"\\n--- Running inference on 300,000 test images ---\")\n\ntest_ids = []\npredictions = []\n\nstart_time = time.time()\n\nwith torch.no_grad():\n\n    for ids, images in tqdm(\n        test_loader,\n        desc=\"Predicting\",\n        total=len(test_loader)\n    ):\n\n        # Moving images to GPU\n        images = images.to(\n            device,\n            non_blocking=True\n        )\n\n        # Forward pass\n        outputs = model(images)\n\n        # Getting predicted class index\n        predicted_indices = outputs.argmax(dim=1)\n\n        # Storing IDs\n        test_ids.extend(\n            ids.cpu().numpy().tolist()\n        )\n\n        # Converting class indices -> class names\n        predictions.extend([\n            classes[int(i)]\n            for i in predicted_indices.cpu().numpy()\n        ])\n\nelapsed = time.time() - start_time\n\nprint(\"\\n============================================\")\nprint(\"INFERENCE COMPLETE\")\nprint(\"============================================\")\nprint(f\"Images predicted : {len(predictions):,}\")\nprint(f\"Time taken       : {elapsed / 60:.2f} minutes\")\n\n# ------------------------------------------------------------\n# 3. Basic sanity checks\n# ------------------------------------------------------------\n\nassert len(test_ids) == 300000\nassert len(predictions) == 300000\n\nassert all(\n    label in classes\n    for label in predictions\n)\n\nprint(\"✅ 300,000 predictions generated successfully.\")\n\n# Showing first few predictions\nprint(\"\\nFirst 10 predictions:\")\n\nfor image_id, label in list(\n    zip(test_ids, predictions)\n)[:10]:\n\n    print(f\"ID {image_id}: {label}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T20:41:33.39773Z","iopub.execute_input":"2026-09-22T20:41:33.398253Z","iopub.status.idle":"2026-09-22T20:42:51.487247Z","shell.execute_reply.started":"2026-09-22T20:41:33.398202Z","shell.execute_reply":"2026-09-22T20:42:51.486333Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# Creating & Validating Kaggle Submission\n# ============================================================\n\nimport pandas as pd\nimport os\n\nprint(\"--- Creating submission.csv ---\")\n\n# Creating DataFrame\nsubmission = pd.DataFrame({\n    \"id\": test_ids,\n    \"label\": predictions\n})\n\n# ------------------------------------------------------------\n# 1. Sorting by image ID\n# ------------------------------------------------------------\n\nsubmission = submission.sort_values(\n    by=\"id\"\n).reset_index(drop=True)\n\n# ------------------------------------------------------------\n# 2. Validating submission\n# ------------------------------------------------------------\n\nprint(\"\\n--- Validating submission ---\")\n\nassert len(submission) == 300000, \\\n    f\"Expected 300,000 rows, got {len(submission)}\"\n\nassert submission[\"id\"].is_unique, \\\n    \"Duplicate image IDs found!\"\n\nassert submission[\"id\"].min() == 1, \\\n    f\"Minimum ID should be 1, got {submission['id'].min()}\"\n\nassert submission[\"id\"].max() == 300000, \\\n    f\"Maximum ID should be 300000, got {submission['id'].max()}\"\n\nassert submission[\"label\"].isin(classes).all(), \\\n    \"Invalid class label detected!\"\n\n# Verifying IDs are exactly 1...300000\nexpected_ids = range(1, 300001)\n\nassert submission[\"id\"].tolist() == list(expected_ids), \\\n    \"IDs are not exactly 1 through 300000!\"\n\nprint(\"✅ Row count: 300,000\")\nprint(\"✅ IDs: 1 through 300,000\")\nprint(\"✅ IDs are unique\")\nprint(\"✅ All labels are valid\")\n\n# ------------------------------------------------------------\n# 3. Saving submission\n# ------------------------------------------------------------\n\nsubmission_path = \"submission.csv\"\n\nsubmission.to_csv(\n    submission_path,\n    index=False\n)\n\nprint(\"\\n============================================\")\nprint(\"SUBMISSION CREATED SUCCESSFULLY\")\nprint(\"============================================\")\n\nprint(f\"File: {os.path.abspath(submission_path)}\")\nprint(f\"Rows: {len(submission):,}\")\nprint(f\"Size: {os.path.getsize(submission_path) / 1024 / 1024:.2f} MB\")\n\n# ------------------------------------------------------------\n# 4. Displaying first and last rows\n# ------------------------------------------------------------\n\nprint(\"\\nFirst 10 rows:\")\nprint(submission.head(10).to_string(index=False))\n\nprint(\"\\nLast 10 rows:\")\nprint(submission.tail(10).to_string(index=False))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-09-22T20:43:27.763731Z","iopub.execute_input":"2026-09-22T20:43:27.764055Z","iopub.status.idle":"2026-09-22T20:43:28.246464Z","shell.execute_reply.started":"2026-09-22T20:43:27.764016Z","shell.execute_reply":"2026-09-22T20:43:28.245803Z"}},"outputs":[],"execution_count":null}]}