{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","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":13836,"databundleVersionId":1718836,"sourceType":"competition"},{"sourceId":656417,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":496135,"modelId":511540}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"52407cc4-c12a-436d-b46a-b9356e5b4f26","cell_type":"code","source":"\n# !pip install -q timm==0.9.12 albumentations==1.4.14 fastprogress\n\nimport os\nimport random\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nimport cv2\n\nimport torch\nimport torch.nn as nn\nfrom torch.optim import AdamW\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.amp import GradScaler, autocast\n\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import accuracy_score\n\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom fastprogress import progress_bar\n\n\nclass CFG:\n    seed = 42\n    model_name = 'convnext_tiny'\n    pretrained = True\n    img_size = 384          \n    batch_size = 32        \n    epochs = 6\n    lr = 3e-4\n    weight_decay = 1e-5\n    n_folds = 5\n    num_workers = 4\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n\n    # data paths (Kaggle default path)\n    base_path = '/kaggle/input/cassava-leaf-disease-classification'\n    train_csv = os.path.join(base_path, 'train.csv')\n    train_dir = os.path.join(base_path, 'train_images')\n    sample_submission = os.path.join(base_path, 'sample_submission.csv')\n\n    output_dir = '/kaggle/working/models'\n    os.makedirs(output_dir, exist_ok=True)\n\n    # normalization (ImageNet)\n    mean = (0.485, 0.456, 0.406)\n    std = (0.229, 0.224, 0.225)\n\n    use_amp = True\n    early_stop_rounds = 3\n\n# -----------------------------\n# Seed\n# -----------------------------\ndef seed_everything(seed=CFG.seed):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n\nseed_everything()\n\n# -----------------------------\n# Albumentations compatibility helper\n# The library's RandomResizedCrop signature varies by version; choose a robust call.\n# -----------------------------\n\ndef rr_crop(size, **kwargs):\n    \"\"\"Return a RandomResizedCrop augmentation compatible with multiple albumentations versions.\n    size: int (height and width)\n    \"\"\"\n    try:\n        # recent versions expect size=(h,w)\n        return A.RandomResizedCrop(size=(size, size), **kwargs)\n    except Exception:\n        try:\n            # older versions accept positional ints\n            return A.RandomResizedCrop(size, size, **kwargs)\n        except Exception:\n            # fallback to Resize + RandomCrop\n            return A.RandomCrop(height=size, width=size, **kwargs)\n\n# -----------------------------\n# Transforms\n# -----------------------------\n\ndef get_transforms(img_size=CFG.img_size):\n    train_transforms = A.Compose([\n        rr_crop(img_size, scale=(0.7, 1.0), p=1.0),\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.2),\n        A.ShiftScaleRotate(shift_limit=0.06, scale_limit=0.06, rotate_limit=15, p=0.4),\n        A.RandomBrightnessContrast(p=0.5),\n        A.OneOf([A.GaussNoise(var_limit=(10.0, 50.0)), A.ISONoise()], p=0.2),\n        A.Normalize(mean=CFG.mean, std=CFG.std),\n        ToTensorV2()\n    ])\n\n    valid_transforms = A.Compose([\n        A.Resize(height=img_size, width=img_size),\n        A.Normalize(mean=CFG.mean, std=CFG.std),\n        ToTensorV2()\n    ])\n\n    test_transforms = valid_transforms\n    return train_transforms, valid_transforms, test_transforms\n\ntrain_transforms, valid_transforms, test_transforms = get_transforms()\n\n# -----------------------------\n# Dataset\n# -----------------------------\nclass CassavaDataset(Dataset):\n    def __init__(self, df, img_dir, transforms=None, is_test=False):\n        self.df = df.reset_index(drop=True)\n        self.img_dir = Path(img_dir)\n        self.transforms = transforms\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = self.img_dir / row['image_id']\n        img = cv2.imread(str(img_path))\n        if img is None:\n            raise FileNotFoundError(f'Image not found: {img_path}')\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        if self.transforms:\n            img = self.transforms(image=img)['image']\n        if self.is_test:\n            return img, row['image_id']\n        label = int(row['label'])\n        return img, label\n\n# -----------------------------\n# Model wrapper\n# -----------------------------\nclass CassavaModel(nn.Module):\n    def __init__(self, model_name=CFG.model_name, pretrained=CFG.pretrained, num_classes=5):\n        super().__init__()\n        self.backbone = timm.create_model(model_name, pretrained=pretrained, num_classes=num_classes)\n\n    def forward(self, x):\n        return self.backbone(x)\n\n# -----------------------------\n# Training utilities\n# -----------------------------\ncriterion = nn.CrossEntropyLoss()\n\nfrom torch.optim.lr_scheduler import OneCycleLR\n\n\ndef train_one_epoch(model, loader, optimizer, scheduler, device, scaler):\n    model.train()\n    running_loss = 0.0\n    preds = []\n    targets = []\n    for images, labels in loader:\n        images = images.to(device)\n        labels = labels.to(device)\n        optimizer.zero_grad()\n        with autocast(device_type='cuda' if torch.cuda.is_available() else 'cpu', enabled=CFG.use_amp and torch.cuda.is_available()):\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        if scheduler is not None:\n            try:\n                scheduler.step()\n            except Exception:\n                pass\n        running_loss += loss.item() * images.size(0)\n        preds.extend(torch.argmax(outputs, dim=1).detach().cpu().numpy().tolist())\n        targets.extend(labels.detach().cpu().numpy().tolist())\n    epoch_loss = running_loss / len(loader.dataset)\n    epoch_acc = accuracy_score(targets, preds)\n    return epoch_loss, epoch_acc\n\n\ndef valid_one_epoch(model, loader, device):\n    model.eval()\n    running_loss = 0.0\n    preds = []\n    targets = []\n    with torch.no_grad():\n        for images, labels in loader:\n            images = images.to(device)\n            labels = labels.to(device)\n            outputs = model(images)\n            loss = criterion(outputs, labels)\n            running_loss += loss.item() * images.size(0)\n            preds.extend(torch.argmax(outputs, dim=1).detach().cpu().numpy().tolist())\n            targets.extend(labels.detach().cpu().numpy().tolist())\n    epoch_loss = running_loss / len(loader.dataset)\n    epoch_acc = accuracy_score(targets, preds)\n    return epoch_loss, epoch_acc\n\n# -----------------------------\n# K-Fold training\n# -----------------------------\n\ndef run_kfold(df):\n    skf = StratifiedKFold(n_splits=CFG.n_folds, shuffle=True, random_state=CFG.seed)\n    fold_scores = []\n    for fold, (train_idx, val_idx) in enumerate(skf.split(df, df.label)):\n        print(f\"\\n===== Fold {fold} =====\")\n        train_df = df.iloc[train_idx].reset_index(drop=True)\n        val_df = df.iloc[val_idx].reset_index(drop=True)\n\n        train_ds = CassavaDataset(train_df, CFG.train_dir, transforms=train_transforms)\n        val_ds = CassavaDataset(val_df, CFG.train_dir, transforms=valid_transforms)\n\n        train_loader = DataLoader(train_ds, batch_size=CFG.batch_size, shuffle=True, num_workers=CFG.num_workers, pin_memory=True)\n        val_loader = DataLoader(val_ds, batch_size=CFG.batch_size, shuffle=False, num_workers=CFG.num_workers, pin_memory=True)\n\n        model = CassavaModel().to(CFG.device)\n        optimizer = AdamW(model.parameters(), lr=CFG.lr, weight_decay=CFG.weight_decay)\n        steps_per_epoch = int(len(train_loader))\n        scheduler = OneCycleLR(optimizer, max_lr=CFG.lr, total_steps=max(1, CFG.epochs * steps_per_epoch))\n        scaler = GradScaler(enabled=torch.cuda.is_available())\n\n        best_acc = 0.0\n        no_improve = 0\n        t0 = time.time()\n        for epoch in range(CFG.epochs):\n            ep_start = time.time()\n            train_loss, train_acc = train_one_epoch(model, train_loader, optimizer, scheduler, CFG.device, scaler)\n            val_loss, val_acc = valid_one_epoch(model, val_loader, CFG.device)\n            ep_time = time.time() - ep_start\n            print(f\"Epoch {epoch+1}/{CFG.epochs} - train_loss: {train_loss:.4f} train_acc: {train_acc:.4f} | val_loss: {val_loss:.4f} val_acc: {val_acc:.4f} | epoch_time: {ep_time:.1f}s\")\n            if val_acc > best_acc:\n                best_acc = val_acc\n                torch.save(model.state_dict(), os.path.join(CFG.output_dir, f\"best_fold{fold}.pth\"))\n                no_improve = 0\n            else:\n                no_improve += 1\n            if no_improve >= CFG.early_stop_rounds:\n                print('Early stopping')\n                break\n        fold_time = time.time() - t0\n        print(f\"Fold {fold} best acc: {best_acc:.4f} | fold_time: {fold_time/60:.2f}min\")\n        fold_scores.append(best_acc)\n    print('\\n==== CV ===> mean: {:.4f} std: {:.4f}'.format(np.mean(fold_scores), np.std(fold_scores)))\n    return fold_scores\n\n# -----------------------------\n# Inference skeleton with simple TTA (horizontal flip)\n# -----------------------------\n\ndef predict_test(models_paths, test_df):\n    ds = CassavaDataset(test_df, CFG.train_dir, transforms=test_transforms, is_test=True)\n    loader = DataLoader(ds, batch_size=CFG.batch_size, shuffle=False, num_workers=CFG.num_workers)\n    preds = np.zeros((len(test_df), 5))\n    for mp in models_paths:\n        model = CassavaModel().to(CFG.device)\n        model.load_state_dict(torch.load(mp, map_location=CFG.device))\n        model.eval()\n        all_out = []\n        with torch.no_grad():\n            for images, img_ids in loader:\n                images = images.to(CFG.device)\n                outputs = model(images)\n                all_out.append(torch.softmax(outputs, dim=1).cpu().numpy())\n        preds += np.vstack(all_out)\n    preds /= len(models_paths)\n    final = np.argmax(preds, axis=1)\n    submission = pd.DataFrame({'image_id': test_df['image_id'], 'label': final})\n    return submission\n\n# -----------------------------\n# Run training when executed\n# -----------------------------\nif __name__ == '__main__':\n    # Load CSV\n    df = pd.read_csv(CFG.train_csv)\n    print('Total samples:', len(df))\n    print('Device:', CFG.device)\n    # Run kfold\n    scores = run_kfold(df)\n    print('Fold scores:', scores)\n    print('Done')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T13:49:52.892565Z","iopub.execute_input":"2025-11-21T13:49:52.893136Z","iopub.status.idle":"2025-11-21T20:47:27.095553Z","shell.execute_reply.started":"2025-11-21T13:49:52.89311Z","shell.execute_reply":"2025-11-21T20:47:27.094534Z"}},"outputs":[],"execution_count":null},{"id":"e6a1e890-835a-4041-beb6-5b38c0d5161e","cell_type":"code","source":"def predict_test(models_paths, test_df):\n    ds = CassavaDataset(test_df, '/kaggle/input/cassava-leaf-disease-classification/test_images', transforms=test_transforms, is_test=True)\n    loader = DataLoader(ds, batch_size=CFG.batch_size, shuffle=False, num_workers=CFG.num_workers)\n    preds = np.zeros((len(test_df), 5))\n    for mp in models_paths:\n        model = CassavaModel().to(CFG.device)\n        model.load_state_dict(torch.load(mp, map_location=CFG.device))\n        model.eval()\n        all_out = []\n        with torch.no_grad():\n            for images, img_ids in loader:\n                images = images.to(CFG.device)\n                outputs = model(images)\n                all_out.append(torch.softmax(outputs, dim=1).cpu().numpy())\n        preds += np.vstack(all_out)\n    preds /= len(models_paths)\n    final = np.argmax(preds, axis=1)\n    submission = pd.DataFrame({'image_id': test_df['image_id'], 'label': final})\n    return submission\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T21:06:10.877252Z","iopub.execute_input":"2025-11-21T21:06:10.877549Z","iopub.status.idle":"2025-11-21T21:06:10.884002Z","shell.execute_reply.started":"2025-11-21T21:06:10.87753Z","shell.execute_reply":"2025-11-21T21:06:10.883163Z"}},"outputs":[],"execution_count":null},{"id":"7df74547-31b0-4249-acc0-3c679f6d2af5","cell_type":"code","source":"# Load the sample submission file to get test image IDs\ntest_df = pd.read_csv(CFG.sample_submission)\n\n# Check the first few rows\ntest_df.head()\n# List of trained fold models\nmodels_paths = [os.path.join(CFG.output_dir, f'best_fold{i}.pth') for i in range(CFG.n_folds)]\n\n# Run inference\nsubmission_df = predict_test(models_paths, test_df)\n\n# Save submission\nsubmission_file = '/kaggle/working/submission.csv'\nsubmission_df.to_csv(submission_file, index=False)\n\nprint(f\"Submission file created: {submission_file}\")\nsubmission_df.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T21:06:16.585056Z","iopub.execute_input":"2025-11-21T21:06:16.585321Z","iopub.status.idle":"2025-11-21T21:06:21.963035Z","shell.execute_reply.started":"2025-11-21T21:06:16.585303Z","shell.execute_reply":"2025-11-21T21:06:21.962186Z"}},"outputs":[],"execution_count":null},{"id":"5a7ce29b-b47d-4a4c-9fdf-f601e9f77578","cell_type":"code","source":"!ls -lh /kaggle/working/","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T21:26:00.012962Z","iopub.execute_input":"2025-11-21T21:26:00.013259Z","iopub.status.idle":"2025-11-21T21:26:00.175373Z","shell.execute_reply.started":"2025-11-21T21:26:00.013239Z","shell.execute_reply":"2025-11-21T21:26:00.174419Z"}},"outputs":[],"execution_count":null},{"id":"2c4d1eba-7cfd-475d-9a93-a1455e2d5b18","cell_type":"code","source":"!kaggle competitions submit -c cassava-leaf-disease-classification \\\n  -f /kaggle/working/submission.csv \\\n  -m \"ConvNeXt-Tiny ensemble submission\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T21:31:06.446366Z","iopub.execute_input":"2025-11-21T21:31:06.44725Z","iopub.status.idle":"2025-11-21T21:31:07.17141Z","shell.execute_reply.started":"2025-11-21T21:31:06.447217Z","shell.execute_reply":"2025-11-21T21:31:07.170408Z"}},"outputs":[],"execution_count":null},{"id":"24655860-60f0-4917-8131-d121b94c76b2","cell_type":"code","source":"# -----------------------------\n# Auto-generate and submit Kaggle CSV\n# -----------------------------\nimport os\nimport pandas as pd\nfrom IPython.display import display, FileLink\n\n# --- Step 1: Load sample submission (test image IDs) ---\ntest_df = pd.read_csv(CFG.sample_submission)\n\n# --- Step 2: List trained fold models ---\nmodels_paths = [os.path.join(CFG.output_dir, f'best_fold{i}.pth') for i in range(CFG.n_folds)]\n\n# --- Step 3: Run inference ---\nsubmission_df = predict_test(models_paths, test_df)\n\n# --- Step 4: Save submission file to /kaggle/working ---\nsubmission_file = '/kaggle/working/submission.csv'\nsubmission_df.to_csv(submission_file, index=False)\nprint(f\"✅ Submission file created at {submission_file}\")\n\n# Display clickable link (optional)\ndisplay(FileLink(submission_file))\n\n# --- Step 5: Auto-submit to Kaggle competition ---\n# Make sure the Kaggle API token is configured in the notebook environment\ncompetition_name = 'cassava-leaf-disease-classification'\nsubmission_message = 'ConvNeXt-Tiny 5-fold ensemble'\n\nprint(\"📤 Submitting to Kaggle...\")\nos.system(f'kaggle competitions submit -c {competition_name} -f {submission_file} -m \"{submission_message}\"')\n\nprint(\"✅ Submission command executed. Check Kaggle for status and score.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T21:41:03.619167Z","iopub.execute_input":"2025-11-21T21:41:03.619482Z","iopub.status.idle":"2025-11-21T21:41:09.369579Z","shell.execute_reply.started":"2025-11-21T21:41:03.619455Z","shell.execute_reply":"2025-11-21T21:41:09.368623Z"}},"outputs":[],"execution_count":null},{"id":"2f7e9cda-318a-4dc4-a67a-95c48e466c93","cell_type":"code","source":"# ============================\n# 1. Imports\n# ============================\nimport os\nimport pandas as pd\nimport numpy as np\nfrom pathlib import Path\nimport cv2\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\n\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom tqdm import tqdm\n\n# ============================\n# 2. CFG\n# ============================\nclass CFG:\n    img_size = 384\n    batch_size = 32\n    num_workers = 2\n    device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\n    # Kaggle auto-mounts the hidden test images here:\n    test_dir = \"/kaggle/input/cassava-leaf-disease-classification/test_images\"\n    sample_submission = \"/kaggle/input/cassava-leaf-disease-classification/sample_submission.csv\"\n\n    # ImageNet normalization\n    mean = (0.485, 0.456, 0.406)\n    std = (0.229, 0.224, 0.225)\n\n# ============================\n# 3. Dataset\n# ============================\nclass CassavaTestDataset(Dataset):\n    def __init__(self, df, img_dir, transforms=None):\n        self.df = df.reset_index(drop=True)\n        self.img_dir = Path(img_dir)\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img_path = self.img_dir / row[\"image_id\"]\n\n        img = cv2.imread(str(img_path))\n        if img is None:\n            raise FileNotFoundError(f\"Missing test image: {img_path}\")\n\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        if self.transforms:\n            img = self.transforms(image=img)[\"image\"]\n        return img, row[\"image_id\"]\n\n# ============================\n# 4. Model\n# ============================\nclass CassavaModel(nn.Module):\n    def __init__(self, model_name=\"convnext_tiny\", num_classes=5):\n        super().__init__()\n        self.model = timm.create_model(model_name, pretrained=False, num_classes=num_classes)\n\n    def forward(self, x):\n        return self.model(x)\n\n# ============================\n# 5. Test Transforms\n# ============================\ntest_transforms = A.Compose([\n    A.Resize(CFG.img_size, CFG.img_size),\n    A.Normalize(mean=CFG.mean, std=CFG.std),\n    ToTensorV2()\n])\n\n# ============================\n# 6. Load Test Data\n# ============================\ntest_df = pd.read_csv(CFG.sample_submission)  # contains image_id column\ntest_dataset = CassavaTestDataset(test_df, CFG.test_dir, transforms=test_transforms)\ntest_loader = DataLoader(test_dataset, batch_size=CFG.batch_size,\n                         shuffle=False, num_workers=CFG.num_workers)\n\n# ============================\n# 7. Load Models\n# (Upload your best_foldX.pth files to the left sidebar first)\n# ============================\nmodel_paths = [\n    \"/kaggle/input/your-upload-folder/best_fold0.pth\",\n    \"/kaggle/input/your-upload-folder/best_fold1.pth\",\n    \"/kaggle/input/your-upload-folder/best_fold2.pth\",\n    \"/kaggle/input/your-upload-folder/best_fold3.pth\",\n    \"/kaggle/input/your-upload-folder/best_fold4.pth\",\n]\n\nmodels = []\nfor mp in model_paths:\n    m = CassavaModel().to(CFG.device)\n    m.load_state_dict(torch.load(mp, map_location=CFG.device))\n    m.eval()\n    models.append(m)\n\n# ============================\n# 8. Inference (Ensemble)\n# ============================\nall_preds = np.zeros((len(test_df), 5))\n\nwith torch.no_grad():\n    i = 0\n    for images, image_ids in tqdm(test_loader):\n        images = images.to(CFG.device)\n\n        # Collect outputs from each model\n        out = []\n        for m in models:\n            o = torch.softmax(m(images), dim=1).cpu().numpy()\n            out.append(o)\n\n        # Average ensemble\n        out = np.mean(out, axis=0)\n        all_preds[i:i+len(images)] = out\n        i += len(images)\n\nfinal_preds = np.argmax(all_preds, axis=1)\n\n# ============================\n# 9. Save submission file\n# ============================\nsubmission = pd.DataFrame({\n    \"image_id\": test_df[\"image_id\"],\n    \"label\": final_preds\n})\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"✅ submission.csv created at /kaggle/working/\")\n","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"30168b01-9e57-445b-847b-de3e9df95fee","cell_type":"code","source":"import pandas as pd\ntest_df = pd.read_csv('/kaggle/input/cassava-leaf-disease-classification/sample_submission.csv')\ntest_df.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-22T05:34:38.583695Z","iopub.execute_input":"2025-11-22T05:34:38.584047Z","iopub.status.idle":"2025-11-22T05:34:38.989918Z","shell.execute_reply.started":"2025-11-22T05:34:38.584021Z","shell.execute_reply":"2025-11-22T05:34:38.98849Z"}},"outputs":[],"execution_count":null},{"id":"8f5a725e-b6d2-48f7-ad49-381fdb2b50f2","cell_type":"code","source":"# ================================\n# FINAL INFERENCE – Your Models + GPU P100 + TTA\n# ================================\n\nimport os\nimport cv2\nimport glob\nimport numpy as np\nimport pandas as pd\nfrom pathlib import Path\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.amp import autocast\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom tqdm.notebook import tqdm\nimport timm\n\n# ------------------ CONFIG (updated for your model location) ------------------\nclass CFG:\n    img_size = 384\n    batch_size = 64          # P100 can handle larger batch → faster inference\n    num_workers = 4\n    device = 'cuda'\n    base_path = '/kaggle/input/cassava-leaf-disease-classification'\n    test_dir = os.path.join(base_path, 'test_images')\n    model_dir = '/kaggle/input/cassava-convnext-tiny/pytorch/default/1'  # ← YOUR MODELS\n    mean = (0.485, 0.456, 0.406)\n    std = (0.229, 0.224, 0.225)\n\n# ------------------ Transforms ------------------\ntest_transforms = A.Compose([\n    A.Resize(height=CFG.img_size, width=CFG.img_size),\n    A.Normalize(mean=CFG.mean, std=CFG.std),\n    ToTensorV2()\n])\n\n# ------------------ Test Dataset ------------------\nclass TestDataset(Dataset):\n    def __init__(self, img_dir, transform=None):\n        self.img_dir = Path(img_dir)\n        self.transform = transform\n        self.images = sorted([f for f in os.listdir(img_dir) if f.endswith('.jpg')])\n\n    def __len__(self): return len(self.images)\n    def __getitem__(self, idx):\n        img_name = self.images[idx]\n        img = cv2.imread(str(self.img_dir / img_name))\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        if self.transform:\n            img = self.transform(image=img)['image']\n        return img, img_name\n\n# ------------------ Model ------------------\nclass CassavaModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.backbone = timm.create_model('convnext_tiny', pretrained=False, num_classes=5)\n    def forward(self, x):\n        return self.backbone(x)\n\n# ------------------ TTA (horizontal flip) ------------------\n@torch.no_grad()\ndef predict_with_tta(model, loader):\n    model.eval()\n    all_probs = []\n    for images, _ in tqdm(loader, desc=\"TTA\", leave=False):\n        images = images.to(CFG.device)\n        with autocast(device_type='cuda', enabled=True):\n            prob1 = torch.softmax(model(images), dim=1)\n            prob2 = torch.softmax(model(torch.flip(images, dims=[3])), dim=1)\n        all_probs.append(((prob1 + prob2) / 2).cpu().numpy())\n    return np.concatenate(all_probs)\n\n# ------------------ Load your 5 models ------------------\nmodel_paths = sorted(glob.glob(f\"{CFG.model_dir}/best_fold*.pth\"))\nprint(f\"Found {len(model_paths)} models in your dataset:\")\nfor p in model_paths: print(\"  →\", os.path.basename(p))\n\n# ------------------ Inference ------------------\ntest_dataset = TestDataset(CFG.test_dir, test_transforms)\ntest_loader = DataLoader(test_dataset, batch_size=CFG.batch_size, shuffle=False,\n                         num_workers=CFG.num_workers, pin_memory=True)\n\nensemble_probs = None\n\nfor i, path in enumerate(model_paths):\n    print(f\"\\n[{i+1}/5] Loading {os.path.basename(path)}\")\n    model = CassavaModel().to(CFG.device)\n    model.load_state_dict(torch.load(path, map_location=CFG.device))\n    \n    fold_probs = predict_with_tta(model, test_loader)\n    \n    if ensemble_probs is None:\n        ensemble_probs = fold_probs\n    else:\n        ensemble_probs += fold_probs\n    \n    del model\n    torch.cuda.empty_cache()\n\n# ------------------ Final submission ------------------\nensemble_probs /= len(model_paths)\npred_labels = np.argmax(ensemble_probs, axis=1)\n\nsubmission = pd.DataFrame({\n    'image_id': test_dataset.images,\n    'label': pred_labels\n})\n\n# Ensure exact order matches sample_submission.csv\nsample = pd.read_csv(os.path.join(CFG.base_path, 'sample_submission.csv'))\nsubmission = sample[['image_id']].merge(submission, on='image_id', how='left')\nsubmission.to_csv('submission.csv', index=False)\n\nprint(\"\\n\" + \"=\"*50)\nprint(\"SUBMISSION READY!\")\nprint(submission.head(10))\nprint(f\"Total predictions: {len(submission)}\")\nprint(\"→ Click 'Submit' on the right panel now!\")\nprint(\"=\"*50)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-22T06:04:22.792026Z","iopub.execute_input":"2025-11-22T06:04:22.792687Z","iopub.status.idle":"2025-11-22T06:04:32.450376Z","shell.execute_reply.started":"2025-11-22T06:04:22.792657Z","shell.execute_reply":"2025-11-22T06:04:32.449657Z"}},"outputs":[],"execution_count":null},{"id":"ce3f7a18-9761-4345-a27b-2ac55441dcd5","cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}