{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":290273213,"sourceType":"kernelVersion"},{"sourceId":710667,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":539910,"modelId":553106}],"dockerImageVersionId":31234,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport torch\nimport torch.nn as nn\nimport pandas as pd\nimport cv2\nimport numpy as np\nimport timm\nimport re\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom tqdm import tqdm\n\n# --- CONFIG ---\nclass CFG:\n    img_size = 448\n    batch_size = 16\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    \n    # PATHS TO YOUR MODELS\n    # Update these to the actual locations in your Input folder\n    path_convnext = \"/kaggle/input/cisro-train2/model_huber.pth\" \n    path_effnet   = \"/kaggle/input/ciscro-effnet/pytorch/default/1/model_effnet.pth\"\n    \n    test_csv = \"/kaggle/input/csiro-biomass/test.csv\"\n    test_imgs_dir = \"/kaggle/input/csiro-biomass/test\"\n\n# --- MODEL WRAPPERS ---\nclass ConvNextModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.backbone = timm.create_model('convnext_base.fb_in22k_ft_in1k', pretrained=False, num_classes=0)\n        self.head = nn.Sequential(\n            nn.Linear(1024, 512),\n            nn.LayerNorm(512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, 5)\n        )\n    def forward(self, x): return self.head(self.backbone(x))\n\nclass EffNetModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n        # Must match training name exactly\n        self.backbone = timm.create_model('tf_efficientnetv2_m.in21k_ft_in1k', pretrained=False, num_classes=0)\n        embed_dim = self.backbone.num_features\n        self.head = nn.Sequential(\n            nn.Linear(embed_dim, 512),\n            nn.LayerNorm(512),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(512, 5)\n        )\n    def forward(self, x): return self.head(self.backbone(x))\n\ndef get_transforms():\n    return A.Compose([\n        A.Resize(CFG.img_size, CFG.img_size),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        ToTensorV2(),\n    ])\n\n# --- ENSEMBLE INFERENCE ---\ndef run_ensemble():\n    if not os.path.exists(CFG.test_csv): return\n    test_df = pd.read_csv(CFG.test_csv)\n    \n    print(\"Loading Models...\")\n    \n    # Load ConvNeXt (0.57)\n    model_a = ConvNextModel().to(CFG.device)\n    if os.path.exists(CFG.path_convnext):\n        print(f\"✅ Loaded ConvNeXt: {CFG.path_convnext}\")\n        state = torch.load(CFG.path_convnext, map_location=CFG.device)\n        model_a.load_state_dict({k.replace(\"module.\", \"\"): v for k, v in state.items()})\n    else:\n        print(f\"❌ ERROR: ConvNeXt path wrong: {CFG.path_convnext}\")\n    model_a.eval()\n    \n    # Load EfficientNet (New)\n    model_b = EffNetModel().to(CFG.device)\n    if os.path.exists(CFG.path_effnet):\n        print(f\"✅ Loaded EfficientNet: {CFG.path_effnet}\")\n        state = torch.load(CFG.path_effnet, map_location=CFG.device)\n        model_b.load_state_dict({k.replace(\"module.\", \"\"): v for k, v in state.items()})\n    else:\n        print(f\"❌ ERROR: EfficientNet path wrong: {CFG.path_effnet}\")\n    model_b.eval()\n\n    # Dataset\n    class TestDataset(Dataset):\n        def __init__(self, df, img_dir):\n            self.df = df\n            self.img_dir = img_dir\n            self.transform = get_transforms()\n        def __len__(self): return len(self.df)\n        def __getitem__(self, idx):\n            sid = self.df.iloc[idx]['sample_id']\n            match = re.search(r'(ID[0-9]+)', sid)\n            fn = f\"{match.group(1)}.jpg\" if match else \"unknown.jpg\"\n            path = os.path.join(self.img_dir, fn)\n            img = cv2.imread(path) if os.path.exists(path) else np.zeros((CFG.img_size, CFG.img_size, 3), dtype=np.uint8)\n            if img is not None: img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            else: img = np.zeros((CFG.img_size, CFG.img_size, 3), dtype=np.uint8)\n            return self.transform(image=img)['image'], sid\n\n    loader = DataLoader(TestDataset(test_df, CFG.test_imgs_dir), batch_size=CFG.batch_size, shuffle=False, num_workers=2)\n    \n    image_preds = {}\n    \n    print(\"Running 50/50 Ensemble Inference...\")\n    with torch.no_grad():\n        for images, sids in tqdm(loader):\n            images = images.to(CFG.device)\n            \n            # Predict A\n            out_a = model_a(images)\n            # Predict B\n            out_b = model_b(images)\n            \n            # Average in Log Space\n            avg_out = (out_a + out_b) / 2.0\n            \n            # Convert to Grams\n            preds = np.expm1(avg_out.cpu().numpy())\n            \n            for i, sid in enumerate(sids):\n                match = re.search(r'(ID[0-9]+)', sid)\n                if match:\n                    cid = match.group(1)\n                    if cid not in image_preds: image_preds[cid] = preds[i]\n\n    # Map to submission\n    vals = []\n    t_map = {'Dry_Total_g':0, 'GDM_g':1, 'Dry_Green_g':2, 'Dry_Dead_g':3, 'Dry_Clover_g':4}\n    for _, row in test_df.iterrows():\n        sid = row['sample_id']\n        match = re.search(r'(ID[0-9]+)', sid)\n        v = 0.0\n        if match and match.group(1) in image_preds:\n            vec = image_preds[match.group(1)]\n            for k, idx in t_map.items():\n                if sid.endswith(k):\n                    v = vec[idx]\n                    break\n        vals.append(max(0.0, v))\n        \n    sub = pd.DataFrame({'sample_id': test_df['sample_id'], 'target': vals})\n    sub.to_csv('submission.csv', index=False)\n    print(\"✅ submission.csv saved!\")\n\nif __name__ == '__main__':\n    run_ensemble()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-25T12:41:30.735460Z","iopub.execute_input":"2025-12-25T12:41:30.736243Z","iopub.status.idle":"2025-12-25T12:42:23.837252Z","shell.execute_reply.started":"2025-12-25T12:41:30.736204Z","shell.execute_reply":"2025-12-25T12:42:23.836540Z"}},"outputs":[],"execution_count":null}]}