{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","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":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31236,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Import","metadata":{}},{"cell_type":"code","source":"import pandas as pd\nfrom glob import glob\nimport os\nimport cv2\nimport numpy as np\nimport matplotlib.pyplot as plt\n\nimport torch\nfrom torch.utils.data import Dataset , DataLoader\n\nimport torchvision.transforms as T\n\nimport torch.nn as nn\n\n\nimport torchvision.models  as models\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T03:06:28.722555Z","iopub.execute_input":"2026-01-03T03:06:28.723268Z","iopub.status.idle":"2026-01-03T03:06:28.727662Z","shell.execute_reply.started":"2026-01-03T03:06:28.723241Z","shell.execute_reply":"2026-01-03T03:06:28.726882Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"\n# Config","metadata":{}},{"cell_type":"code","source":"class Config:\n    \n    data_dir=\"/kaggle/input/csiro-biomass\"\n    \n    train_csv=pd.read_csv(os.path.join(data_dir,\"train.csv\"))\n    test_csv=os.path.join(data_dir,\"test.csv\")\n    train_images=os.path.join(data_dir,\"train\")\n    test_images=os.path.join(data_dir,\"test\")\n    batch=8\n    \n    epochs=10\n\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.001378Z","iopub.execute_input":"2026-01-03T02:20:17.001744Z","iopub.status.idle":"2026-01-03T02:20:17.034564Z","shell.execute_reply.started":"2026-01-03T02:20:17.001720Z","shell.execute_reply":"2026-01-03T02:20:17.034065Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"Config.train_csv","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.035393Z","iopub.execute_input":"2026-01-03T02:20:17.035657Z","iopub.status.idle":"2026-01-03T02:20:17.063882Z","shell.execute_reply.started":"2026-01-03T02:20:17.035630Z","shell.execute_reply":"2026-01-03T02:20:17.063370Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_wide = Config.train_csv.pivot_table(\n    index=['image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm'],\n    columns='target_name',\n    values='target',\n    aggfunc='first'\n).reset_index()\n\ndef weighted_r2_score(y_true, y_pred):\n        weights = np.array([0.1, 0.1, 0.1, 0.2, 0.5])\n        r2s = []\n    \n        for i in range(5):\n            yt, yp = y_true[:, i], y_pred[:, i]\n            ss_res = np.sum((yt - yp) ** 2)\n            ss_tot = np.sum((yt - yt.mean()) ** 2)\n            r2 = 1 - ss_res / ss_tot if ss_tot > 0 else 0\n            r2s.append(r2)\n    \n        r2s = np.array(r2s)\n        return np.sum(r2s * weights) / weights.sum(), r2s","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.064702Z","iopub.execute_input":"2026-01-03T02:20:17.064947Z","iopub.status.idle":"2026-01-03T02:20:17.099812Z","shell.execute_reply.started":"2026-01-03T02:20:17.064919Z","shell.execute_reply":"2026-01-03T02:20:17.099142Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_wide","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.100552Z","iopub.execute_input":"2026-01-03T02:20:17.100745Z","iopub.status.idle":"2026-01-03T02:20:17.113598Z","shell.execute_reply.started":"2026-01-03T02:20:17.100726Z","shell.execute_reply":"2026-01-03T02:20:17.113032Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Loading Data","metadata":{}},{"cell_type":"code","source":"train_wide[\"full_path\"]=train_wide[\"image_path\"].apply(lambda x: os.path.join(Config.data_dir,x))\nos.path.exists(train_wide.full_path.iloc[0])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.115524Z","iopub.execute_input":"2026-01-03T02:20:17.115937Z","iopub.status.idle":"2026-01-03T02:20:17.132221Z","shell.execute_reply.started":"2026-01-03T02:20:17.115889Z","shell.execute_reply":"2026-01-03T02:20:17.131618Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"x=cv2.imread(train_wide.full_path.iloc[5])\nx=cv2.cvtColor(x,cv2.COLOR_BGR2RGB)\n\nplt.imshow(x)\nplt.axis(\"off\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.133028Z","iopub.execute_input":"2026-01-03T02:20:17.133303Z","iopub.status.idle":"2026-01-03T02:20:17.456524Z","shell.execute_reply.started":"2026-01-03T02:20:17.133266Z","shell.execute_reply":"2026-01-03T02:20:17.455768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_paths=train_wide[\"full_path\"].values\n#target_cols = [\"Dry_Clover_g\",\"Dry_Dead_g\",\"Dry_Green_g\",\"Dry_Total_g\",\"GDM_g\"]\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.457816Z","iopub.execute_input":"2026-01-03T02:20:17.458041Z","iopub.status.idle":"2026-01-03T02:20:17.461183Z","shell.execute_reply.started":"2026-01-03T02:20:17.458020Z","shell.execute_reply":"2026-01-03T02:20:17.460611Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"target_cols = [\"Dry_Clover_g\",\"Dry_Dead_g\",\"Dry_Green_g\",\"Dry_Total_g\",\"GDM_g\"]\ny = train_wide[target_cols].values.astype(\"float32\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.461942Z","iopub.execute_input":"2026-01-03T02:20:17.462135Z","iopub.status.idle":"2026-01-03T02:20:17.483432Z","shell.execute_reply.started":"2026-01-03T02:20:17.462117Z","shell.execute_reply":"2026-01-03T02:20:17.482825Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Preprocessing ","metadata":{}},{"cell_type":"code","source":"class Biomass(Dataset):\n    def __init__(self,paths,labels,transform=None):\n        self.paths=paths\n        self.labels=labels\n        self.transform=transform\n\n\n    def __len__(self):\n        l=len((self.paths))\n        return l\n\n\n    def __getitem__(self,idx):\n        img=cv2.imread(self.paths[idx])\n        img=cv2.cvtColor(img,cv2.COLOR_BGR2RGB)\n        img=self.transform(img) if self.transform else img\n        label=torch.tensor(self.labels[idx],dtype=torch.float32)\n        return img , label\n        \n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.484537Z","iopub.execute_input":"2026-01-03T02:20:17.485162Z","iopub.status.idle":"2026-01-03T02:20:17.498586Z","shell.execute_reply.started":"2026-01-03T02:20:17.485137Z","shell.execute_reply":"2026-01-03T02:20:17.497831Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model Loading","metadata":{}},{"cell_type":"code","source":"import torchvision.transforms as T\n\nimg_size = 224\n\ntrain_transform = T.Compose([\n    T.ToPILImage(),                          # from cv2 (numpy) -> PIL\n    T.Resize((224, 224)),\n    T.RandomHorizontalFlip(),\n    T.RandomVerticalFlip(),\n    T.ColorJitter(0.1, 0.1, 0.1, 0.05),\n    T.ToTensor(),\n    T.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])\n\nvalid_transform = T.Compose([\n    T.ToPILImage(),\n    T.Resize((224, 224)),\n    T.ToTensor(),\n    T.Normalize([0.485, 0.456, 0.406],\n                         [0.229, 0.224, 0.225])\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.499387Z","iopub.execute_input":"2026-01-03T02:20:17.499616Z","iopub.status.idle":"2026-01-03T02:20:17.513299Z","shell.execute_reply.started":"2026-01-03T02:20:17.499593Z","shell.execute_reply":"2026-01-03T02:20:17.512630Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\nmodel=models.resnet50(weights=models.ResNet50_Weights.IMAGENET1K_V1)\n\nmodel.fc=nn.Linear(model.fc.in_features,len(target_cols))\nmodel.fc\n\nmodel=model.to(device)\nnext(model.parameters()).device\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:17.514137Z","iopub.execute_input":"2026-01-03T02:20:17.514999Z","iopub.status.idle":"2026-01-03T02:20:18.743668Z","shell.execute_reply.started":"2026-01-03T02:20:17.514977Z","shell.execute_reply":"2026-01-03T02:20:18.742966Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split \n\nX_train,X_valid,y_train,y_valid=train_test_split(image_paths,y,test_size=0.2,random_state=42)\n\ntrain_dataset = Biomass(X_train, y_train, transform=train_transform)\nvalid_dataset = Biomass(X_valid, y_valid, transform=valid_transform)\n\ntrain_loader=DataLoader(train_dataset,batch_size=Config.batch,shuffle=True)\nvalid_loader=DataLoader(valid_dataset,batch_size=Config.batch ,shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:18.763053Z","iopub.execute_input":"2026-01-03T02:20:18.763227Z","iopub.status.idle":"2026-01-03T02:20:19.384835Z","shell.execute_reply.started":"2026-01-03T02:20:18.763211Z","shell.execute_reply":"2026-01-03T02:20:19.384072Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"criterion=nn.SmoothL1Loss()\noptimizer=torch.optim.AdamW(model.parameters(),lr=3e-4,weight_decay=1e-2)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:19.385939Z","iopub.execute_input":"2026-01-03T02:20:19.386437Z","iopub.status.idle":"2026-01-03T02:20:19.391056Z","shell.execute_reply.started":"2026-01-03T02:20:19.386398Z","shell.execute_reply":"2026-01-03T02:20:19.390319Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"imgs, labels = next(iter(train_loader))\nprint(imgs.mean().item(), imgs.std().item())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:19.391713Z","iopub.execute_input":"2026-01-03T02:20:19.391928Z","iopub.status.idle":"2026-01-03T02:20:20.051046Z","shell.execute_reply.started":"2026-01-03T02:20:19.391885Z","shell.execute_reply":"2026-01-03T02:20:20.050341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(Config.epochs):\n    model.train()\n    total_loss=0\n    for  img , label in train_loader:\n        img,label=img.to(device),label.to(device)\n        preds=model(img)\n        loss=criterion(preds,label)\n        optimizer.zero_grad()\n        loss.backward()\n        optimizer.step()\n        total_loss+=loss.item()\n#        scheduler.step() \n\n    avg_loss = total_loss / len(train_loader)\n    print(f\"Epoch {epoch+1}/{Config.epochs} Loss: {avg_loss:.4f}\")\n        \n        ","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:20:20.056276Z","iopub.execute_input":"2026-01-03T02:20:20.056493Z","iopub.status.idle":"2026-01-03T02:23:38.050134Z","shell.execute_reply.started":"2026-01-03T02:20:20.056472Z","shell.execute_reply":"2026-01-03T02:23:38.049396Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\nall_preds, all_labels = [], []\n\nwith torch.no_grad():\n    for imgs, labels in valid_loader:\n        imgs = imgs.to(device)\n\n        preds = model(imgs).detach().cpu().numpy()\n        labels = labels.detach().cpu().numpy()\n\n        all_preds.append(preds)\n        all_labels.append(labels)\n\nall_preds = np.vstack(all_preds)\nall_labels = np.vstack(all_labels)\n\nscore, per_label_r2 = weighted_r2_score(all_labels, all_preds)\nprint(\"Weighted R2:\", score)\nprint(\"Per-target R2:\", per_label_r2)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:23:38.051110Z","iopub.execute_input":"2026-01-03T02:23:38.051577Z","iopub.status.idle":"2026-01-03T02:23:43.032716Z","shell.execute_reply.started":"2026-01-03T02:23:38.051552Z","shell.execute_reply":"2026-01-03T02:23:43.031957Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ================== K-Fold Cross Validation (ResNet50, 5-target regression + weighted R2) ==================\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import DataLoader\nfrom sklearn.model_selection import KFold\nfrom torchvision import models\n\n# ---- sanity check ----\ny_np = np.asarray(y)\nprint(\"y shape:\", y_np.shape)   # should be (N, 5)\n\n# ---- model factory (fresh model each fold) ----\ndef build_model(num_outputs=5):\n    m = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)\n    in_features = m.fc.in_features\n    m.fc = nn.Linear(in_features, num_outputs)\n    return m\n\n# ---- train loop ----\ndef train_one_epoch(model, loader, optimizer, criterion, device):\n    model.train()\n    total_loss, n = 0.0, 0\n\n    for imgs, labels in loader:\n        imgs = imgs.to(device)\n        labels = labels.to(device).float()     # (B, 5)  \n\n        preds = model(imgs)                    # (B, 5)\n        loss = criterion(preds, labels)\n\n        optimizer.zero_grad(set_to_none=True)\n        loss.backward()\n        optimizer.step()\n\n        bs = imgs.size(0)\n        total_loss += loss.item() * bs\n        n += bs\n\n    return total_loss / max(n, 1)\n\n# ---- valid loop (returns loss + weighted r2 + per-target r2) ----\n@torch.no_grad()\ndef valid_one_epoch(model, loader, criterion, device):\n    model.eval()\n    total_loss, n = 0.0, 0\n    all_preds, all_labels = [], []\n\n    for imgs, labels in loader:\n        imgs = imgs.to(device)\n        labels = labels.to(device).float()     # (B, 5)\n\n        preds = model(imgs)                    # (B, 5)\n        loss = criterion(preds, labels)\n\n        bs = imgs.size(0)\n        total_loss += loss.item() * bs\n        n += bs\n\n        all_preds.append(preds.detach().cpu().numpy())\n        all_labels.append(labels.detach().cpu().numpy())\n\n    all_preds = np.vstack(all_preds)\n    all_labels = np.vstack(all_labels)\n\n    \n    score, per_label_r2 = weighted_r2_score(all_labels, all_preds)\n\n    return total_loss / max(n, 1), score, per_label_r2\n\n# ---- CV settings ----\nN_SPLITS = 5\nSEED = 42\nEPOCHS = getattr(Config, \"epochs\", 1)\nBATCH_SIZE = getattr(Config, \"batch\", 8)\nNUM_WORKERS = 2\n\nkf = KFold(n_splits=N_SPLITS, shuffle=True, random_state=SEED)\n\nimage_paths_np = np.asarray(image_paths)\ny_np = np.asarray(y)  # (N, 5)\n\nfold_losses = []\nfold_r2 = []\nfold_per_target_r2 = []\n\nfor fold, (tr_idx, va_idx) in enumerate(kf.split(image_paths_np), 1):\n    print(f\"\\n========== Fold {fold}/{N_SPLITS} ==========\")\n\n    train_ds = Biomass(image_paths_np[tr_idx], y_np[tr_idx], transform=train_transform)\n    valid_ds = Biomass(image_paths_np[va_idx], y_np[va_idx], transform=valid_transform)\n\n    train_loader = DataLoader(\n        train_ds,\n        batch_size=BATCH_SIZE,\n        shuffle=True,\n        num_workers=NUM_WORKERS,\n        pin_memory=True\n    )\n    valid_loader = DataLoader(\n        valid_ds,\n        batch_size=BATCH_SIZE,\n        shuffle=False,\n        num_workers=NUM_WORKERS,\n        pin_memory=True\n    )\n\n    model = build_model(num_outputs=5).to(device)\n\n    criterion = nn.SmoothL1Loss()\n    optimizer = torch.optim.AdamW(model.parameters(), lr=3e-4, weight_decay=1e-2)\n\n    best_val_loss = float(\"inf\")\n    best_val_r2 = -1e9\n    best_per_r2 = None\n\n    for epoch in range(1, EPOCHS + 1):\n        tr_loss = train_one_epoch(model, train_loader, optimizer, criterion, device)\n        va_loss, va_r2, per_r2 = valid_one_epoch(model, valid_loader, criterion, device)\n\n        print(\n            f\"Epoch {epoch}/{EPOCHS} | \"\n            f\"train_loss: {tr_loss:.5f} | val_loss: {va_loss:.5f} | \"\n            f\"weighted_R2: {va_r2:.5f} | per_target_R2: {per_r2}\"\n        )\n\n        # keep best by val loss (you can change to best by R2 if you prefer)\n        if va_loss < best_val_loss:\n            best_val_loss = va_loss\n            best_val_r2 = va_r2\n            best_per_r2 = per_r2\n            torch.save(model.state_dict(), f\"resnet50_fold{fold}.pth\")\n\n    fold_losses.append(best_val_loss)\n    fold_r2.append(best_val_r2)\n    fold_per_target_r2.append(best_per_r2)\n\n    print(f\"\\nFold {fold} BEST -> val_loss: {best_val_loss:.6f} | weighted_R2: {best_val_r2:.6f}\")\n    print(f\"Fold {fold} BEST per-target R2:\", best_per_r2)\n\n# ---- summary ----\nprint(\"\\n========== CV Summary ==========\")\nprint(\"Fold best losses:\", [f\"{x:.6f}\" for x in fold_losses])\nprint(\"Mean loss:\", float(np.mean(fold_losses)))\nprint(\"Std  loss:\", float(np.std(fold_losses)))\n\nprint(\"\\nFold best weighted R2:\", [f\"{x:.6f}\" for x in fold_r2])\nprint(\"Mean weighted R2:\", float(np.mean(fold_r2)))\nprint(\"Std  weighted R2:\", float(np.std(fold_r2)))\n\nfold_per_target_r2 = np.vstack(fold_per_target_r2)  # (folds, 5)\nprint(\"\\nMean per-target R2:\", fold_per_target_r2.mean(axis=0))\nprint(\"Std  per-target R2:\", fold_per_target_r2.std(axis=0))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-03T02:35:52.081660Z","iopub.execute_input":"2026-01-03T02:35:52.082288Z","iopub.status.idle":"2026-01-03T02:45:54.992045Z","shell.execute_reply.started":"2026-01-03T02:35:52.082244Z","shell.execute_reply":"2026-01-03T02:45:54.991215Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}