{"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":"import os\nimport random\nimport numpy as np\nimport pandas as pd\n\nfrom pathlib import Path\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom torchvision import transforms\n\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import LabelEncoder\n\nfrom tqdm.auto import tqdm\n\n\n\nSEED = 42\n\nIMG_SIZE = 128\nBATCH_SIZE = 64\n\nNUM_WORKERS = 4\n\nEPOCHS = 20\nLEARNING_RATE = 1e-3\nWEIGHT_DECAY = 1e-4\n\nDEVICE = torch.device(\n    \"cuda\" if torch.cuda.is_available() else \"cpu\"\n)\n\nprint(\"Device:\", DEVICE)\n\n\ndef seed_everything(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n\n\nseed_everything(SEED)\n\ntorch.backends.cudnn.benchmark = True","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:09:44.579547Z","iopub.execute_input":"2026-08-31T16:09:44.580047Z","iopub.status.idle":"2026-08-31T16:09:53.518431Z","shell.execute_reply.started":"2026-08-31T16:09:44.580014Z","shell.execute_reply":"2026-08-31T16:09:53.517645Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\n\nprint(\"PyTorch version:\", torch.__version__)\nprint(\"CUDA available:\", torch.cuda.is_available())\n\nif torch.cuda.is_available():\n    print(\"GPU:\", torch.cuda.get_device_name(0))\n    print(\"CUDA version:\", torch.version.cuda)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:09:25.408380Z","iopub.execute_input":"2026-08-31T16:09:25.408726Z","iopub.status.idle":"2026-08-31T16:09:30.158353Z","shell.execute_reply.started":"2026-08-31T16:09:25.408702Z","shell.execute_reply":"2026-08-31T16:09:30.157490Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for root, dirs, files in os.walk(\"/kaggle/input\"):\n\n    level = root.replace(\"/kaggle/input\", \"\").count(os.sep)\n\n    if level < 2:\n\n        print(root)\n\n        for f in files[:10]:\n            print(\"   \", f)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:10:01.456467Z","iopub.execute_input":"2026-08-31T16:10:01.457295Z","iopub.status.idle":"2026-08-31T16:10:18.398128Z","shell.execute_reply.started":"2026-08-31T16:10:01.457263Z","shell.execute_reply":"2026-08-31T16:10:18.397069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from pathlib import Path\n\nDATA_DIR = Path(\n    \"/kaggle/input/competitions/dog-breed-identification\"\n)\n\nLABELS_PATH = DATA_DIR / \"labels.csv\"\nSAMPLE_PATH = DATA_DIR / \"sample_submission.csv\"\n\nTRAIN_DIR = DATA_DIR / \"train\"\nTEST_DIR = DATA_DIR / \"test\"\n\nprint(\"Labels:\", LABELS_PATH.exists())\nprint(\"Sample:\", SAMPLE_PATH.exists())\nprint(\"Train:\", TRAIN_DIR.exists())\nprint(\"Test:\", TEST_DIR.exists())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:10:22.725673Z","iopub.execute_input":"2026-08-31T16:10:22.726077Z","iopub.status.idle":"2026-08-31T16:10:22.733097Z","shell.execute_reply.started":"2026-08-31T16:10:22.726047Z","shell.execute_reply":"2026-08-31T16:10:22.732322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"labels = pd.read_csv(LABELS_PATH)\nsample_submission = pd.read_csv(SAMPLE_PATH)\n\nprint(labels.head())\n\nprint(\"\\nNumber of images:\", len(labels))\nprint(\"Number of breeds:\", labels[\"breed\"].nunique())\n\nprint(\"\\nSubmission columns:\", len(sample_submission.columns))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:10:26.553304Z","iopub.execute_input":"2026-08-31T16:10:26.553579Z","iopub.status.idle":"2026-08-31T16:10:27.104489Z","shell.execute_reply.started":"2026-08-31T16:10:26.553557Z","shell.execute_reply":"2026-08-31T16:10:27.103667Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"label_encoder = LabelEncoder()\n\nlabels[\"label\"] = label_encoder.fit_transform(\n    labels[\"breed\"]\n)\n\nNUM_CLASSES = len(label_encoder.classes_)\n\nprint(\"Number of classes:\", NUM_CLASSES)\n\nprint(\"\\nBreeds:\")\n\nfor i, breed in enumerate(label_encoder.classes_):\n    print(i, breed)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:10:30.437260Z","iopub.execute_input":"2026-08-31T16:10:30.437815Z","iopub.status.idle":"2026-08-31T16:10:30.446758Z","shell.execute_reply.started":"2026-08-31T16:10:30.437787Z","shell.execute_reply":"2026-08-31T16:10:30.445764Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df, val_df = train_test_split(\n    labels,\n    test_size=0.15,\n    random_state=SEED,\n    stratify=labels[\"label\"]\n)\n\ntrain_df = train_df.reset_index(drop=True)\nval_df = val_df.reset_index(drop=True)\n\nprint(\"Training images:\", len(train_df))\nprint(\"Validation images:\", len(val_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:10:34.985020Z","iopub.execute_input":"2026-08-31T16:10:34.985801Z","iopub.status.idle":"2026-08-31T16:10:35.008154Z","shell.execute_reply.started":"2026-08-31T16:10:34.985767Z","shell.execute_reply":"2026-08-31T16:10:35.007523Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_transform = transforms.Compose([\n\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n\n    transforms.RandomHorizontalFlip(p=0.5),\n\n    transforms.RandomRotation(15),\n\n    transforms.RandomAffine(\n        degrees=0,\n        translate=(0.1, 0.1),\n        scale=(0.9, 1.1)\n    ),\n\n    transforms.ColorJitter(\n        brightness=0.2,\n        contrast=0.2,\n        saturation=0.2\n    ),\n\n    transforms.ToTensor(),\n\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    ),\n\n    transforms.RandomErasing(\n        p=0.2\n    )\n])\n\n\nval_transform = transforms.Compose([\n\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n\n    transforms.ToTensor(),\n\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:10:40.276666Z","iopub.execute_input":"2026-08-31T16:10:40.277302Z","iopub.status.idle":"2026-08-31T16:10:40.283650Z","shell.execute_reply.started":"2026-08-31T16:10:40.277268Z","shell.execute_reply":"2026-08-31T16:10:40.282684Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class DogDataset(Dataset):\n\n    def __init__(\n        self,\n        df,\n        image_dir,\n        transform=None,\n        train=True\n    ):\n\n        self.df = df.reset_index(drop=True)\n        self.image_dir = Path(image_dir)\n        self.transform = transform\n        self.train = train\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n\n        row = self.df.iloc[idx]\n\n        image_id = str(row[\"id\"])\n\n        # Handle IDs with or without .jpg\n        if image_id.lower().endswith(\".jpg\"):\n            image_filename = image_id\n        else:\n            image_filename = image_id + \".jpg\"\n\n        image_path = self.image_dir / image_filename\n\n        # Helpful debugging if something is wrong\n        if not image_path.exists():\n            raise FileNotFoundError(\n                f\"\\nImage not found!\\n\"\n                f\"Path: {image_path}\\n\"\n                f\"ID: {image_id}\\n\"\n                f\"Directory exists: {self.image_dir.exists()}\"\n            )\n\n        image = Image.open(\n            image_path\n        ).convert(\"RGB\")\n\n        if self.transform:\n            image = self.transform(image)\n\n        if self.train:\n            label = int(row[\"label\"])\n            return image, label\n\n        return image, image_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:10:44.503062Z","iopub.execute_input":"2026-08-31T16:10:44.503354Z","iopub.status.idle":"2026-08-31T16:10:44.510552Z","shell.execute_reply.started":"2026-08-31T16:10:44.503330Z","shell.execute_reply":"2026-08-31T16:10:44.509834Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = DogDataset(\n    train_df,\n    TRAIN_DIR,\n    transform=train_transform,\n    train=True\n)\n\nval_dataset = DogDataset(\n    val_df,\n    TRAIN_DIR,\n    transform=val_transform,\n    train=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:10:50.307797Z","iopub.execute_input":"2026-08-31T16:10:50.308346Z","iopub.status.idle":"2026-08-31T16:10:50.314162Z","shell.execute_reply.started":"2026-08-31T16:10:50.308312Z","shell.execute_reply":"2026-08-31T16:10:50.313211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_loader = DataLoader(\n    train_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=True,\n    num_workers=NUM_WORKERS,\n    pin_memory=True,\n    persistent_workers=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n    num_workers=NUM_WORKERS,\n    pin_memory=True,\n    persistent_workers=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:10:54.852948Z","iopub.execute_input":"2026-08-31T16:10:54.853412Z","iopub.status.idle":"2026-08-31T16:10:54.859099Z","shell.execute_reply.started":"2026-08-31T16:10:54.853376Z","shell.execute_reply":"2026-08-31T16:10:54.858193Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SimpleCNN(nn.Module):\n\n    def __init__(self, num_classes=120):\n\n        super().__init__()\n\n\n       \n\n        self.block1 = nn.Sequential(\n\n            nn.Conv2d(\n                3,\n                32,\n                kernel_size=3,\n                padding=1\n            ),\n\n            nn.BatchNorm2d(32),\n\n            nn.ReLU(inplace=True),\n\n            nn.MaxPool2d(2)\n        )\n\n\n       \n        self.block2 = nn.Sequential(\n\n            nn.Conv2d(\n                32,\n                64,\n                kernel_size=3,\n                padding=1\n            ),\n\n            nn.BatchNorm2d(64),\n\n            nn.ReLU(inplace=True),\n\n            nn.MaxPool2d(2)\n        )\n\n\n        \n        self.block3 = nn.Sequential(\n\n            nn.Conv2d(\n                64,\n                128,\n                kernel_size=3,\n                padding=1\n            ),\n\n            nn.BatchNorm2d(128),\n\n            nn.ReLU(inplace=True),\n\n            nn.MaxPool2d(2)\n        )\n\n\n\n        self.block4 = nn.Sequential(\n\n            nn.Conv2d(\n                128,\n                256,\n                kernel_size=3,\n                padding=1\n            ),\n\n            nn.BatchNorm2d(256),\n\n            nn.ReLU(inplace=True),\n\n            nn.MaxPool2d(2)\n        )\n\n\n      \n        self.global_pool = nn.AdaptiveAvgPool2d(\n            (1, 1)\n        )\n\n        self.dropout = nn.Dropout(0.5)\n\n        self.fc = nn.Linear(\n            256,\n            num_classes\n        )\n\n\n    def forward(self, x):\n\n        x = self.block1(x)\n\n        x = self.block2(x)\n\n        x = self.block3(x)\n\n        x = self.block4(x)\n\n        x = self.global_pool(x)\n\n        x = torch.flatten(\n            x,\n            start_dim=1\n        )\n\n        x = self.dropout(x)\n\n        x = self.fc(x)\n\n        return x","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:10:57.651894Z","iopub.execute_input":"2026-08-31T16:10:57.652197Z","iopub.status.idle":"2026-08-31T16:10:57.660547Z","shell.execute_reply.started":"2026-08-31T16:10:57.652170Z","shell.execute_reply":"2026-08-31T16:10:57.659880Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = SimpleCNN(\n    num_classes=NUM_CLASSES\n)\n\nmodel = model.to(DEVICE)\n\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:11:03.811688Z","iopub.execute_input":"2026-08-31T16:11:03.812496Z","iopub.status.idle":"2026-08-31T16:11:04.120704Z","shell.execute_reply.started":"2026-08-31T16:11:03.812465Z","shell.execute_reply":"2026-08-31T16:11:04.119665Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"total_params = sum(\n    p.numel()\n    for p in model.parameters()\n)\n\nprint(\n    f\"\\nTotal parameters: \"\n    f\"{total_params:,}\"\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:11:07.249947Z","iopub.execute_input":"2026-08-31T16:11:07.250232Z","iopub.status.idle":"2026-08-31T16:11:07.255466Z","shell.execute_reply.started":"2026-08-31T16:11:07.250207Z","shell.execute_reply":"2026-08-31T16:11:07.254423Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss(\n    label_smoothing=0.1\n)\n\noptimizer = torch.optim.AdamW(\n    model.parameters(),\n    lr=LEARNING_RATE,\n    weight_decay=WEIGHT_DECAY\n)\n\nscheduler = torch.optim.lr_scheduler.CosineAnnealingLR(\n    optimizer,\n    T_max=EPOCHS\n)\n\nscaler = torch.amp.GradScaler(\n    \"cuda\"\n) if DEVICE.type == \"cuda\" else None","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:11:09.953402Z","iopub.execute_input":"2026-08-31T16:11:09.953942Z","iopub.status.idle":"2026-08-31T16:11:09.959231Z","shell.execute_reply.started":"2026-08-31T16:11:09.953910Z","shell.execute_reply":"2026-08-31T16:11:09.958278Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(\n    model,\n    loader,\n    criterion,\n    optimizer\n):\n\n    model.train()\n\n    running_loss = 0.0\n    correct = 0\n    total = 0\n\n    progress = tqdm(\n        loader,\n        desc=\"Training\",\n        leave=False\n    )\n\n    for images, targets in progress:\n\n        images = images.to(\n            DEVICE,\n            non_blocking=True\n        )\n\n        targets = targets.to(\n            DEVICE,\n            non_blocking=True\n        )\n\n        optimizer.zero_grad(\n            set_to_none=True\n        )\n\n\n        if DEVICE.type == \"cuda\":\n\n            with torch.autocast(\n                device_type=\"cuda\",\n                dtype=torch.float16\n            ):\n\n                outputs = model(images)\n\n                loss = criterion(\n                    outputs,\n                    targets\n                )\n\n            scaler.scale(loss).backward()\n\n            scaler.step(optimizer)\n\n            scaler.update()\n\n\n        else:\n\n            outputs = model(images)\n\n            loss = criterion(\n                outputs,\n                targets\n            )\n\n            loss.backward()\n\n            optimizer.step()\n\n\n        running_loss += (\n            loss.item() *\n            images.size(0)\n        )\n\n        predictions = outputs.argmax(\n            dim=1\n        )\n\n        correct += (\n            predictions == targets\n        ).sum().item()\n\n        total += targets.size(0)\n\n        progress.set_postfix(\n            loss=f\"{loss.item():.4f}\"\n        )\n\n\n    epoch_loss = (\n        running_loss / total\n    )\n\n    accuracy = (\n        correct / total\n    )\n\n    return epoch_loss, accuracy","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:11:13.218659Z","iopub.execute_input":"2026-08-31T16:11:13.219067Z","iopub.status.idle":"2026-08-31T16:11:13.226211Z","shell.execute_reply.started":"2026-08-31T16:11:13.219037Z","shell.execute_reply":"2026-08-31T16:11:13.225348Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def validate(\n    model,\n    loader,\n    criterion\n):\n\n    model.eval()\n\n    running_loss = 0.0\n\n    total = 0\n\n    correct = 0\n\n    all_probs = []\n\n    all_targets = []\n\n\n    with torch.no_grad():\n\n        for images, targets in tqdm(\n            loader,\n            desc=\"Validation\",\n            leave=False\n        ):\n\n            images = images.to(\n                DEVICE,\n                non_blocking=True\n            )\n\n            targets = targets.to(\n                DEVICE,\n                non_blocking=True\n            )\n\n\n            if DEVICE.type == \"cuda\":\n\n                with torch.autocast(\n                    device_type=\"cuda\",\n                    dtype=torch.float16\n                ):\n\n                    outputs = model(images)\n\n                    loss = criterion(\n                        outputs,\n                        targets\n                    )\n\n            else:\n\n                outputs = model(images)\n\n                loss = criterion(\n                    outputs,\n                    targets\n                )\n\n\n            probs = torch.softmax(\n                outputs,\n                dim=1\n            )\n\n\n            running_loss += (\n                loss.item() *\n                images.size(0)\n            )\n\n            total += images.size(0)\n\n\n            predictions = outputs.argmax(\n                dim=1\n            )\n\n            correct += (\n                predictions == targets\n            ).sum().item()\n\n\n            all_probs.append(\n                probs.cpu().numpy()\n            )\n\n            all_targets.append(\n                targets.cpu().numpy()\n            )\n\n\n    avg_loss = (\n        running_loss / total\n    )\n\n    accuracy = (\n        correct / total\n    )\n\n\n    probs = np.concatenate(\n        all_probs\n    )\n\n    targets = np.concatenate(\n        all_targets\n    )\n\n\n    # Multiclass log loss\n    eps = 1e-15\n\n    probs = np.clip(\n        probs,\n        eps,\n        1 - eps\n    )\n\n    log_loss = -np.mean(\n        np.log(\n            probs[\n                np.arange(len(targets)),\n                targets\n            ]\n        )\n    )\n\n\n    return (\n        avg_loss,\n        log_loss,\n        accuracy\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:11:17.873832Z","iopub.execute_input":"2026-08-31T16:11:17.874312Z","iopub.status.idle":"2026-08-31T16:11:17.884252Z","shell.execute_reply.started":"2026-08-31T16:11:17.874282Z","shell.execute_reply":"2026-08-31T16:11:17.883453Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_logloss = float(\"inf\")\n\npatience = 5\n\npatience_counter = 0\n\n\nfor epoch in range(EPOCHS):\n\n    print(\n        f\"\\n==============================\"\n    )\n\n    print(\n        f\"Epoch {epoch + 1}/{EPOCHS}\"\n    )\n\n    print(\n        f\"==============================\"\n    )\n\n\n    train_loss, train_acc = train_one_epoch(\n        model,\n        train_loader,\n        criterion,\n        optimizer\n    )\n\n\n    val_loss, val_logloss, val_acc = validate(\n        model,\n        val_loader,\n        criterion\n    )\n\n\n    scheduler.step()\n\n\n    print(\n        f\"\\nTrain Loss: {train_loss:.4f}\"\n    )\n\n    print(\n        f\"Train Accuracy: {train_acc:.4f}\"\n    )\n\n    print(\n        f\"Validation Loss: {val_loss:.4f}\"\n    )\n\n    print(\n        f\"Validation Accuracy: {val_acc:.4f}\"\n    )\n\n    print(\n        f\"Validation Log Loss: {val_logloss:.4f}\"\n    )\n\n\n    # Save best model\n    if val_logloss < best_logloss:\n\n        best_logloss = val_logloss\n\n        torch.save(\n            model.state_dict(),\n            \"/kaggle/working/simple_cnn_best.pth\"\n        )\n\n        patience_counter = 0\n\n        print(\n            \"🔥 Best model saved!\"\n        )\n\n    else:\n\n        patience_counter += 1\n\n        print(\n            f\"No improvement \"\n            f\"({patience_counter}/{patience})\"\n        )\n\n\n    if patience_counter >= patience:\n\n        print(\n            \"\\nEarly stopping.\"\n        )\n\n        break","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:11:24.194111Z","iopub.execute_input":"2026-08-31T16:11:24.194573Z","iopub.status.idle":"2026-08-31T16:19:50.621062Z","shell.execute_reply.started":"2026-08-31T16:11:24.194540Z","shell.execute_reply":"2026-08-31T16:19:50.620071Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(\n    torch.load(\n        \"/kaggle/working/simple_cnn_best.pth\",\n        map_location=DEVICE\n    )\n)\n\nmodel.eval()\n\nprint(\n    \"Best validation log loss:\",\n    best_logloss\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:20:24.734473Z","iopub.execute_input":"2026-08-31T16:20:24.734915Z","iopub.status.idle":"2026-08-31T16:20:24.754385Z","shell.execute_reply.started":"2026-08-31T16:20:24.734823Z","shell.execute_reply":"2026-08-31T16:20:24.753717Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_ids = sample_submission[\"id\"].values\n\ntest_df = pd.DataFrame({\n    \"id\": test_ids\n})\n\n\ntest_dataset = DogDataset(\n    test_df,\n    TEST_DIR,\n    transform=val_transform,\n    train=False\n)\n\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=BATCH_SIZE,\n    shuffle=False,\n    num_workers=NUM_WORKERS,\n    pin_memory=True,\n    persistent_workers=True\n)\n\n\nprint(\n    \"Test images:\",\n    len(test_dataset)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:20:36.568468Z","iopub.execute_input":"2026-08-31T16:20:36.568942Z","iopub.status.idle":"2026-08-31T16:20:36.578284Z","shell.execute_reply.started":"2026-08-31T16:20:36.568911Z","shell.execute_reply":"2026-08-31T16:20:36.577472Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def predict_test(\n    model,\n    loader\n):\n\n    model.eval()\n\n    predictions = []\n\n    image_ids = []\n\n\n    with torch.no_grad():\n\n        for images, ids in tqdm(\n            loader,\n            desc=\"Predicting\"\n        ):\n\n            images = images.to(\n                DEVICE,\n                non_blocking=True\n            )\n\n\n            if DEVICE.type == \"cuda\":\n\n                with torch.autocast(\n                    device_type=\"cuda\",\n                    dtype=torch.float16\n                ):\n\n                    outputs = model(images)\n\n            else:\n\n                outputs = model(images)\n\n\n            probs = torch.softmax(\n                outputs,\n                dim=1\n            )\n\n\n            predictions.append(\n                probs.cpu().numpy()\n            )\n\n            image_ids.extend(ids)\n\n\n    predictions = np.concatenate(\n        predictions,\n        axis=0\n    )\n\n\n    return image_ids, predictions\n\n\ntest_ids_pred, test_probs = predict_test(\n    model,\n    test_loader\n)\n\n\nprint(\n    \"Prediction shape:\",\n    test_probs.shape\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:20:59.497282Z","iopub.execute_input":"2026-08-31T16:20:59.498052Z","iopub.status.idle":"2026-08-31T16:21:32.907946Z","shell.execute_reply.started":"2026-08-31T16:20:59.498017Z","shell.execute_reply":"2026-08-31T16:21:32.906840Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = pd.DataFrame(\n    test_probs,\n    columns=label_encoder.classes_\n)\n\n\nsubmission.insert(\n    0,\n    \"id\",\n    test_ids_pred\n)\n\n\n# EXACT column order required by Kaggle\nsubmission = submission[\n    sample_submission.columns\n]\n\n\nprint(\n    submission.head()\n)\n\nprint(\n    \"\\nShape:\",\n    submission.shape\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:22:14.067780Z","iopub.execute_input":"2026-08-31T16:22:14.068919Z","iopub.status.idle":"2026-08-31T16:22:14.109476Z","shell.execute_reply.started":"2026-08-31T16:22:14.068826Z","shell.execute_reply":"2026-08-31T16:22:14.108626Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"probability_columns = (\n    sample_submission.columns[1:]\n)\n\nrow_sums = submission[\n    probability_columns\n].sum(axis=1)\n\n\nprint(\n    \"Minimum probability:\",\n    submission[\n        probability_columns\n    ].min().min()\n)\n\nprint(\n    \"Maximum probability:\",\n    submission[\n        probability_columns\n    ].max().max()\n)\n\nprint(\n    \"Maximum row-sum error:\",\n    np.abs(\n        row_sums - 1\n    ).max()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:22:26.481608Z","iopub.execute_input":"2026-08-31T16:22:26.481932Z","iopub.status.idle":"2026-08-31T16:22:26.532329Z","shell.execute_reply.started":"2026-08-31T16:22:26.481907Z","shell.execute_reply":"2026-08-31T16:22:26.531605Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_path = (\n    \"/kaggle/working/submission.csv\"\n)\n\nsubmission.to_csv(\n    submission_path,\n    index=False\n)\n\nprint(\n    \"Submission saved to:\",\n    submission_path\n)\n\ndisplay(\n    submission.head()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-08-31T16:22:45.243323Z","iopub.execute_input":"2026-08-31T16:22:45.244003Z","iopub.status.idle":"2026-08-31T16:22:46.290067Z","shell.execute_reply.started":"2026-08-31T16:22:45.243970Z","shell.execute_reply":"2026-08-31T16:22:46.289362Z"}},"outputs":[],"execution_count":null}]}