{"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":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31259,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 📦 Cell 1: Imports & Offline Setup","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport timm\n\nclass BiomassModel(nn.Module):\n    def __init__(self, model_name, num_features, pretrained=False):\n        super().__init__()\n        # Load backbone without internet access\n        self.backbone = timm.create_model(\n            model_name, \n            pretrained=pretrained, \n            num_classes=0, \n            global_pool='avg'\n        )\n        \n        # Determine backbone output dimension\n        backbone_out = self.backbone(torch.randn(1, 3, CFG.img_size, CFG.img_size)).shape[1]\n        \n        # Metadata MLP\n        self.mlp = nn.Sequential(\n            nn.Linear(num_features, 128),\n            nn.SiLU(),\n            nn.Dropout(0.2)\n        )\n        \n        # Fusion Head\n        self.head = nn.Sequential(\n            nn.Linear(backbone_out + 128, 256),\n            nn.SiLU(),\n            nn.Linear(256, 5) # 5 targets\n        )\n\n    def forward(self, img, features):\n        v_feat = self.backbone(img)\n        m_feat = self.mlp(features)\n        combined = torch.cat([v_feat, m_feat], dim=1)\n        return self.head(combined)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:17:41.36007Z","iopub.execute_input":"2026-01-21T08:17:41.36045Z","iopub.status.idle":"2026-01-21T08:17:41.368288Z","shell.execute_reply.started":"2026-01-21T08:17:41.360423Z","shell.execute_reply":"2026-01-21T08:17:41.367482Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🖼️ Cell 2: Robust Dataset Class","metadata":{}},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom torch.utils.data import Dataset\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nclass BiomassDataset(Dataset):\n    def __init__(self, df, phase='test'):\n        self.df = df.reset_index(drop=True)\n        self.phase = phase\n        self.img_dir = f\"{CFG.data_dir}/{phase}_images\"\n        self.transforms = 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        # AUTOMATIC FEATURE SELECTION: Only use numeric columns as features\n        # This prevents the \"could not convert string to float\" error\n        self.feature_cols = df.select_dtypes(include=[np.number]).columns.tolist()\n        self.feature_cols = [c for c in self.feature_cols if c not in CFG.targets]\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        # ID Cleaning: Remove suffixes and extensions\n        img_id = str(row['image_id']).split('__')[0].replace('.jpg', '')\n        img_path = os.path.join(self.img_dir, f\"{img_id}.jpg\")\n        \n        # Image Loading with Fallback\n        img = cv2.imread(img_path)\n        if img is None:\n            img = np.zeros((CFG.img_size, CFG.img_size, 3), dtype=np.uint8)\n        else:\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            \n        img = self.transforms(image=img)['image']\n        \n        # Extract only numerical features\n        features = row[self.feature_cols].values.astype(np.float32)\n        \n        return img, torch.tensor(features), img_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:17:41.377565Z","iopub.execute_input":"2026-01-21T08:17:41.378485Z","iopub.status.idle":"2026-01-21T08:17:41.395165Z","shell.execute_reply.started":"2026-01-21T08:17:41.378452Z","shell.execute_reply":"2026-01-21T08:17:41.394256Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🧠 Cell 3: Offline-Ready Model Architecture","metadata":{}},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"code","source":"class BiomassModel(nn.Module):\n    def __init__(self, model_name, num_features, pretrained=False):\n        super().__init__()\n        # pretrained=False is mandatory for offline submission\n        self.backbone = timm.create_model(model_name, pretrained=pretrained, num_classes=0, global_pool='avg')\n        \n        # Extract backbone feature size\n        backbone_out = self.backbone(torch.randn(1, 3, CFG.img_size, CFG.img_size)).shape[1]\n        \n        self.mlp = nn.Sequential(\n            nn.Linear(num_features, 128),\n            nn.SiLU(),\n            nn.Dropout(0.2)\n        )\n        \n        self.head = nn.Sequential(\n            nn.Linear(backbone_out + 128, 256),\n            nn.SiLU(),\n            nn.Linear(256, len(CFG.targets))\n        )\n\n    def forward(self, img, features):\n        v_feat = self.backbone(img)\n        m_feat = self.mlp(features)\n        combined = torch.cat([v_feat, m_feat], dim=1)\n        return self.head(combined)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:17:41.400382Z","iopub.execute_input":"2026-01-21T08:17:41.4007Z","iopub.status.idle":"2026-01-21T08:17:41.42238Z","shell.execute_reply.started":"2026-01-21T08:17:41.400666Z","shell.execute_reply":"2026-01-21T08:17:41.421116Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔮 Cell 4: Inference & Long-Format Formatting","metadata":{}},{"cell_type":"code","source":"def run_inference():\n    test_df = pd.read_csv(f'{CFG.data_dir}/test.csv')\n    # Pre-processing image_id\n    if 'sample_id' in test_df.columns:\n        test_df = test_df.rename(columns={'sample_id': 'image_id'})\n    \n    # Feature engineering for metadata (must match training)\n    if 'NDVI' in test_df.columns:\n        test_df['NDVI'] = test_df['NDVI'].fillna(0.5)\n    \n    test_ds = BiomassDataset(test_df, phase='test')\n    test_loader = DataLoader(test_ds, batch_size=CFG.batch_size, shuffle=False, num_workers=2)\n    \n    # Init Model\n    _, dummy_feat, _ = test_ds[0]\n    model = BiomassModel(CFG.model_name, dummy_feat.shape[0], pretrained=False).to(CFG.device)\n    \n    # Load your trained weights (Ensure file is in /kaggle/working/ or added as Input)\n    weight_path = 'best_fold0.pth'\n    if os.path.exists(weight_path):\n        model.load_state_dict(torch.load(weight_path, map_location=CFG.device))\n        print(\"✅ Weights Loaded Successfully\")\n    else:\n        print(\"⚠️ Warning: Running with random weights (for testing only)\")\n\n    model.eval()\n    results = []\n\n    with torch.no_grad():\n        for img, feat, img_ids in tqdm(test_loader):\n            img, feat = img.to(CFG.device), feat.to(CFG.device)\n            with autocast(enabled=CFG.mixed_precision):\n                preds = model(img, feat).cpu().numpy()\n            \n            # Convert to Long Format immediately\n            for i, img_id in enumerate(img_ids):\n                for j, target_name in enumerate(CFG.targets):\n                    results.append({\n                        'sample_id': f\"{img_id}__{target_name}\",\n                        'target': max(0, preds[i, j]) # Biomass cannot be negative\n                    })\n\n    # Save to CSV\n    sub_df = pd.DataFrame(results)\n    sub_df.to_csv('submission.csv', index=False)\n    print(\"🏆 Submission saved successfully in Long Format!\")\n    return sub_df\n\nfinal_sub = run_inference()\ndisplay(final_sub.head(10))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-21T08:17:41.471492Z","iopub.execute_input":"2026-01-21T08:17:41.471829Z","iopub.status.idle":"2026-01-21T08:17:51.647593Z","shell.execute_reply.started":"2026-01-21T08:17:41.471802Z","shell.execute_reply":"2026-01-21T08:17:51.64534Z"}},"outputs":[],"execution_count":null}]}