{"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"}],"dockerImageVersionId":31260,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport os\nimport pandas as pd\nimport numpy as np\nfrom pathlib import Path\nfrom torch.utils.data import DataLoader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:54:28.311334Z","iopub.execute_input":"2026-01-31T12:54:28.311875Z","iopub.status.idle":"2026-01-31T12:54:28.315879Z","shell.execute_reply.started":"2026-01-31T12:54:28.311842Z","shell.execute_reply":"2026-01-31T12:54:28.315118Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"root_dir = Path(\"/kaggle/input/csiro-biomass\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:54:28.948042Z","iopub.execute_input":"2026-01-31T12:54:28.948328Z","iopub.status.idle":"2026-01-31T12:54:28.952031Z","shell.execute_reply.started":"2026-01-31T12:54:28.948301Z","shell.execute_reply":"2026-01-31T12:54:28.951244Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(root_dir/\"train.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:36:32.540664Z","iopub.execute_input":"2026-01-31T13:36:32.541216Z","iopub.status.idle":"2026-01-31T13:36:32.561828Z","shell.execute_reply.started":"2026-01-31T13:36:32.541183Z","shell.execute_reply":"2026-01-31T13:36:32.561188Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images_path_list = []\nfor img_path in train_df['image_path']:\n    images_path_list.append(root_dir/img_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:54:29.551651Z","iopub.execute_input":"2026-01-31T12:54:29.551924Z","iopub.status.idle":"2026-01-31T12:54:29.559149Z","shell.execute_reply.started":"2026-01-31T12:54:29.551900Z","shell.execute_reply":"2026-01-31T12:54:29.558538Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df[\"image_path\"] = images_path_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:54:32.275593Z","iopub.execute_input":"2026-01-31T12:54:32.276209Z","iopub.status.idle":"2026-01-31T12:54:32.282488Z","shell.execute_reply.started":"2026-01-31T12:54:32.276183Z","shell.execute_reply":"2026-01-31T12:54:32.281712Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5. Data preparation (STEP BY STEP)","metadata":{"execution":{"iopub.status.busy":"2026-01-31T12:10:19.846706Z","iopub.execute_input":"2026-01-31T12:10:19.847305Z","iopub.status.idle":"2026-01-31T12:10:19.851822Z","shell.execute_reply.started":"2026-01-31T12:10:19.847278Z","shell.execute_reply":"2026-01-31T12:10:19.850869Z"}}},{"cell_type":"code","source":"from sklearn.preprocessing import LabelEncoder\n\nstate_le = LabelEncoder()\nspecies_le = LabelEncoder()\ntargetname_le = LabelEncoder()\n\ndf = train_df.copy()\n\ndf[\"State\"] = state_le.fit_transform(df[\"State\"])\ndf[\"Species\"] = species_le.fit_transform(df[\"Species\"])\ndf[\"target_name\"] = targetname_le.fit_transform(df[\"target_name\"])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:55:34.691878Z","iopub.execute_input":"2026-01-31T12:55:34.692581Z","iopub.status.idle":"2026-01-31T12:55:34.699888Z","shell.execute_reply.started":"2026-01-31T12:55:34.692552Z","shell.execute_reply":"2026-01-31T12:55:34.699089Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5.2 Normalize numerical features","metadata":{"execution":{"iopub.status.busy":"2026-01-31T12:10:40.360104Z","iopub.execute_input":"2026-01-31T12:10:40.360800Z","iopub.status.idle":"2026-01-31T12:10:40.372262Z","shell.execute_reply.started":"2026-01-31T12:10:40.360771Z","shell.execute_reply":"2026-01-31T12:10:40.371506Z"}}},{"cell_type":"code","source":"num_cols = [\"Pre_GSHH_NDVI\", \"Height_Ave_cm\"]\n\ndf[num_cols] = df[num_cols].fillna(df[num_cols].mean())\n\nnum_mean = df[num_cols].mean()\nnum_std  = df[num_cols].std()\n\ndf[num_cols] = (df[num_cols] - num_mean) / num_std","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:55:38.114392Z","iopub.execute_input":"2026-01-31T12:55:38.114733Z","iopub.status.idle":"2026-01-31T12:55:38.125540Z","shell.execute_reply.started":"2026-01-31T12:55:38.114704Z","shell.execute_reply":"2026-01-31T12:55:38.124999Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5.3 Normalize target (IMPORTANT)","metadata":{"execution":{"iopub.status.busy":"2026-01-31T12:11:35.478242Z","iopub.execute_input":"2026-01-31T12:11:35.478573Z","iopub.status.idle":"2026-01-31T12:11:35.482662Z","shell.execute_reply.started":"2026-01-31T12:11:35.478546Z","shell.execute_reply":"2026-01-31T12:11:35.481847Z"}}},{"cell_type":"code","source":"target_mean = df[\"target\"].mean()\ntarget_std  = df[\"target\"].std()\n\ndf[\"target_norm\"] = (df[\"target\"] - target_mean) / target_std","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:55:54.370789Z","iopub.execute_input":"2026-01-31T12:55:54.371343Z","iopub.status.idle":"2026-01-31T12:55:54.376452Z","shell.execute_reply.started":"2026-01-31T12:55:54.371315Z","shell.execute_reply":"2026-01-31T12:55:54.375785Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 5.4 Train / validation split (by image!)","metadata":{}},{"cell_type":"code","source":"from sklearn.model_selection import GroupShuffleSplit\n\ngss = GroupShuffleSplit(test_size=0.2, random_state=42)\n\ntrain_idx, val_idx = next(\n    gss.split(df, groups=df[\"image_path\"])\n)\n\ntrain_df = df.iloc[train_idx]\nval_df   = df.iloc[val_idx]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:56:11.994383Z","iopub.execute_input":"2026-01-31T12:56:11.995183Z","iopub.status.idle":"2026-01-31T12:56:12.021642Z","shell.execute_reply.started":"2026-01-31T12:56:11.995151Z","shell.execute_reply":"2026-01-31T12:56:12.020918Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 6. Dataset class (FINAL, CORRECT)","metadata":{}},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset\nfrom PIL import Image\n\nclass BiomassDataset(Dataset):\n    def __init__(self, df, transforms=None):\n        self.df = df.reset_index(drop=True)\n        self.transforms = transforms\n\n        self.num_cols = [\"Pre_GSHH_NDVI\", \"Height_Ave_cm\"]\n        self.cat_cols = [\"State\", \"Species\", \"target_name\"]\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.loc[idx]\n\n        image = Image.open(row[\"image_path\"]).convert(\"RGB\")\n        if self.transforms:\n            image = self.transforms(image)\n\n        num_feats = torch.tensor(\n            row[self.num_cols].values.astype(\"float32\")\n        )\n\n        cat_feats = torch.tensor(\n            row[self.cat_cols].values.astype(\"long\")\n        )\n\n        target = torch.tensor(row[\"target_norm\"], dtype=torch.float32)\n\n        return image, num_feats, cat_feats, target","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:56:48.052927Z","iopub.execute_input":"2026-01-31T12:56:48.053545Z","iopub.status.idle":"2026-01-31T12:56:48.059520Z","shell.execute_reply.started":"2026-01-31T12:56:48.053518Z","shell.execute_reply":"2026-01-31T12:56:48.058734Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 7. Image transforms","metadata":{}},{"cell_type":"code","source":"from torchvision import transforms\n\ntrain_tfms = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])\n\nval_tfms = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:56:50.073065Z","iopub.execute_input":"2026-01-31T12:56:50.073672Z","iopub.status.idle":"2026-01-31T12:56:50.078479Z","shell.execute_reply.started":"2026-01-31T12:56:50.073643Z","shell.execute_reply":"2026-01-31T12:56:50.077661Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = BiomassDataset(train_df, transforms=train_tfms)\nval_dataset   = BiomassDataset(val_df, transforms=val_tfms)\n\ntrain_loader = DataLoader(\n    train_dataset,\n    batch_size=16,\n    shuffle=True,\n    num_workers=2,\n    pin_memory=True\n)\n\nval_loader = DataLoader(\n    val_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:56:51.910905Z","iopub.execute_input":"2026-01-31T12:56:51.911603Z","iopub.status.idle":"2026-01-31T12:56:51.918141Z","shell.execute_reply.started":"2026-01-31T12:56:51.911574Z","shell.execute_reply":"2026-01-31T12:56:51.917373Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"batch = next(iter(train_loader))\n\nimages, num_feats, cat_feats, targets = batch\n\nprint(\"Images:\", images.shape)       # [B, 3, 224, 224]\nprint(\"Num feats:\", num_feats.shape) # [B, 2]\nprint(\"Cat feats:\", cat_feats.shape) # [B, 3]\nprint(\"Targets:\", targets.shape)     # [B]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:56:53.491309Z","iopub.execute_input":"2026-01-31T12:56:53.492055Z","iopub.status.idle":"2026-01-31T12:56:55.409904Z","shell.execute_reply.started":"2026-01-31T12:56:53.492024Z","shell.execute_reply":"2026-01-31T12:56:55.409188Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 8. Model architecture (CNN + tabular fusion)","metadata":{}},{"cell_type":"code","source":"import torch.nn as nn\nimport torchvision.models as models\n\nclass BiomassModel(nn.Module):\n    def __init__(self, num_tabular, num_targets):\n        super().__init__()\n\n        self.backbone = models.resnet18(pretrained=True)\n        self.backbone.fc = nn.Identity()\n\n        self.embed_target = nn.Embedding(num_targets, 8)\n\n        self.head = nn.Sequential(\n            nn.Linear(512 + num_tabular + 8, 256),\n            nn.ReLU(),\n            nn.Linear(256, 1)\n        )\n\n    def forward(self, img, num_feats, cat_feats):\n        img_feat = self.backbone(img)\n        target_emb = self.embed_target(cat_feats[:, 2])\n\n        x = torch.cat([img_feat, num_feats, target_emb], dim=1)\n        return self.head(x).squeeze(1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:56:56.664953Z","iopub.execute_input":"2026-01-31T12:56:56.665256Z","iopub.status.idle":"2026-01-31T12:56:56.671974Z","shell.execute_reply.started":"2026-01-31T12:56:56.665224Z","shell.execute_reply":"2026-01-31T12:56:56.671437Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 9. Loss function (weighted!)","metadata":{}},{"cell_type":"code","source":"weights = {\n    0: 0.1,  # Dry_Green_g\n    1: 0.1,  # Dry_Dead_g\n    2: 0.1,  # Dry_Clover_g\n    3: 0.2,  # GDM_g\n    4: 0.5   # Dry_Total_g\n}\n\ndef weighted_mse(pred, target, target_name):\n    w = torch.tensor(\n        [weights[int(t.item())] for t in target_name],\n        device=pred.device\n    )\n    return (w * (pred - target) ** 2).mean()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:57:01.929779Z","iopub.execute_input":"2026-01-31T12:57:01.930065Z","iopub.status.idle":"2026-01-31T12:57:01.935085Z","shell.execute_reply.started":"2026-01-31T12:57:01.930040Z","shell.execute_reply":"2026-01-31T12:57:01.934122Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 10. Validation metric (global weighted R²)","metadata":{"execution":{"iopub.status.busy":"2026-01-31T12:14:23.387909Z","iopub.execute_input":"2026-01-31T12:14:23.388648Z"}}},{"cell_type":"code","source":"from sklearn.metrics import r2_score\n\ndef weighted_r2(y_true, y_pred, weights):\n    return r2_score(y_true, y_pred, sample_weight=weights)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:57:05.666059Z","iopub.execute_input":"2026-01-31T12:57:05.666344Z","iopub.status.idle":"2026-01-31T12:57:05.670172Z","shell.execute_reply.started":"2026-01-31T12:57:05.666316Z","shell.execute_reply":"2026-01-31T12:57:05.669559Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nfrom tqdm import tqdm\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = BiomassModel(\n    num_tabular=2,      # Pre_GSHH_NDVI, Height_Ave_cm\n    num_targets=5       # target_name\n).to(device)\n\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:57:08.514282Z","iopub.execute_input":"2026-01-31T12:57:08.514846Z","iopub.status.idle":"2026-01-31T12:57:08.728898Z","shell.execute_reply.started":"2026-01-31T12:57:08.514815Z","shell.execute_reply":"2026-01-31T12:57:08.728085Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TARGET_WEIGHTS = {\n    0: 0.1,  # Dry_Clover_g\n    1: 0.1,  # Dry_Dead_g\n    2: 0.1,  # Dry_Green_g\n    3: 0.5,  # Dry_Total_g\n    4: 0.2   # GDM_g\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:57:24.086158Z","iopub.execute_input":"2026-01-31T12:57:24.086919Z","iopub.status.idle":"2026-01-31T12:57:24.090272Z","shell.execute_reply.started":"2026-01-31T12:57:24.086888Z","shell.execute_reply":"2026-01-31T12:57:24.089564Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def weighted_mse_loss(pred, target, target_name):\n    weights = torch.tensor(\n        [TARGET_WEIGHTS[int(t.item())] for t in target_name],\n        device=pred.device\n    )\n    return (weights * (pred - target) ** 2).mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:57:26.835831Z","iopub.execute_input":"2026-01-31T12:57:26.836355Z","iopub.status.idle":"2026-01-31T12:57:26.840446Z","shell.execute_reply.started":"2026-01-31T12:57:26.836306Z","shell.execute_reply":"2026-01-31T12:57:26.839678Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def train_one_epoch(model, loader, optimizer):\n    model.train()\n    running_loss = 0.0\n\n    for imgs, num_feats, cat_feats, targets in tqdm(loader, leave=False):\n        imgs = imgs.to(device)\n        num_feats = num_feats.to(device)\n        cat_feats = cat_feats.to(device)\n        targets = targets.to(device)\n\n        optimizer.zero_grad()\n\n        preds = model(imgs, num_feats, cat_feats)\n        loss = weighted_mse_loss(\n            preds,\n            targets,\n            cat_feats[:, 2]  # target_name index\n        )\n\n        loss.backward()\n        optimizer.step()\n\n        running_loss += loss.item()\n\n    return running_loss / len(loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:57:32.504748Z","iopub.execute_input":"2026-01-31T12:57:32.505385Z","iopub.status.idle":"2026-01-31T12:57:32.510606Z","shell.execute_reply.started":"2026-01-31T12:57:32.505355Z","shell.execute_reply":"2026-01-31T12:57:32.509921Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nfrom sklearn.metrics import r2_score\n\ndef validate(model, loader, target_mean, target_std):\n    model.eval()\n\n    y_true, y_pred, y_weight = [], [], []\n\n    with torch.no_grad():\n        for imgs, num_feats, cat_feats, targets in loader:\n            imgs = imgs.to(device)\n            num_feats = num_feats.to(device)\n            cat_feats = cat_feats.to(device)\n            targets = targets.to(device)\n\n            preds = model(imgs, num_feats, cat_feats)\n\n            # de-normalize\n            preds = preds.cpu().numpy() * target_std + target_mean\n            targets = targets.cpu().numpy() * target_std + target_mean\n\n            y_pred.extend(preds)\n            y_true.extend(targets)\n\n            for t in cat_feats[:, 2].cpu().numpy():\n                y_weight.append(TARGET_WEIGHTS[int(t)])\n\n    r2 = r2_score(y_true, y_pred, sample_weight=y_weight)\n    return r2\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:57:57.705471Z","iopub.execute_input":"2026-01-31T12:57:57.705783Z","iopub.status.idle":"2026-01-31T12:57:57.712060Z","shell.execute_reply.started":"2026-01-31T12:57:57.705753Z","shell.execute_reply":"2026-01-31T12:57:57.711473Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 10\nbest_r2 = -1e9\n\nfor epoch in range(EPOCHS):\n    train_loss = train_one_epoch(model, train_loader, optimizer)\n    val_r2 = validate(model, val_loader, target_mean, target_std)\n\n    print(\n        f\"Epoch {epoch+1:02d} | \"\n        f\"Train Loss: {train_loss:.4f} | \"\n        f\"Val Weighted R²: {val_r2:.4f}\"\n    )\n\n    # Save best model\n    if val_r2 > best_r2:\n        best_r2 = val_r2\n        torch.save(\n            {\n                \"model_state\": model.state_dict(),\n                \"target_mean\": target_mean,\n                \"target_std\": target_std,\n            },\n            \"best_biomass_model.pth\"\n        )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T12:58:01.143613Z","iopub.execute_input":"2026-01-31T12:58:01.144248Z","iopub.status.idle":"2026-01-31T13:06:26.943183Z","shell.execute_reply.started":"2026-01-31T12:58:01.144217Z","shell.execute_reply":"2026-01-31T13:06:26.942319Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Inference for ONE row (correct way)","metadata":{}},{"cell_type":"code","source":"def predict_one_row(model, row, transforms):\n    model.eval()\n\n    img = Image.open(row[\"image_path\"]).convert(\"RGB\")\n    img = transforms(img).unsqueeze(0).to(device)\n\n    num_feats = torch.tensor(\n        row[[\"Pre_GSHH_NDVI\", \"Height_Ave_cm\"]].values.astype(\"float32\")\n    ).unsqueeze(0).to(device)\n\n    cat_feats = torch.tensor(\n        row[[\"State\", \"Species\", \"target_name\"]].values.astype(\"int64\")\n    ).unsqueeze(0).to(device)\n\n    with torch.no_grad():\n        pred = model(img, num_feats, cat_feats)\n\n    pred = pred.cpu().item() * target_std + target_mean\n    return pred\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:10:29.541544Z","iopub.execute_input":"2026-01-31T13:10:29.542296Z","iopub.status.idle":"2026-01-31T13:10:29.547999Z","shell.execute_reply.started":"2026-01-31T13:10:29.542262Z","shell.execute_reply":"2026-01-31T13:10:29.547207Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"row = train_df.iloc[0]\nprint(\"GT:\", row[\"target\"])\nprint(\"Pred:\", predict_one_row(model, row, val_tfms))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:10:31.745920Z","iopub.execute_input":"2026-01-31T13:10:31.746487Z","iopub.status.idle":"2026-01-31T13:10:31.817505Z","shell.execute_reply.started":"2026-01-31T13:10:31.746461Z","shell.execute_reply":"2026-01-31T13:10:31.816780Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## submission","metadata":{}},{"cell_type":"code","source":"test_df = pd.read_csv(root_dir/\"test.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:37:26.504802Z","iopub.execute_input":"2026-01-31T13:37:26.505102Z","iopub.status.idle":"2026-01-31T13:37:26.511628Z","shell.execute_reply.started":"2026-01-31T13:37:26.505060Z","shell.execute_reply":"2026-01-31T13:37:26.510953Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# set the correct image path\ntest_img_path_list = []\n\nfor img_path in test_df['image_path']:\n    test_img_path_list.append(root_dir/img_path)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:28:16.706856Z","iopub.execute_input":"2026-01-31T13:28:16.707443Z","iopub.status.idle":"2026-01-31T13:28:16.711079Z","shell.execute_reply.started":"2026-01-31T13:28:16.707387Z","shell.execute_reply":"2026-01-31T13:28:16.710332Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df[\"image_path\"] = test_img_path_list","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:28:19.241053Z","iopub.execute_input":"2026-01-31T13:28:19.241344Z","iopub.status.idle":"2026-01-31T13:28:19.245530Z","shell.execute_reply.started":"2026-01-31T13:28:19.241318Z","shell.execute_reply":"2026-01-31T13:28:19.244806Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Encode","metadata":{}},{"cell_type":"code","source":"test_df = test_df.copy()\n\n# numeric defaults\ntest_df[\"Pre_GSHH_NDVI\"] = train_df[\"Pre_GSHH_NDVI\"].mean()\ntest_df[\"Height_Ave_cm\"] = train_df[\"Height_Ave_cm\"].mean()\n\n# categorical defaults (already encoded!)\ntest_df[\"State\"] = train_df[\"State\"].mode()[0]\ntest_df[\"Species\"] = train_df[\"Species\"].mode()[0]\n\n# ---- encode ONLY target_name (string -> int) ----\ntest_df[\"target_name\"] = targetname_le.transform(test_df[\"target_name\"])\n\n# ---- normalize numeric features ----\nnum_cols = [\"Pre_GSHH_NDVI\", \"Height_Ave_cm\"]\ntest_df[num_cols] = (test_df[num_cols] - num_mean) / num_std","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:28:20.716926Z","iopub.execute_input":"2026-01-31T13:28:20.717227Z","iopub.status.idle":"2026-01-31T13:28:20.726701Z","shell.execute_reply.started":"2026-01-31T13:28:20.717201Z","shell.execute_reply":"2026-01-31T13:28:20.725938Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom PIL import Image\n\nclass BiomassTestDataset(Dataset):\n    def __init__(self, df, transforms=None):\n        self.df = df.reset_index(drop=True)\n        self.transforms = transforms\n\n        self.num_cols = [\"Pre_GSHH_NDVI\", \"Height_Ave_cm\"]\n        self.cat_cols = [\"State\", \"Species\", \"target_name\"]\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.loc[idx]\n\n        image = Image.open(row[\"image_path\"]).convert(\"RGB\")\n        if self.transforms:\n            image = self.transforms(image)\n\n        num_feats = torch.tensor(\n            row[self.num_cols].values.astype(\"float32\")\n        )\n\n        cat_feats = torch.tensor(\n            row[self.cat_cols].values.astype(\"int64\")\n        )\n\n        return image, num_feats, cat_feats","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:28:22.781384Z","iopub.execute_input":"2026-01-31T13:28:22.782126Z","iopub.status.idle":"2026-01-31T13:28:22.788083Z","shell.execute_reply.started":"2026-01-31T13:28:22.782096Z","shell.execute_reply":"2026-01-31T13:28:22.787234Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset = BiomassTestDataset(test_df, transforms=val_tfms)\n\ntest_loader = DataLoader(\n    test_dataset,\n    batch_size=16,\n    shuffle=False,\n    num_workers=0,      # safest on Kaggle\n    pin_memory=True\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:28:24.742841Z","iopub.execute_input":"2026-01-31T13:28:24.743468Z","iopub.status.idle":"2026-01-31T13:28:24.748059Z","shell.execute_reply.started":"2026-01-31T13:28:24.743427Z","shell.execute_reply":"2026-01-31T13:28:24.747153Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\n\npredictions = []\n\nwith torch.no_grad():\n    for imgs, num_feats, cat_feats in test_loader:\n        imgs = imgs.to(device)\n        num_feats = num_feats.to(device)\n        cat_feats = cat_feats.to(device)\n\n        preds = model(imgs, num_feats, cat_feats)\n\n        # de-normalize predictions\n        preds = preds.cpu().numpy() * target_std + target_mean\n\n        predictions.extend(preds)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:28:26.529194Z","iopub.execute_input":"2026-01-31T13:28:26.529884Z","iopub.status.idle":"2026-01-31T13:28:26.924340Z","shell.execute_reply.started":"2026-01-31T13:28:26.529856Z","shell.execute_reply":"2026-01-31T13:28:26.923779Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"assert len(predictions) == len(test_df)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:28:37.789642Z","iopub.execute_input":"2026-01-31T13:28:37.790373Z","iopub.status.idle":"2026-01-31T13:28:37.793459Z","shell.execute_reply.started":"2026-01-31T13:28:37.790347Z","shell.execute_reply":"2026-01-31T13:28:37.792811Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission = test_df[[\"sample_id\"]].copy()\nsubmission[\"target\"] = predictions","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:29:15.825871Z","iopub.execute_input":"2026-01-31T13:29:15.826155Z","iopub.status.idle":"2026-01-31T13:29:15.831688Z","shell.execute_reply.started":"2026-01-31T13:29:15.826131Z","shell.execute_reply":"2026-01-31T13:29:15.830789Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:29:23.589672Z","iopub.execute_input":"2026-01-31T13:29:23.590199Z","iopub.status.idle":"2026-01-31T13:29:23.597753Z","shell.execute_reply.started":"2026-01-31T13:29:23.590173Z","shell.execute_reply":"2026-01-31T13:29:23.596854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-31T13:29:56.308576Z","iopub.execute_input":"2026-01-31T13:29:56.309562Z","iopub.status.idle":"2026-01-31T13:29:56.323280Z","shell.execute_reply.started":"2026-01-31T13:29:56.309522Z","shell.execute_reply":"2026-01-31T13:29:56.322473Z"}},"outputs":[],"execution_count":null}]}