{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"},{"sourceId":13254059,"sourceType":"datasetVersion","datasetId":8398805}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ===================\n# 1. Imports & Settings\n# ===================\nimport os, glob\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.optim.lr_scheduler import LambdaLR\nfrom torch.optim.swa_utils import AveragedModel\nfrom torch.utils.data import DataLoader, random_split, Dataset\nimport torchvision\nimport torchvision.transforms as transforms\n\nbatch_size = 128\nnum_epochs = 200\nbase_lr = 0.001   # reduced for stability\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(\"Using device:\", device)\n\n# ===================\n# 2. Compute CIFAR-10 Mean & Std\n# ===================\nraw_trainset = torchvision.datasets.CIFAR10(\n    root='./data', train=True, download=True, transform=transforms.ToTensor()\n)\nraw_loader = DataLoader(raw_trainset, batch_size=500, shuffle=False, num_workers=2)\n\nmean = 0.0\nstd = 0.0\nnb_samples = 0\nfor images, _ in raw_loader:\n    batch_samples = images.size(0)\n    images = images.view(batch_samples, images.size(1), -1)\n    mean += images.mean(2).sum(0)\n    std += images.std(2).sum(0)\n    nb_samples += batch_samples\n\nmean /= nb_samples\nstd /= nb_samples\nprint(\"CIFAR10 mean:\", mean, \"std:\", std)\n\n# ===================\n# 3. Data Augmentation\n# ===================\ntransform_train = transforms.Compose([\n    transforms.RandomCrop(32, padding=4),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std)\n])\n\ntransform_test = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std)\n])\n\n# Split train into train+val\nfull_trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_train)\ntrain_size = int(0.9 * len(full_trainset))\nval_size = len(full_trainset) - train_size\ntrainset, valset = random_split(full_trainset, [train_size, val_size])\n\ntrainloader = DataLoader(trainset, batch_size=batch_size, shuffle=True, num_workers=2)\nvalloader = DataLoader(valset, batch_size=batch_size, shuffle=False, num_workers=2)\n\nclass_names = full_trainset.classes\n\n# ===================\n# 4. Custom ConvNet with He Init + Dropout\n# ===================\nclass CustomCNN(nn.Module):\n    def __init__(self, num_classes=10):\n        super().__init__()\n        self.features = nn.Sequential(\n            nn.Conv2d(3, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(),\n            nn.Conv2d(64, 64, 3, padding=1), nn.BatchNorm2d(64), nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(64, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(),\n            nn.Conv2d(128, 128, 3, padding=1), nn.BatchNorm2d(128), nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(128, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(),\n            nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(),\n            nn.MaxPool2d(2),\n\n            nn.Conv2d(256, 512, 3, padding=1), nn.BatchNorm2d(512), nn.ReLU(),\n            nn.Conv2d(512, 512, 3, padding=1), nn.BatchNorm2d(512), nn.ReLU(),\n            nn.AdaptiveAvgPool2d((1,1))\n        )\n        self.classifier = nn.Sequential(\n            nn.Dropout(0.3),\n            nn.Linear(512, num_classes)\n        )\n        self._init_weights()\n\n    def _init_weights(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                nn.init.kaiming_normal_(m.weight)\n            elif isinstance(m, nn.Linear):\n                nn.init.xavier_normal_(m.weight)\n\n    def forward(self, x):\n        x = self.features(x)\n        x = torch.flatten(x, 1)\n        return self.classifier(x)\n\nnet = CustomCNN().to(device)\n\n# ===================\n# 5. Optimizer & Scheduler\n# ===================\noptimizer = optim.AdamW(net.parameters(), lr=base_lr, weight_decay=1e-4)\n\ndef lr_lambda(current_step):\n    warmup_steps = 500\n    total_steps = num_epochs * len(trainloader)\n    if current_step < warmup_steps:\n        return float(current_step) / float(max(1, warmup_steps))\n    progress = float(current_step - warmup_steps) / float(max(1, total_steps - warmup_steps))\n    return 0.5 * (1.0 + np.cos(np.pi * progress))\n\nscheduler = LambdaLR(optimizer, lr_lambda)\ncriterion = nn.CrossEntropyLoss(label_smoothing=0.1)\n\n# SWA setup (last 20 epochs)\nswa_model = AveragedModel(net)\nswa_start = 180\n\n# ===================\n# 6. CutMix (Less Frequent)\n# ===================\ndef rand_bbox(size, lam):\n    W = size[2]; H = size[3]\n    cut_rat = np.sqrt(1. - lam)\n    cut_w = int(W * cut_rat)\n    cut_h = int(H * cut_rat)\n    cx = np.random.randint(W)\n    cy = np.random.randint(H)\n    bbx1 = np.clip(cx - cut_w // 2, 0, W)\n    bby1 = np.clip(cy - cut_h // 2, 0, H)\n    bbx2 = np.clip(cx + cut_w // 2, 0, W)\n    bby2 = np.clip(cy + cut_h // 2, 0, H)\n    return bbx1, bby1, bbx2, bby2\n\ndef cutmix_data(x, y, alpha=1.0):\n    lam = np.random.beta(alpha, alpha)\n    rand_index = torch.randperm(x.size()[0]).to(x.device)\n    target_a = y\n    target_b = y[rand_index]\n    bbx1, bby1, bbx2, bby2 = rand_bbox(x.size(), lam)\n    x[:, :, bbx1:bbx2, bby1:bby2] = x[rand_index, :, bbx1:bbx2, bby1:bby2]\n    lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (x.size()[-1] * x.size()[-2]))\n    return x, target_a, target_b, lam\n\n# ===================\n# 7. Training Loop (CutMix 30%, SWA after 180)\n# ===================\nfor epoch in range(num_epochs):\n    net.train()\n    running_loss, correct, total = 0.0, 0, 0\n\n    for i, (inputs, targets) in enumerate(trainloader):\n        inputs, targets = inputs.to(device), targets.to(device)\n        optimizer.zero_grad()\n\n        # Apply CutMix with lower probability\n        if np.random.rand() < 0.3:\n            inputs, targets_a, targets_b, lam = cutmix_data(inputs, targets)\n            outputs = net(inputs)\n            loss = lam * criterion(outputs, targets_a) + (1 - lam) * criterion(outputs, targets_b)\n        else:\n            outputs = net(inputs)\n            loss = criterion(outputs, targets)\n\n        loss.backward()\n        optimizer.step()\n        scheduler.step()\n\n        running_loss += loss.item()\n        _, predicted = outputs.max(1)\n        total += targets.size(0)\n        correct += predicted.eq(targets).sum().item()\n\n    acc = 100. * correct / total\n    print(f\"Epoch {epoch+1}/{num_epochs} Loss: {running_loss/len(trainloader):.4f} Train Acc: {acc:.2f}%\")\n\n    # Validation\n    net.eval()\n    val_correct, val_total = 0, 0\n    with torch.no_grad():\n        for x, y in valloader:\n            x, y = x.to(device), y.to(device)\n            out = net(x)\n            _, pred = out.max(1)\n            val_total += y.size(0)\n            val_correct += pred.eq(y).sum().item()\n    val_acc = 100. * val_correct / val_total\n    print(f\"Validation Acc: {val_acc:.2f}%\")\n\n    if epoch >= swa_start:\n        swa_model.update_parameters(net)\n\n# ===================\n# 8. Kaggle Test Dataset\n# ===================\ntest_dir = \"/kaggle/input/cifar-dataset/test\"\nprint(\"Found test images:\", len(glob.glob(os.path.join(test_dir, '*.png'))))\n\nclass TestDataset(Dataset):\n    def __init__(self, files, transform=None):\n        self.files = files\n        self.transform = transform\n    def __len__(self):\n        return len(self.files)\n    def __getitem__(self, idx):\n        img_path = self.files[idx]\n        image = Image.open(img_path)\n        if self.transform:\n            image = self.transform(image)\n        return image, os.path.splitext(os.path.basename(img_path))[0]\n\ntest_files = sorted(glob.glob(os.path.join(test_dir, '*.png')))\ntest_dataset = TestDataset(test_files, transform=transform_test)\ntest_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)\n\n# ===================\n# 9. Predictions & Submission\n# ===================\nswa_model.eval()\nall_preds, all_ids = [], []\nwith torch.no_grad():\n    for inputs, ids in tqdm(test_loader, desc=\"Predicting\"):\n        inputs = inputs.to(device)\n        outputs = swa_model(inputs)\n        _, predicted = outputs.max(1)\n        all_preds.extend(predicted.cpu().numpy())\n        all_ids.extend(ids)\n\nsubmission = pd.DataFrame({\n    \"id\": all_ids,\n    \"label\": [class_names[p] for p in all_preds]\n})\nsubmission.to_csv(\"/kaggle/working/submission.csv\", index=False)\nprint(\"✅ Submission file saved: /kaggle/working/submission.csv\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-04T11:59:00.283484Z","iopub.execute_input":"2025-10-04T11:59:00.283681Z","iopub.status.idle":"2025-10-04T13:35:47.050070Z","shell.execute_reply.started":"2025-10-04T11:59:00.283665Z","shell.execute_reply":"2025-10-04T13:35:47.049200Z"}},"outputs":[],"execution_count":null}]}