{"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":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!apt install libarchive-dev\n\n!pip install libarchive\n\n# CIFAR-10 Kaggle Competition Solution\n# Date: September 2025\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, Dataset\nimport torchvision\nimport torchvision.transforms as transforms\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport os\nimport glob\nimport libarchive.public\nfrom sklearn.metrics import accuracy_score\nimport matplotlib.pyplot as plt\n\n# Set device\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\n\n# CIFAR-10 class names\nCIFAR10_CLASSES = ['airplane', 'automobile', 'bird', 'cat', 'deer', \n                   'dog', 'frog', 'horse', 'ship', 'truck']\n\nclass ResidualBlock(nn.Module):\n    \"\"\"Enhanced Residual block with stochastic depth\"\"\"\n    def __init__(self, in_channels, out_channels, stride=1, drop_rate=0.0):\n        super(ResidualBlock, self).__init__()\n        \n        self.drop_rate = drop_rate\n        \n        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, \n                              stride=stride, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(out_channels)\n        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, \n                              stride=1, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_channels)\n        \n        # Skip connection\n        self.skip = nn.Sequential()\n        if stride != 1 or in_channels != out_channels:\n            self.skip = nn.Sequential(\n                nn.Conv2d(in_channels, out_channels, kernel_size=1, \n                         stride=stride, bias=False),\n                nn.BatchNorm2d(out_channels)\n            )\n    \n    def forward(self, x):\n        residual = self.skip(x)\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        \n        # Stochastic depth: randomly skip the residual connection during training\n        if self.training and self.drop_rate > 0:\n            if torch.rand(1).item() < self.drop_rate:\n                out = residual\n            else:\n                out = (out + residual) / (1 - self.drop_rate)  # Scale to maintain expected value\n        else:\n            out = out + residual\n            \n        out = F.relu(out)\n        return out\n\nclass CIFAR10ConvNet(nn.Module):\n    \"\"\"\n    Custom ConvNet for CIFAR-10 classification\n    Architecture: Modified ResNet-style with attention mechanism\n    \"\"\"\n    def __init__(self, num_classes=10):\n        super(CIFAR10ConvNet, self).__init__()\n        \n        # Initial convolution\n        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(64)\n        \n        # Residual blocks with stochastic depth\n        drop_rates = [0.0, 0.1, 0.2, 0.3]  # Increasing drop rates for deeper layers\n        self.layer1 = self._make_layer(64, 64, 2, stride=1, drop_rate=drop_rates[0])\n        self.layer2 = self._make_layer(64, 128, 2, stride=2, drop_rate=drop_rates[1])\n        self.layer3 = self._make_layer(128, 256, 2, stride=2, drop_rate=drop_rates[2])\n        self.layer4 = self._make_layer(256, 512, 2, stride=2, drop_rate=drop_rates[3])\n        \n        # Global average pooling and classifier\n        self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))\n        self.dropout = nn.Dropout(0.5)\n        self.fc = nn.Linear(512, num_classes)\n        \n        # Initialize weights\n        self._initialize_weights()\n    \n    def _make_layer(self, in_channels, out_channels, num_blocks, stride, drop_rate=0.0):\n        layers = []\n        layers.append(ResidualBlock(in_channels, out_channels, stride, drop_rate))\n        for _ in range(1, num_blocks):\n            layers.append(ResidualBlock(out_channels, out_channels, 1, drop_rate))\n        return nn.Sequential(*layers)\n    \n    def _initialize_weights(self):\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)\n                nn.init.constant_(m.bias, 0)\n            elif isinstance(m, nn.Linear):\n                nn.init.normal_(m.weight, 0, 0.01)\n                nn.init.constant_(m.bias, 0)\n    \n    def forward(self, x):\n        x = F.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.avg_pool(x)\n        x = x.view(x.size(0), -1)\n        x = self.dropout(x)\n        x = self.fc(x)\n        \n        return x\n\nclass KaggleTestDataset(Dataset):\n    \"\"\"Dataset class for Kaggle test images\"\"\"\n    def __init__(self, image_dir, transform=None):\n        self.image_paths = sorted(glob.glob(os.path.join(image_dir, '*.png')))\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.image_paths)\n    \n    def __getitem__(self, idx):\n        image_path = self.image_paths[idx]\n        image = Image.open(image_path).convert('RGB')\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        # Extract image ID from filename\n        image_id = int(os.path.basename(image_path).split('.')[0])\n        \n        return image, image_id\n\ndef extract_test_data():\n    \"\"\"Extract test.7z file\"\"\"\n    print(\"Extracting test.7z...\")\n    cnt = 0\n    for entry in libarchive.public.file_pour('./test.7z'):\n        cnt += 1\n        if cnt % 1000 == 0:\n            print(f\"Extracted {cnt} files\")\n    \n    # Verify extraction\n    num_files = len(glob.glob('test/*'))\n    print(f\"Total extracted files: {num_files}\")\n    return num_files\n\ndef get_data_transforms():\n    \"\"\"Enhanced data augmentation with RandAugment\"\"\"\n    \n    # Training transforms with RandAugment\n    train_transform = transforms.Compose([\n        transforms.RandomCrop(32, padding=4),\n        transforms.RandomHorizontalFlip(p=0.5),\n        transforms.RandAugment(num_ops=2, magnitude=9),  # CHANGED: Use RandAugment\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], \n                           std=[0.2470, 0.2435, 0.2616])  # CHANGED: Use correct std\n    ])\n    \n    # Test transforms\n    test_transform = transforms.Compose([\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], \n                           std=[0.2470, 0.2435, 0.2616])  # CHANGED: Use correct std\n    ])\n    \n    return train_transform, test_transform\n\ndef train_model(model, train_loader, val_loader, num_epochs=200, lr=0.2):  # CHANGED: Higher LR\n    \"\"\"Enhanced training with SGD and mixed precision\"\"\"\n    \n    # CHANGED: Use CrossEntropy with label smoothing built-in\n    criterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n    \n    # CHANGED: Use SGD instead of AdamW\n    optimizer = optim.SGD(model.parameters(), lr=lr, momentum=0.9, \n                         weight_decay=5e-4, nesterov=True)\n    \n    # CHANGED: Cosine annealing with higher epochs\n    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=num_epochs)\n    \n    # ADDED: Mixed precision scaler\n    scaler = torch.cuda.amp.GradScaler(enabled=(device.type=='cuda'))\n    \n    train_losses = []\n    val_accuracies = []\n    best_val_acc = 0.0\n    \n    print(\"Starting enhanced training with SGD + Mixed Precision...\")\n    \n    for epoch in range(num_epochs):\n        # Training phase\n        model.train()\n        running_loss = 0.0\n        \n        for batch_idx, (data, target) in enumerate(train_loader):\n            data, target = data.to(device, non_blocking=True), target.to(device, non_blocking=True)\n            \n            optimizer.zero_grad(set_to_none=True)  # CHANGED: More efficient\n            \n            # ADDED: Mixed precision forward pass\n            with torch.cuda.amp.autocast(enabled=(device.type=='cuda')):\n                output = model(data)\n                loss = criterion(output, target)\n            \n            # CHANGED: Mixed precision backward pass\n            scaler.scale(loss).backward()\n            scaler.step(optimizer)\n            scaler.update()\n            \n            running_loss += loss.item()\n            \n            if batch_idx % 100 == 0:\n                print(f'Epoch: {epoch+1}/{num_epochs}, Batch: {batch_idx}/{len(train_loader)}, '\n                      f'Loss: {loss.item():.6f}')\n        \n        # Validation phase\n        model.eval()\n        val_loss = 0.0\n        correct = 0\n        total = 0\n        \n        with torch.no_grad():\n            for data, target in val_loader:\n                data, target = data.to(device, non_blocking=True), target.to(device, non_blocking=True)\n                \n                # ADDED: Mixed precision for validation too\n                with torch.cuda.amp.autocast(enabled=(device.type=='cuda')):\n                    output = model(data)\n                    val_loss += criterion(output, target).item()\n                \n                _, predicted = torch.max(output.data, 1)\n                total += target.size(0)\n                correct += (predicted == target).sum().item()\n        \n        val_acc = 100. * correct / total\n        avg_train_loss = running_loss / len(train_loader)\n        avg_val_loss = val_loss / len(val_loader)\n        \n        train_losses.append(avg_train_loss)\n        val_accuracies.append(val_acc)\n        \n        print(f'Epoch: {epoch+1}/{num_epochs}')\n        print(f'Train Loss: {avg_train_loss:.6f}, Val Loss: {avg_val_loss:.6f}, Val Acc: {val_acc:.2f}%')\n        \n        # Save best model\n        if val_acc > best_val_acc:\n            best_val_acc = val_acc\n            torch.save({'model': model.state_dict(), 'acc': best_val_acc, 'epoch': epoch}, 'best_model.pth')\n            print(f'New best model saved with validation accuracy: {val_acc:.2f}%')\n        \n        scheduler.step()\n        print(f'Learning rate: {scheduler.get_last_lr()[0]:.6f}')\n        print('-' * 60)\n    \n    return train_losses, val_accuracies, best_val_acc\n\ndef evaluate_on_cifar10_test(model):\n    \"\"\"Evaluate model on official CIFAR-10 test set\"\"\"\n    _, test_transform = get_data_transforms()\n    \n    # Load CIFAR-10 test set\n    test_dataset = torchvision.datasets.CIFAR10(root='./data', train=False,\n                                               download=True, transform=test_transform)\n    test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=2)\n    \n    model.eval()\n    correct = 0\n    total = 0\n    \n    with torch.no_grad():\n        for data, target in test_loader:\n            data, target = data.to(device), target.to(device)\n            output = model(data)\n            _, predicted = torch.max(output, 1)\n            total += target.size(0)\n            correct += (predicted == target).sum().item()\n    \n    accuracy = 100. * correct / total\n    print(f'CIFAR-10 Test Accuracy: {accuracy:.2f}%')\n    return accuracy\n\ndef create_kaggle_submission(model, test_dir='test'):\n    \"\"\"Create submission file for Kaggle - FIXED VERSION\"\"\"\n    \n    _, test_transform = get_data_transforms()\n    \n    # Create test dataset\n    test_dataset = KaggleTestDataset(test_dir, transform=test_transform)\n    test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=2)\n    \n    model.eval()\n    predictions = []\n    image_ids = []\n    \n    print(\"Making predictions on Kaggle test set...\")\n    \n    with torch.no_grad():\n        for batch_idx, (data, ids) in enumerate(test_loader):\n            data = data.to(device, non_blocking=True)\n            \n            # ADDED: Mixed precision for inference\n            with torch.cuda.amp.autocast(enabled=(device.type=='cuda')):\n                output = model(data)\n            \n            _, predicted = torch.max(output, 1)\n            \n            # FIXED: Convert to class names instead of numbers\n            class_names = [CIFAR10_CLASSES[i] for i in predicted.cpu().numpy()]\n            predictions.extend(class_names)\n            image_ids.extend(ids.numpy())\n            \n            if batch_idx % 100 == 0:\n                print(f'Processed {batch_idx * test_loader.batch_size} images')\n    \n    # Create submission DataFrame\n    submission_df = pd.DataFrame({\n        'id': image_ids,\n        'label': predictions  # Now contains class names like 'airplane', 'cat', etc.\n    })\n    \n    # Sort by id to ensure correct order\n    submission_df = submission_df.sort_values('id').reset_index(drop=True)\n    \n    # Save submission file\n    submission_df.to_csv('submission.csv', index=False)\n    print(f\"Submission file created with {len(submission_df)} predictions\")\n    print(\"Sample predictions:\")\n    print(submission_df.head(10))\n    \n    return submission_df\n\ndef main():\n    \"\"\"Main training and evaluation pipeline with improvements\"\"\"\n    \n    # Set random seeds for reproducibility\n    torch.manual_seed(42)\n    np.random.seed(42)\n    \n    # Get enhanced transforms\n    train_transform, test_transform = get_data_transforms()\n    \n    # Load CIFAR-10 training data\n    print(\"Loading CIFAR-10 training data...\")\n    train_dataset_full = torchvision.datasets.CIFAR10(root='./data', train=True,\n                                                      download=True, transform=train_transform)\n    \n    # Create validation dataset with test transforms\n    val_dataset_full = torchvision.datasets.CIFAR10(root='./data', train=True,\n                                                    download=False, transform=test_transform)\n    \n    # Split both datasets with same indices\n    train_size = int(0.95 * len(train_dataset_full))\n    val_size = len(train_dataset_full) - train_size\n    \n    # Use same random generator seed for consistent splits\n    generator = torch.Generator().manual_seed(42)\n    train_indices = torch.randperm(len(train_dataset_full), generator=generator)\n    \n    train_idx = train_indices[:train_size]\n    val_idx = train_indices[train_size:]\n    \n    # Create subsets\n    train_dataset = torch.utils.data.Subset(train_dataset_full, train_idx)\n    val_dataset = torch.utils.data.Subset(val_dataset_full, val_idx)\n    \n    # CHANGED: Add pin_memory for faster data loading\n    train_loader = DataLoader(train_dataset, batch_size=128, shuffle=True, \n                             num_workers=2, pin_memory=True)\n    val_loader = DataLoader(val_dataset, batch_size=128, shuffle=False, \n                           num_workers=2, pin_memory=True)\n    \n    print(f\"Training samples: {len(train_dataset)}\")\n    print(f\"Validation samples: {len(val_dataset)}\")\n    \n    # Initialize model\n    print(\"Initializing enhanced model...\")\n    model = CIFAR10ConvNet(num_classes=10).to(device)\n    \n    # CHANGED: Train with SGD and higher epochs\n    train_losses, val_accuracies, best_val_acc = train_model(\n        model, train_loader, val_loader, num_epochs=200, lr=0.2\n    )\n    \n    # CHANGED: Load best model checkpoint properly\n    checkpoint = torch.load('best_model.pth')\n    model.load_state_dict(checkpoint['model'])\n    print(f\"Loaded best model with validation accuracy: {checkpoint['acc']:.2f}%\")\n    \n    # Evaluate on CIFAR-10 test set\n    cifar10_accuracy = evaluate_on_cifar10_test(model)\n    \n    # Create Kaggle submission - FIXED VERSION\n    submission_df = create_kaggle_submission(model)\n    \n    print(\"\\n\" + \"=\"*60)\n    print(\"TRAINING COMPLETE!\")\n    print(f\"Best Validation Accuracy: {best_val_acc:.2f}%\")\n    print(f\"CIFAR-10 Test Accuracy: {cifar10_accuracy:.2f}%\")\n    print(\"Submission file: submission.csv\")\n    print(\"Model saved as: best_model.pth\")\n    print(\"=\"*60)\n\ndef create_kaggle_submission_tta(model, test_dir='test', num_tta=5):\n    \"\"\"Create submission with TTA - FIXED VERSION\"\"\"\n    \n    # Simpler TTA transforms\n    tta_transforms = [\n        transforms.Compose([\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], \n                               std=[0.2470, 0.2435, 0.2616])\n        ]),\n        transforms.Compose([\n            transforms.RandomHorizontalFlip(p=1.0),\n            transforms.ToTensor(),\n            transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], \n                               std=[0.2470, 0.2435, 0.2616])\n        ]),\n        # Add more transforms...\n    ]\n    \n    # Create test dataset\n    test_dataset = KaggleTestDataset(test_dir, transform=None)\n    test_loader = DataLoader(test_dataset, batch_size=32, shuffle=False, num_workers=2)\n    \n    model.eval()\n    all_predictions = []\n    image_ids = []\n    \n    with torch.no_grad():\n        for batch_idx, (raw_images, ids) in enumerate(test_loader):\n            batch_preds = []\n            \n            for tta_transform in tta_transforms:\n                pil_images = [transforms.ToPILImage()(img) for img in raw_images]\n                transformed_batch = torch.stack([tta_transform(img) for img in pil_images])\n                \n                data_device = transformed_batch.to(device, non_blocking=True)\n                with torch.cuda.amp.autocast(enabled=(device.type=='cuda')):\n                    output = model(data_device)\n                batch_preds.append(F.softmax(output, dim=1))\n            \n            avg_pred = torch.stack(batch_preds).mean(dim=0)\n            _, predicted = torch.max(avg_pred, 1)\n            \n            # FIXED: Convert to class names\n            class_names = [CIFAR10_CLASSES[i] for i in predicted.cpu().numpy()]\n            all_predictions.extend(class_names)\n            image_ids.extend(ids.numpy())\n    \n    submission_df = pd.DataFrame({\n        'id': image_ids,\n        'label': all_predictions\n    })\n    \n    submission_df = submission_df.sort_values('id').reset_index(drop=True)\n    submission_df.to_csv('submission_tta.csv', index=False)\n    print(f\"TTA Submission file created with {len(submission_df)} predictions\")\n    \n    return submission_df\n    \nif __name__ == \"__main__\":\n    main()\n\n# Model Architecture Summary:\n\"\"\"\nCIFAR10ConvNet Architecture:\n- Input: 3x32x32 RGB images\n- Conv1: 3->64 channels, 3x3 kernel\n- ResBlock layers with skip connections:\n  - Layer1: 64->64 channels (2 blocks)\n  - Layer2: 64->128 channels (2 blocks, stride=2)\n  - Layer3: 128->256 channels (2 blocks, stride=2)  \n  - Layer4: 256->512 channels (2 blocks, stride=2)\n- Global Average Pooling\n- Dropout (0.5)\n- Fully Connected: 512->10 classes\n\nTraining Details:\n- Optimizer: Adam with weight decay (1e-4)\n- Learning rate: 0.001 with StepLR scheduler\n- Data augmentation: RandomHorizontalFlip, RandomRotation, ColorJitter, RandomCrop\n- Batch size: 128\n- Epochs: 100\n- Loss function: CrossEntropyLoss\n\nExpected Performance: >88% accuracy on CIFAR-10 test set\n\"\"\"","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-09-23T04:14:11.009038Z","iopub.execute_input":"2025-09-23T04:14:11.0093Z","iopub.status.idle":"2025-09-23T05:35:01.443077Z","shell.execute_reply.started":"2025-09-23T04:14:11.009277Z","shell.execute_reply":"2025-09-23T05:35:01.44214Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Method 1: Using Kaggle's display function (Recommended)\nfrom IPython.display import FileLink\nimport os\n\n# After creating your submission.csv\nif os.path.exists('submission.csv'):\n    print(\"Submission file created successfully!\")\n    print(f\"File size: {os.path.getsize('submission.csv')} bytes\")\n    \n    # This creates a downloadable link in Kaggle notebooks\n    display(FileLink('submission.csv'))\nelse:\n    print(\"submission.csv not found!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-23T05:44:58.571651Z","iopub.execute_input":"2025-09-23T05:44:58.571876Z","iopub.status.idle":"2025-09-23T05:44:58.577488Z","shell.execute_reply.started":"2025-09-23T05:44:58.571858Z","shell.execute_reply":"2025-09-23T05:44:58.576906Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import DataLoader, Dataset\nimport torchvision.transforms as transforms\nimport pandas as pd\nfrom PIL import Image\nimport os\nimport glob\nimport libarchive.public\nfrom IPython.display import FileLink\n\n# Set device\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\nprint(f\"Using device: {device}\")\n\n# CIFAR-10 class names\nCIFAR10_CLASSES = ['airplane', 'automobile', 'bird', 'cat', 'deer', \n                   'dog', 'frog', 'horse', 'ship', 'truck']\n\n# Copy your model architecture classes here (or they should already be defined)\nclass ResidualBlock(nn.Module):\n    \"\"\"Enhanced Residual block with stochastic depth\"\"\"\n    def __init__(self, in_channels, out_channels, stride=1, drop_rate=0.0):\n        super(ResidualBlock, self).__init__()\n        \n        self.drop_rate = drop_rate\n        \n        self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3, \n                              stride=stride, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(out_channels)\n        self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3, \n                              stride=1, padding=1, bias=False)\n        self.bn2 = nn.BatchNorm2d(out_channels)\n        \n        # Skip connection\n        self.skip = nn.Sequential()\n        if stride != 1 or in_channels != out_channels:\n            self.skip = nn.Sequential(\n                nn.Conv2d(in_channels, out_channels, kernel_size=1, \n                         stride=stride, bias=False),\n                nn.BatchNorm2d(out_channels)\n            )\n    \n    def forward(self, x):\n        residual = self.skip(x)\n        out = F.relu(self.bn1(self.conv1(x)))\n        out = self.bn2(self.conv2(out))\n        \n        # Stochastic depth: randomly skip the residual connection during training\n        if self.training and self.drop_rate > 0:\n            if torch.rand(1).item() < self.drop_rate:\n                out = residual\n            else:\n                out = (out + residual) / (1 - self.drop_rate)\n        else:\n            out = out + residual\n            \n        out = F.relu(out)\n        return out\n\nclass CIFAR10ConvNet(nn.Module):\n    \"\"\"Custom ConvNet for CIFAR-10 classification\"\"\"\n    def __init__(self, num_classes=10):\n        super(CIFAR10ConvNet, self).__init__()\n        \n        # Initial convolution\n        self.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False)\n        self.bn1 = nn.BatchNorm2d(64)\n        \n        # Residual blocks with stochastic depth\n        drop_rates = [0.0, 0.1, 0.2, 0.3]\n        self.layer1 = self._make_layer(64, 64, 2, stride=1, drop_rate=drop_rates[0])\n        self.layer2 = self._make_layer(64, 128, 2, stride=2, drop_rate=drop_rates[1])\n        self.layer3 = self._make_layer(128, 256, 2, stride=2, drop_rate=drop_rates[2])\n        self.layer4 = self._make_layer(256, 512, 2, stride=2, drop_rate=drop_rates[3])\n        \n        # Global average pooling and classifier\n        self.avg_pool = nn.AdaptiveAvgPool2d((1, 1))\n        self.dropout = nn.Dropout(0.5)\n        self.fc = nn.Linear(512, num_classes)\n        \n        # Initialize weights\n        self._initialize_weights()\n    \n    def _make_layer(self, in_channels, out_channels, num_blocks, stride, drop_rate=0.0):\n        layers = []\n        layers.append(ResidualBlock(in_channels, out_channels, stride, drop_rate))\n        for _ in range(1, num_blocks):\n            layers.append(ResidualBlock(out_channels, out_channels, 1, drop_rate))\n        return nn.Sequential(*layers)\n    \n    def _initialize_weights(self):\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)\n                nn.init.constant_(m.bias, 0)\n            elif isinstance(m, nn.Linear):\n                nn.init.normal_(m.weight, 0, 0.01)\n                nn.init.constant_(m.bias, 0)\n    \n    def forward(self, x):\n        x = F.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.avg_pool(x)\n        x = x.view(x.size(0), -1)\n        x = self.dropout(x)\n        x = self.fc(x)\n        \n        return x\n\nclass KaggleTestDataset(Dataset):\n    \"\"\"Dataset class for Kaggle test images\"\"\"\n    def __init__(self, image_dir, transform=None):\n        self.image_paths = sorted(glob.glob(os.path.join(image_dir, '*.png')))\n        self.transform = transform\n        \n    def __len__(self):\n        return len(self.image_paths)\n    \n    def __getitem__(self, idx):\n        image_path = self.image_paths[idx]\n        image = Image.open(image_path).convert('RGB')\n        \n        if self.transform:\n            image = self.transform(image)\n            \n        # Extract image ID from filename\n        image_id = int(os.path.basename(image_path).split('.')[0])\n        \n        return image, image_id\n\ndef extract_test_data():\n    \"\"\"Extract test.7z file from Kaggle input directory\"\"\"\n    test_archive_path = '/kaggle/input/cifar-10/test.7z'\n    print(f\"Extracting {test_archive_path}...\")\n    cnt = 0\n    for entry in libarchive.public.file_pour(test_archive_path):\n        cnt += 1\n        if cnt % 1000 == 0:\n            print(f\"Extracted {cnt} files\")\n    \n    # Verify extraction\n    num_files = len(glob.glob('test/*'))\n    print(f\"Total extracted files: {num_files}\")\n    return num_files\n\ndef load_model_and_predict():\n    \"\"\"Load saved model and make predictions\"\"\"\n    \n    # Check if model file exists\n    if not os.path.exists('best_model.pth'):\n        print(\"❌ best_model.pth not found! Please run training first.\")\n        return\n    \n    # Extract test data if needed\n    extract_test_data()\n    \n    # Load the trained model\n    print(\"📦 Loading trained model...\")\n    model = CIFAR10ConvNet(num_classes=10).to(device)\n    \n    checkpoint = torch.load('best_model.pth', map_location=device)\n    model.load_state_dict(checkpoint['model'])\n    print(f\"✅ Model loaded! Best validation accuracy: {checkpoint['acc']:.2f}%\")\n    \n    # Set model to evaluation mode\n    model.eval()\n    \n    # Define test transforms (same as training)\n    test_transform = transforms.Compose([\n        transforms.ToTensor(),\n        transforms.Normalize(mean=[0.4914, 0.4822, 0.4465], \n                           std=[0.2470, 0.2435, 0.2616])\n    ])\n    \n    # Create test dataset and dataloader\n    print(\"📁 Loading test images...\")\n    test_dataset = KaggleTestDataset('test', transform=test_transform)\n    test_loader = DataLoader(test_dataset, batch_size=128, shuffle=False, num_workers=2)\n    \n    print(f\"Found {len(test_dataset)} test images\")\n    \n    # Make predictions\n    print(\"🔮 Making predictions...\")\n    predictions = []\n    image_ids = []\n    \n    with torch.no_grad():\n        for batch_idx, (data, ids) in enumerate(test_loader):\n            data = data.to(device, non_blocking=True)\n            \n            # Mixed precision for faster inference\n            with torch.cuda.amp.autocast(enabled=(device.type=='cuda')):\n                output = model(data)\n            \n            _, predicted = torch.max(output, 1)\n            \n            # Convert to class names\n            class_names = [CIFAR10_CLASSES[i] for i in predicted.cpu().numpy()]\n            predictions.extend(class_names)\n            image_ids.extend(ids.numpy())\n            \n            if batch_idx % 50 == 0:\n                print(f'Processed {batch_idx * test_loader.batch_size}/{len(test_dataset)} images')\n    \n    # Create submission DataFrame\n    print(\"📝 Creating submission file...\")\n    submission_df = pd.DataFrame({\n        'id': image_ids,\n        'label': predictions\n    })\n    \n    # Sort by id to ensure correct order\n    submission_df = submission_df.sort_values('id').reset_index(drop=True)\n    \n    # Save submission file\n    submission_df.to_csv('submission.csv', index=False)\n    print(f\"✅ Submission file created with {len(submission_df)} predictions\")\n    \n    # Show sample predictions\n    print(\"\\n📊 Sample predictions:\")\n    print(submission_df.head(10))\n    \n    # Show class distribution\n    print(\"\\n📈 Prediction distribution:\")\n    print(submission_df['label'].value_counts().sort_index())\n    \n    # Create download link\n    print(\"\\n📁 Download your submission file:\")\n    display(FileLink('submission.csv'))\n    \n    # Also copy to working directory\n    import shutil\n    shutil.copy('submission.csv', '/kaggle/working/submission.csv')\n    print(\"✅ File also copied to /kaggle/working/ for easy access\")\n    \n    return submission_df\n\n# Run the inference\nif __name__ == \"__main__\":\n    submission_df = load_model_and_predict()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-23T05:58:58.791632Z","iopub.execute_input":"2025-09-23T05:58:58.792415Z","iopub.status.idle":"2025-09-23T06:01:21.355709Z","shell.execute_reply.started":"2025-09-23T05:58:58.792386Z","shell.execute_reply":"2025-09-23T06:01:21.354656Z"}},"outputs":[],"execution_count":null}]}