{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31234,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2026-01-07T09:53:19.126311Z","iopub.execute_input":"2026-01-07T09:53:19.126566Z","iopub.status.idle":"2026-01-07T09:53:20.406829Z","shell.execute_reply.started":"2026-01-07T09:53:19.126543Z","shell.execute_reply":"2026-01-07T09:53:20.40602Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\nimport numpy as np\nimport matplotlib.pyplot as plt\nfrom PIL import Image\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:21:21.239388Z","iopub.execute_input":"2026-01-07T10:21:21.240031Z","iopub.status.idle":"2026-01-07T10:21:21.243899Z","shell.execute_reply.started":"2026-01-07T10:21:21.24Z","shell.execute_reply":"2026-01-07T10:21:21.243149Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport torch\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import datasets\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport cv2\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:21:24.302243Z","iopub.execute_input":"2026-01-07T10:21:24.302529Z","iopub.status.idle":"2026-01-07T10:21:32.675456Z","shell.execute_reply.started":"2026-01-07T10:21:24.302503Z","shell.execute_reply":"2026-01-07T10:21:32.674886Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"base = \"/kaggle/input/csiro-biomass\"\nos.listdir(base)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:21:37.329286Z","iopub.execute_input":"2026-01-07T10:21:37.329954Z","iopub.status.idle":"2026-01-07T10:21:37.337664Z","shell.execute_reply.started":"2026-01-07T10:21:37.329922Z","shell.execute_reply":"2026-01-07T10:21:37.337106Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(f\"{base}/train.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:21:40.240774Z","iopub.execute_input":"2026-01-07T10:21:40.24154Z","iopub.status.idle":"2026-01-07T10:21:40.269672Z","shell.execute_reply.started":"2026-01-07T10:21:40.241512Z","shell.execute_reply":"2026-01-07T10:21:40.268927Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head(20)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:21:42.711846Z","iopub.execute_input":"2026-01-07T10:21:42.71257Z","iopub.status.idle":"2026-01-07T10:21:42.739141Z","shell.execute_reply.started":"2026-01-07T10:21:42.712539Z","shell.execute_reply":"2026-01-07T10:21:42.738583Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:07:16.543201Z","iopub.execute_input":"2025-12-30T13:07:16.544236Z","iopub.status.idle":"2025-12-30T13:07:16.550281Z","shell.execute_reply.started":"2025-12-30T13:07:16.544153Z","shell.execute_reply":"2025-12-30T13:07:16.549209Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T12:23:20.23317Z","iopub.execute_input":"2025-12-30T12:23:20.233751Z","iopub.status.idle":"2025-12-30T12:23:20.24868Z","shell.execute_reply.started":"2025-12-30T12:23:20.233724Z","shell.execute_reply":"2025-12-30T12:23:20.248103Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"wide = train_df.pivot(\n    index='image_path',\n    columns='target_name',\n    values='target'\n).reset_index()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:21:49.657624Z","iopub.execute_input":"2026-01-07T10:21:49.658337Z","iopub.status.idle":"2026-01-07T10:21:49.683185Z","shell.execute_reply.started":"2026-01-07T10:21:49.658308Z","shell.execute_reply":"2026-01-07T10:21:49.682669Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"wide.head(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:21:52.543203Z","iopub.execute_input":"2026-01-07T10:21:52.543911Z","iopub.status.idle":"2026-01-07T10:21:52.553359Z","shell.execute_reply.started":"2026-01-07T10:21:52.543883Z","shell.execute_reply":"2026-01-07T10:21:52.552873Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport seaborn as sns\nimport matplotlib.pyplot as plt\n\ncols = [\n    'Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', \n    'Dry_Total_g', 'GDM_g'\n]\n\ncorr_matrix = wide[cols].corr()\n\nplt.figure(figsize=(10, 8))\nsns.heatmap(\n    corr_matrix, \n    annot=True,          \n    cmap='coolwarm',     # Red for positive, Blue for negative\n    fmt=\".2f\",           \n    linewidths=0.5       # Space between squares\n)\n\nplt.title('Correlation Heatmap of Pasture Biomass Features')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:21:55.628527Z","iopub.execute_input":"2026-01-07T10:21:55.628895Z","iopub.status.idle":"2026-01-07T10:21:56.548757Z","shell.execute_reply.started":"2026-01-07T10:21:55.628869Z","shell.execute_reply":"2026-01-07T10:21:56.548051Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"targets = [\"Dry_Green_g\",\"Dry_Dead_g\",\"Dry_Clover_g\",\"GDM_g\",\"Dry_Total_g\"]\n\nfig, axes = plt.subplots(1,5, figsize=(18,3))\nfor ax, t in zip(axes, targets):\n    ax.hist(wide[t], bins=40)\n    ax.set_title(t)\n\nfig.tight_layout()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:01.075173Z","iopub.execute_input":"2026-01-07T10:22:01.076037Z","iopub.status.idle":"2026-01-07T10:22:01.811047Z","shell.execute_reply.started":"2026-01-07T10:22:01.076007Z","shell.execute_reply":"2026-01-07T10:22:01.810397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"targets = [\"Dry_Clover_g\",\"Dry_Dead_g\",\"Dry_Green_g\",\"Dry_Total_g\",\"GDM_g\"]\n\ncorr = wide[targets].corr()\ncorr","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:07:33.59872Z","iopub.execute_input":"2025-12-30T13:07:33.599196Z","iopub.status.idle":"2025-12-30T13:07:33.614547Z","shell.execute_reply.started":"2025-12-30T13:07:33.599151Z","shell.execute_reply":"2025-12-30T13:07:33.613559Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(6,5))\nplt.imshow(corr, cmap=\"viridis\", vmin=-1, vmax=1)\nplt.xticks(range(len(targets)), targets, rotation=45)\nplt.yticks(range(len(targets)), targets)\nplt.colorbar()\nplt.title(\"Target correlation\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:07:35.992222Z","iopub.execute_input":"2025-12-30T13:07:35.992574Z","iopub.status.idle":"2025-12-30T13:07:36.173158Z","shell.execute_reply.started":"2025-12-30T13:07:35.992544Z","shell.execute_reply":"2025-12-30T13:07:36.171992Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"height_per_image = (\n    train_df[['image_path', 'Height_Ave_cm']]\n    .drop_duplicates()\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:07.272623Z","iopub.execute_input":"2026-01-07T10:22:07.273361Z","iopub.status.idle":"2026-01-07T10:22:07.280873Z","shell.execute_reply.started":"2026-01-07T10:22:07.273333Z","shell.execute_reply":"2026-01-07T10:22:07.280116Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"wide = wide.merge(height_per_image, on='image_path', how='left')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:10.430382Z","iopub.execute_input":"2026-01-07T10:22:10.4307Z","iopub.status.idle":"2026-01-07T10:22:10.438268Z","shell.execute_reply.started":"2026-01-07T10:22:10.43065Z","shell.execute_reply":"2026-01-07T10:22:10.437553Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for t in targets:\n    plt.figure(figsize=(5,4))\n    plt.scatter(wide[\"Height_Ave_cm\"], wide[t], alpha=0.4)\n    plt.xlabel(\"Height (cm)\")\n    plt.ylabel(t)\n    plt.title(f\"{t} vs Height\")\n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:13.66557Z","iopub.execute_input":"2026-01-07T10:22:13.666321Z","iopub.status.idle":"2026-01-07T10:22:14.219523Z","shell.execute_reply.started":"2026-01-07T10:22:13.666287Z","shell.execute_reply":"2026-01-07T10:22:14.21897Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NDVI_per_image = (\n    train_df[['image_path','Pre_GSHH_NDVI']]\n    .drop_duplicates()\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:18.878003Z","iopub.execute_input":"2026-01-07T10:22:18.878509Z","iopub.status.idle":"2026-01-07T10:22:18.884534Z","shell.execute_reply.started":"2026-01-07T10:22:18.878479Z","shell.execute_reply":"2026-01-07T10:22:18.883991Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"wide = wide.merge(NDVI_per_image, on='image_path', how='left')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:21.881792Z","iopub.execute_input":"2026-01-07T10:22:21.882288Z","iopub.status.idle":"2026-01-07T10:22:21.888228Z","shell.execute_reply.started":"2026-01-07T10:22:21.882261Z","shell.execute_reply":"2026-01-07T10:22:21.88752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for t in targets:\n    plt.figure(figsize=(5,4))\n    plt.scatter(wide[\"Pre_GSHH_NDVI\"], wide[t], alpha=0.4)\n    plt.xlabel(\"NDVI\")\n    plt.ylabel(t)\n    plt.title(f\"{t} vs NDVI\")\n    plt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:07:52.143179Z","iopub.execute_input":"2025-12-30T13:07:52.144176Z","iopub.status.idle":"2025-12-30T13:07:52.85603Z","shell.execute_reply.started":"2025-12-30T13:07:52.144112Z","shell.execute_reply":"2025-12-30T13:07:52.85516Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\n\n# 1. One-hot encode the original train_df\nencoded_meta = pd.get_dummies(train_df[['image_path', 'State', 'Species']], \n                              columns=['State', 'Species'], \n                              dtype=float)\n\n# 2. Group by image_path to get one row per image\n# (Since state/species are constant per image, max() works perfectly)\nimage_meta_df = encoded_meta.groupby('image_path').max().reset_index()\n\n# 3. Identify the names of the new columns for later use in the Model\nencoded_cols = [col for col in image_meta_df.columns if col.startswith(('State_', 'Species_'))]\n\nprint(f\"Encoded {len(encoded_cols)} categorical features.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:27.944621Z","iopub.execute_input":"2026-01-07T10:22:27.944953Z","iopub.status.idle":"2026-01-07T10:22:27.959729Z","shell.execute_reply.started":"2026-01-07T10:22:27.944927Z","shell.execute_reply":"2026-01-07T10:22:27.958986Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"encoded_meta.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:07:59.140025Z","iopub.execute_input":"2025-12-30T13:07:59.140402Z","iopub.status.idle":"2025-12-30T13:07:59.166488Z","shell.execute_reply.started":"2025-12-30T13:07:59.140373Z","shell.execute_reply":"2025-12-30T13:07:59.165176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Merge the categorical metadata into your pivoted biomass data\ndf_pivoted = pd.merge(wide, image_meta_df, on='image_path', how='left')\n\n# Quick check to ensure no data was lost\nprint(f\"Final Shape: {df_pivoted.shape}\")\nprint(\"New Columns Sample:\", df_pivoted[encoded_cols].head(3))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:33.674847Z","iopub.execute_input":"2026-01-07T10:22:33.675551Z","iopub.status.idle":"2026-01-07T10:22:33.688054Z","shell.execute_reply.started":"2026-01-07T10:22:33.675519Z","shell.execute_reply":"2026-01-07T10:22:33.687466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#### import numpy as np\n\n# Apply log1p (log(1+x)) to the biomass targets\n# log1p is safer than log because it handles 0 values (like 0g of clover)\ntarget_cols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n\nfor col in target_cols:\n    df_pivoted[f'log_{col}'] = np.log1p(df_pivoted[col])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:46.826456Z","iopub.execute_input":"2026-01-07T10:22:46.827093Z","iopub.status.idle":"2026-01-07T10:22:46.831826Z","shell.execute_reply.started":"2026-01-07T10:22:46.827063Z","shell.execute_reply":"2026-01-07T10:22:46.831183Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.preprocessing import RobustScaler\n\nrobust_scaler = RobustScaler()\ndf_pivoted[['Height_Ave_cm', 'Pre_GSHH_NDVI']] = robust_scaler.fit_transform(df_pivoted[['Height_Ave_cm', 'Pre_GSHH_NDVI']])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:50.927515Z","iopub.execute_input":"2026-01-07T10:22:50.92822Z","iopub.status.idle":"2026-01-07T10:22:50.998755Z","shell.execute_reply.started":"2026-01-07T10:22:50.928186Z","shell.execute_reply":"2026-01-07T10:22:50.998206Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_pivoted[[f'log_{t}' for t in targets]].hist(figsize=(12, 10), bins=30, color='teal')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:08:15.362589Z","iopub.execute_input":"2025-12-30T13:08:15.363172Z","iopub.status.idle":"2025-12-30T13:08:16.294037Z","shell.execute_reply.started":"2025-12-30T13:08:15.363104Z","shell.execute_reply":"2025-12-30T13:08:16.293243Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nfrom PIL import Image\nfrom tqdm import tqdm\n\ndef resize_images(input_dir, output_dir, size=(256, 256)):\n    if not os.path.exists(output_dir):\n        os.makedirs(output_dir)\n        \n    images = [f for f in os.listdir(input_dir) if f.endswith(('jpg'))]\n    \n    print(f\"Resizing {len(images)} images to {size}...\")\n    for img_name in tqdm(images):\n        img_path = os.path.join(input_dir, img_name)\n        img = Image.open(img_path)\n        \n        # Using LANCZOS for high-quality downsampling\n        img_resized = img.resize(size, Image.Resampling.LANCZOS)\n        \n        img_resized.save(os.path.join(output_dir, img_name))\n\n# Example paths (update these for your Kaggle environment)\n# Note: On Kaggle, you'll save to '/kaggle/working/resized_train/'\nINPUT_PATH = '/kaggle/input/csiro-biomass/train'\nOUTPUT_PATH = './resized_train/'\n\nresize_images(INPUT_PATH, OUTPUT_PATH, size=(256, 256))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-07T10:22:56.113258Z","iopub.execute_input":"2026-01-07T10:22:56.114125Z","iopub.status.idle":"2026-01-07T10:23:22.48745Z","shell.execute_reply.started":"2026-01-07T10:22:56.114096Z","shell.execute_reply":"2026-01-07T10:23:22.486881Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# Load one resized image\ntest_img = Image.open('/kaggle/working/resized_train/ID1051144034.jpg')\nplt.imshow(test_img)\nplt.title(f\"Resized Shape: {test_img.size}\")\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:08:57.163066Z","iopub.execute_input":"2025-12-30T13:08:57.163466Z","iopub.status.idle":"2025-12-30T13:08:57.340231Z","shell.execute_reply.started":"2025-12-30T13:08:57.163436Z","shell.execute_reply":"2025-12-30T13:08:57.339246Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class AlbumentationsImageFolder(Dataset):\n    def __init__(self, root_dir, transform=None):\n        self.dataset = datasets.ImageFolder(root_dir)\n        self.transform = transform\n\n    def __len__(self):\n        return len(self.dataset)\n\n    def __getitem__(self, idx):\n        img, label = self.dataset[idx]\n        img = np.array(img)  # PIL → NumPy (HWC)\n\n        if self.transform:\n            img = self.transform(image=img)[\"image\"]\n\n        return img, label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:09:00.854931Z","iopub.execute_input":"2025-12-30T13:09:00.85564Z","iopub.status.idle":"2025-12-30T13:09:00.861793Z","shell.execute_reply.started":"2025-12-30T13:09:00.855607Z","shell.execute_reply":"2025-12-30T13:09:00.860788Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport cv2\n\nIMG_DIR = '/kaggle/working/resized_train'\n\nDEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\ntrain_transforms = A.Compose([\n    A.Resize(448, 448),\n\n    A.Rotate(\n        limit=10,\n        p=0.5,\n        border_mode=cv2.BORDER_REFLECT_101,  # 🔥 mirror padding\n        crop_border=False\n    ),\n\n    A.CenterCrop(224, 224),\n\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n\n    A.Normalize(\n        mean=(0.485, 0.456, 0.406),\n        std=(0.229, 0.224, 0.225)\n    ),\n\n    ToTensorV2()\n])\n\n\nval_transforms = A.Compose([\n    A.Resize(256, 256),\n    A.CenterCrop(224, 224),\n\n    A.Normalize(\n        mean=(0.485, 0.456, 0.406),\n        std=(0.229, 0.224, 0.225)\n    ),\n\n    ToTensorV2()\n])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:09:03.408005Z","iopub.execute_input":"2025-12-30T13:09:03.4091Z","iopub.status.idle":"2025-12-30T13:09:03.428327Z","shell.execute_reply.started":"2025-12-30T13:09:03.40906Z","shell.execute_reply":"2025-12-30T13:09:03.427227Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\n# 1. Define target columns (Raw values, not log)\ntarget_cols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n\n# 2. Identify all metadata columns (Height, NDVI + All One-Hot Columns)\n# We exclude image_path and target-related columns\nexclude = target_cols + ['image_path', 'stratify_col'] + [c for c in df_pivoted.columns if 'log_' in c]\nall_meta_features = [c for c in df_pivoted.columns if c not in exclude]\n\n# 3. Final NaN check (very important for neural networks)\ndf_pivoted[target_cols] = df_pivoted[target_cols].fillna(0).astype(np.float32)\ndf_pivoted[all_meta_features] = df_pivoted[all_meta_features].fillna(0).astype(np.float32)\n\n# 4. Perform the Split\ntrain_df, val_df = train_test_split(\n    df_pivoted, \n    test_size=0.2, \n    random_state=42 \n)\n\nprint(f\"Features being used: {len(all_meta_features)}\")\nprint(f\"Training samples: {len(train_df)} | Validation samples: {len(val_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:09:06.922171Z","iopub.execute_input":"2025-12-30T13:09:06.92252Z","iopub.status.idle":"2025-12-30T13:09:07.06776Z","shell.execute_reply.started":"2025-12-30T13:09:06.922492Z","shell.execute_reply":"2025-12-30T13:09:07.066857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a strict list of columns that exist in the dataframe\nexisting_columns = train_df.columns.tolist()\nall_meta_features = [col for col in all_meta_features if col in existing_columns]\n\nprint(f\"Verified {len(all_meta_features)} metadata features are present in the dataframe.\")\n# If this is not 21, the model initialization needs to change to this number.","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:09:09.693058Z","iopub.execute_input":"2025-12-30T13:09:09.693635Z","iopub.status.idle":"2025-12-30T13:09:09.70033Z","shell.execute_reply.started":"2025-12-30T13:09:09.6936Z","shell.execute_reply":"2025-12-30T13:09:09.699372Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class CSIRODataset(Dataset):\n    def __init__(self, df, img_dir, transform=None, meta_cols=[]):\n        # We ensure the dataframe is clean upon initialization\n        self.df = df.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.transform = transform\n        self.meta_cols = meta_cols \n        self.target_cols = ['log_Dry_Clover_g', 'log_Dry_Dead_g', 'log_Dry_Green_g', 'log_Dry_Total_g', 'log_GDM_g']\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        # 1. Robust Image Loading\n        img_filename = os.path.basename(row['image_path'])\n        img_path = os.path.join(self.img_dir, img_filename)\n        image = Image.open(img_path).convert('RGB')\n        \n        if self.transform:\n            image = self.transform(image=np.array(image))[\"image\"]\n\n        # 2. Metadata Handling\n        meta_values = row[self.meta_cols].values.astype(np.float32)\n        # Final check: replace any NaN/Inf in metadata with 0\n        meta_values = np.nan_to_num(meta_values, nan=0.0, posinf=0.0, neginf=0.0)\n        meta = torch.tensor(meta_values)\n\n        # 3. Target Sanitization (The critical fix)\n        target_values = row[self.target_cols].values.astype(np.float32)\n        \n        # FORCE all targets to be finite numbers. \n        # neginf=0.0 handles the log(0) -> -inf case.\n        target_values = np.nan_to_num(target_values, nan=0.0, posinf=10.0, neginf=0.0)\n        \n        targets = torch.tensor(target_values)\n        \n        return image, meta, targets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:09:12.182896Z","iopub.execute_input":"2025-12-30T13:09:12.183326Z","iopub.status.idle":"2025-12-30T13:09:12.192084Z","shell.execute_reply.started":"2025-12-30T13:09:12.183295Z","shell.execute_reply":"2025-12-30T13:09:12.191191Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Re-identify the meta columns from your PIVOTED dataframe\nmeta_cols = ['Height_Ave_cm', 'Pre_GSHH_NDVI']\nstate_cols = [c for c in wide.columns if c.startswith('State_')]\nspecies_cols = [c for c in wide.columns if c.startswith('Species_')]\nall_meta_features = meta_cols + state_cols + species_cols\n\n# 2. Update the target columns to use the LOG versions\ntarget_cols = ['log_Dry_Clover_g', 'log_Dry_Dead_g', 'log_Dry_Green_g', 'log_Dry_Total_g', 'log_GDM_g']\n\n# 3. Create the clean train/val split from the PIVOTED data\nfrom sklearn.model_selection import train_test_split\ntrain_df, val_df = train_test_split(wide, test_size=0.2, random_state=42)\n\nprint(f\"Verified Feature Count: {len(all_meta_features)}\")\nprint(f\"Targets: {target_cols}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:09:19.982032Z","iopub.execute_input":"2025-12-30T13:09:19.982898Z","iopub.status.idle":"2025-12-30T13:09:19.991259Z","shell.execute_reply.started":"2025-12-30T13:09:19.982865Z","shell.execute_reply":"2025-12-30T13:09:19.990321Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\n\ndef imshow(img):\n    # Un-normalize\n    img = img.numpy().transpose((1, 2, 0))\n    mean = np.array([0.485, 0.456, 0.406])\n    std = np.array([0.229, 0.224, 0.225])\n    img = std * img + mean\n    img = np.clip(img, 0, 1)\n    plt.imshow(img)\n\n# Grab one batch\nimages, metas, targets = next(iter(train_loader))\n\nplt.figure(figsize=(12, 6))\nfor i in range(4):\n    plt.subplot(1, 4, i+1)\n    imshow(images[i])\n    plt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:10:35.64733Z","iopub.execute_input":"2025-12-30T13:10:35.648249Z","iopub.status.idle":"2025-12-30T13:10:36.278363Z","shell.execute_reply.started":"2025-12-30T13:10:35.648208Z","shell.execute_reply":"2025-12-30T13:10:36.277315Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_pivoted.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T12:26:33.618399Z","iopub.execute_input":"2025-12-30T12:26:33.618911Z","iopub.status.idle":"2025-12-30T12:26:33.63973Z","shell.execute_reply.started":"2025-12-30T12:26:33.618885Z","shell.execute_reply":"2025-12-30T12:26:33.639104Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Numerical inputs we scaled earlier\nnumeric_cols = ['Height_Ave_cm', 'Pre_GSHH_NDVI']\n\n# All metadata features combined\nall_meta_features = numeric_cols + encoded_cols\n\nprint(f\"Total features for the Metadata branch: {len(all_meta_features)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:09:29.97778Z","iopub.execute_input":"2025-12-30T13:09:29.978162Z","iopub.status.idle":"2025-12-30T13:09:29.983968Z","shell.execute_reply.started":"2025-12-30T13:09:29.978113Z","shell.execute_reply":"2025-12-30T13:09:29.982915Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Define all column groups\ntarget_cols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nnumeric_cols = ['Height_Ave_cm', 'Pre_GSHH_NDVI']\n# encoded_cols are the state_ and species_ columns we just merged\n\n# 2. Convert every data column to float, forcing errors to NaN\nall_data_cols = target_cols + numeric_cols + encoded_cols\nfor col in all_data_cols:\n    df_pivoted[col] = pd.to_numeric(df_pivoted[col], errors='coerce')\n\n# 3. Fill any missing values with 0.0\n# (Essential for Tweedie/Gamma since they can't handle NaNs)\ndf_pivoted[all_data_cols] = df_pivoted[all_data_cols].fillna(0.0).astype(np.float32)\n\n# Verify types - everything should be float32\nprint(df_pivoted[all_data_cols].dtypes.value_counts())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:09:44.853035Z","iopub.execute_input":"2025-12-30T13:09:44.853459Z","iopub.status.idle":"2025-12-30T13:09:44.871169Z","shell.execute_reply.started":"2025-12-30T13:09:44.85343Z","shell.execute_reply":"2025-12-30T13:09:44.870081Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_pivoted.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T12:05:09.286292Z","iopub.execute_input":"2025-12-28T12:05:09.286637Z","iopub.status.idle":"2025-12-28T12:05:09.332497Z","shell.execute_reply.started":"2025-12-28T12:05:09.28659Z","shell.execute_reply":"2025-12-28T12:05:09.331113Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\n\nclass BiomassDINOv2Model(nn.Module):\n    def __init__(self, model_type='dinov2_vits14', num_meta_features=21, pretrained=True):\n        \"\"\"\n        Args:\n            model_type (str): The DINOv2 variant ('dinov2_vits14' is the small/fast one).\n            num_meta_features (int): Number of tabular features (your meta_cols).\n            pretrained (bool): Whether to load weights from Facebook's TorchHub.\n        \"\"\"\n        super(BiomassDINOv2Model, self).__init__()\n        \n        # 1. Load DINOv2 Backbone\n        # ViT-S/14: output dim = 384\n        # ViT-B/14: output dim = 768\n        if pretrained:\n            self.backbone = torch.hub.load('facebookresearch/dinov2', model_type)\n        else:\n            # Load with random weights if needed (usually not recommended for this task)\n            self.backbone = torch.hub.load('facebookresearch/dinov2', model_type, pretrained=False)\n            \n        self.img_feature_dim = self.backbone.embed_dim \n        \n        # 2. Metadata MLP (Handles your 21 columns)\n        # Using BatchNorm and ReLU for stable training of tabular data\n        self.meta_mlp = nn.Sequential(\n            nn.Linear(num_meta_features, 128),\n            nn.BatchNorm1d(128),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(128, 64),\n            nn.ReLU()\n        )\n        \n        # 3. Final Fusion Head\n        # Concatenates: Image Features (384) + Meta Features (64) = 448\n        self.fusion_dim = self.img_feature_dim + 64\n        \n        self.regressor = nn.Sequential(\n            nn.Linear(self.fusion_dim, 256),\n            nn.ReLU(),\n            nn.Dropout(0.2),\n            nn.Linear(256, 128),\n            nn.ReLU(),\n            nn.Linear(128, 5) # Outputting the 5 log-biomass targets\n        )\n\n    def forward(self, image, meta):\n        \"\"\"\n        image: (batch, 3, 224, 224)\n        meta:  (batch, 21)\n        \"\"\"\n        # Get Global Image Features (CLS token)\n        img_feats = self.backbone(image) \n        \n        # Get Metadata Features\n        meta_feats = self.meta_mlp(meta)\n        \n        # Fusion (Concatenate along the feature dimension)\n        combined = torch.cat((img_feats, meta_feats), dim=1)\n        \n        # Final Regression\n        output = self.regressor(combined)\n        return output\n\n    def freeze_backbone(self):\n        \"\"\"Useful for the first few epochs to let the MLP warm up\"\"\"\n        for param in self.backbone.parameters():\n            param.requires_grad = False\n\n    def unfreeze_backbone(self):\n        \"\"\"Call this after warm-up to fine-tune the ViT\"\"\"\n        for param in self.backbone.parameters():\n            param.requires_grad = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:09:49.255223Z","iopub.execute_input":"2025-12-30T13:09:49.255579Z","iopub.status.idle":"2025-12-30T13:09:49.26647Z","shell.execute_reply.started":"2025-12-30T13:09:49.255551Z","shell.execute_reply":"2025-12-30T13:09:49.26549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BiomassDINOv2Model(nn.Module):\n    def __init__(self, model_type='dinov2_vits14', num_meta_features=21):\n        super(BiomassDINOv2Model, self).__init__()\n        self.backbone = torch.hub.load('facebookresearch/dinov2', model_type)\n        embed_dim = self.backbone.embed_dim \n        \n        self.meta_mlp = nn.Sequential(\n            nn.BatchNorm1d(num_meta_features),\n            nn.Linear(num_meta_features, 128),\n            nn.ReLU(),\n            nn.Dropout(0.2), # Regularization\n            nn.Linear(128, 64),\n            nn.ReLU()\n        )\n        \n        self.regressor = nn.Sequential(\n            nn.Linear(embed_dim + 64, 256),\n            nn.ReLU(),\n            nn.Dropout(0.3), # Higher dropout for the fusion head\n            nn.Linear(256, 128),\n            nn.ReLU(),\n            nn.Linear(128, 5),\n            nn.Softplus() # Ensures positive outputs\n        )\n\n    def forward(self, image, meta):\n        img_feats = self.backbone(image)\n        meta_feats = self.meta_mlp(meta)\n        combined = torch.cat((img_feats, meta_feats), dim=1)\n        return self.regressor(combined)\n\n    def freeze_backbone(self):\n        for param in self.backbone.parameters(): param.requires_grad = False\n    def unfreeze_backbone(self):\n        for param in self.backbone.parameters(): param.requires_grad = True","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T12:32:36.859638Z","iopub.execute_input":"2025-12-30T12:32:36.860334Z","iopub.status.idle":"2025-12-30T12:32:36.866805Z","shell.execute_reply.started":"2025-12-30T12:32:36.860304Z","shell.execute_reply":"2025-12-30T12:32:36.866098Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_pivoted.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-28T12:19:10.994024Z","iopub.execute_input":"2025-12-28T12:19:10.994436Z","iopub.status.idle":"2025-12-28T12:19:11.046147Z","shell.execute_reply.started":"2025-12-28T12:19:10.994395Z","shell.execute_reply":"2025-12-28T12:19:11.044008Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport torch\n\n# 1. Clean the master pivoted dataframe\n# Replace inf with 0 and fill NaNs\ndf_pivoted = df_pivoted.replace([np.inf, -np.inf], 0).fillna(0)\n\n# 2. Force the log columns to be finite (This removes the \"Targets - Any Inf? True\" issue)\nlog_cols = [c for c in df_pivoted.columns if 'log_' in c]\nfor col in log_cols:\n    df_pivoted[col] = np.nan_to_num(df_pivoted[col].values, nan=0.0, posinf=0.0, neginf=0.0)\n\n# 3. Update train_df and val_df with the cleaned 'wide' data\n# We use the image_path to make sure the right rows go to the right split\ntrain_paths = train_df['image_path'].unique()\nval_paths = val_df['image_path'].unique()\n\ntrain_df = df_pivoted[df_pivoted['image_path'].isin(train_paths)].copy()\nval_df = df_pivoted[df_pivoted['image_path'].isin(val_paths)].copy()\n\nprint(f\"Cleaned Train rows: {len(train_df)} | Cleaned Val rows: {len(val_df)}\")\nprint(f\"Infinities remaining in Train targets: {np.isinf(train_df[log_cols]).values.sum()}\")\n\n# 4. RE-INITIALIZE DATASETS AND LOADERS (CRITICAL STEP)\n# This pushes the clean data into the model's reach\ntrain_ds = CSIRODataset(train_df, IMG_DIR, transform=train_transforms, meta_cols=all_meta_features)\nval_ds = CSIRODataset(val_df, IMG_DIR, transform=val_transforms, meta_cols=all_meta_features)\n\ntrain_loader = DataLoader(train_ds, batch_size=16, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_ds, batch_size=16, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T16:35:45.728966Z","iopub.execute_input":"2025-12-30T16:35:45.729641Z","iopub.status.idle":"2025-12-30T16:35:45.7793Z","shell.execute_reply.started":"2025-12-30T16:35:45.729573Z","shell.execute_reply":"2025-12-30T16:35:45.778062Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\n\n# 1. Define your specific raw target columns\nraw_targets = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n\n# 2. Clean the raw data in pivoted_df\n# We replace NaNs with 0 and ensure no raw values are infinite\nfor col in raw_targets:\n    df_pivoted[col] = np.nan_to_num(df_pivoted[col].values, nan=0.0, posinf=1e6, neginf=0.0)\n\n# 3. Create the log-transformed columns SAFELY\n# Using log1p (log(1+x)) ensures that 0 becomes 0, not -inf\nfor col in raw_targets:\n    df_pivoted[f'log_{col}'] = np.log1p(df_pivoted[col].clip(lower=0))\n\n# 4. VERIFICATION - This must be 0\nlog_cols = [f'log_{c}' for c in raw_targets]\nremaining_inf = np.isinf(df_pivoted[log_cols].values).sum()\nprint(f\"Verified: {remaining_inf} infinities remain in log columns.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T16:35:55.983311Z","iopub.execute_input":"2025-12-30T16:35:55.983661Z","iopub.status.idle":"2025-12-30T16:35:56.002467Z","shell.execute_reply.started":"2025-12-30T16:35:55.983632Z","shell.execute_reply":"2025-12-30T16:35:56.001433Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nfrom torchvision import transforms\n\n# 1. Load raw data\ntrain_raw = pd.read_csv('/kaggle/input/csiro-biomass/train.csv')\n\n# 2. Pivot the data from long to wide format\n# This converts the 5 target rows per image into 1 row with 5 columns\ncols = ['image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm']\nagg_train_df = train_raw.groupby(cols).apply(\n    lambda df: df.set_index('target_name')['target']\n).reset_index()\n\n# 3. Define Image Transforms (Resize to 518 for DINOv2)\ntrain_aug = transforms.Compose([\n    transforms.Resize((518, 518)),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\nval_aug = transforms.Compose([\n    transforms.Resize((518, 518)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\nprint(f\"Aggregation complete. Unique images: {len(agg_train_df)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T16:36:04.267543Z","iopub.execute_input":"2025-12-30T16:36:04.26787Z","iopub.status.idle":"2025-12-30T16:36:04.458308Z","shell.execute_reply.started":"2025-12-30T16:36:04.267844Z","shell.execute_reply":"2025-12-30T16:36:04.457436Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Updated aggregation to silence the DeprecationWarning\ncols = ['image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm']\nagg_train_df = train_raw.groupby(cols).apply(\n    lambda df: df.set_index('target_name')['target'],\n    include_groups=False # This silences the warning\n).reset_index()\n\n# Setup Stratified Group K-Fold\nfrom sklearn.model_selection import StratifiedGroupKFold\nsgkf = StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=42)\nagg_train_df['fold'] = -1\n\nfor i, (t_idx, v_idx) in enumerate(sgkf.split(agg_train_df, y=agg_train_df.State, groups=agg_train_df.Sampling_Date)):\n    agg_train_df.loc[v_idx, 'fold'] = i","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T16:36:08.066399Z","iopub.execute_input":"2025-12-30T16:36:08.066755Z","iopub.status.idle":"2025-12-30T16:36:08.230502Z","shell.execute_reply.started":"2025-12-30T16:36:08.066727Z","shell.execute_reply":"2025-12-30T16:36:08.229474Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Pivot the data to get the 5 biomass targets in one row\ncols = ['image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm']\nagg_train_df = train_raw.groupby(cols).apply(\n    lambda df: df.set_index('target_name')['target'],\n    include_groups=False\n).reset_index()\n\n# 2. CREATE THE LOG COLUMNS (The missing step causing your KeyError)\ntarget_cols = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g']\nfor col in target_cols:\n    agg_train_df[f'log_{col}'] = np.log1p(agg_train_df[col].values.astype(np.float32))\n\n# 3. Setup the Folds\nfrom sklearn.model_selection import StratifiedGroupKFold\nsgkf = StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=42)\nagg_train_df['fold'] = -1\nfor i, (t_idx, v_idx) in enumerate(sgkf.split(agg_train_df, y=agg_train_df.State, groups=agg_train_df.Sampling_Date)):\n    agg_train_df.loc[v_idx, 'fold'] = i\n\nprint(\"Columns in agg_train_df:\", agg_train_df.columns.tolist())\n# Check if 'log_Dry_Green_g' is in the list above. It must be there!","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T16:36:12.418545Z","iopub.execute_input":"2025-12-30T16:36:12.418895Z","iopub.status.idle":"2025-12-30T16:36:12.58236Z","shell.execute_reply.started":"2025-12-30T16:36:12.418862Z","shell.execute_reply":"2025-12-30T16:36:12.581322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Identify all potential features\n# We exclude the image path and all target/log-target columns\nall_cols = list(df_pivoted.columns)\ntarget_cols = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nlog_target_cols = [f'log_{c}' for c in target_cols]\nexclude = ['image_path'] + target_cols + log_target_cols\n\n# Dynamically find the features\nfeatures = [c for c in all_cols if c not in exclude]\n\nprint(f\"Total features found in dataframe: {len(features)}\")\n\n# 2. Force the selection to the FIRST 21 features (or specifically name them)\n# If you have 26 and only want 21, you must be specific\nfinal_meta_features = features[:21] \n\nprint(f\"Count of final_meta_features: {len(final_meta_features)}\")\nprint(\"--- Features being used ---\")\nfor i, f in enumerate(final_meta_features):\n    print(f\"{i+1}: {f}\")\n\n# 3. Update your Dataset initialization to use this STRICT list\n# train_ds = CSIRODataset(train_df, ..., meta_cols=final_meta_features)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T16:36:16.68219Z","iopub.execute_input":"2025-12-30T16:36:16.682533Z","iopub.status.idle":"2025-12-30T16:36:16.692215Z","shell.execute_reply.started":"2025-12-30T16:36:16.682506Z","shell.execute_reply":"2025-12-30T16:36:16.691066Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"pip install --upgrade timm pydantic","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T13:11:47.152806Z","iopub.execute_input":"2025-12-30T13:11:47.153201Z","iopub.status.idle":"2025-12-30T13:11:51.70147Z","shell.execute_reply.started":"2025-12-30T13:11:47.15317Z","shell.execute_reply":"2025-12-30T13:11:51.700401Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport gc\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import StratifiedGroupKFold\nfrom tqdm.auto import tqdm\nimport timm\nfrom torchvision import transforms\n\n# 1. --- COMPETITION METRIC ---\ndef weighted_r2_score(y_true, y_pred):\n    weights = np.array([0.1, 0.1, 0.1, 0.2, 0.5])\n    r2_scores = []\n    for i in range(5):\n        y_t, y_p = y_true[:, i], y_pred[:, i]\n        ss_res = np.sum((y_t - y_p) ** 2)\n        ss_tot = np.sum((y_t - np.mean(y_t)) ** 2)\n        r2 = 1 - ss_res / ss_tot if ss_tot > 0 else 0.0\n        r2_scores.append(r2)\n    \n    r2_scores = np.array(r2_scores)\n    weighted_r2 = np.sum(r2_scores * weights)\n    return weighted_r2, r2_scores\n\n# 2. --- DATA PREPARATION ---\n# (Assumes train_raw is already loaded in your environment)\n\n\ntarget_cols = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g']\nfor col in target_cols:\n    agg_train_df[f'log_{col}'] = np.log1p(agg_train_df[col].values.astype(np.float32))\n\nsgkf = StratifiedGroupKFold(n_splits=5, shuffle=True, random_state=42)\nagg_train_df['fold'] = -1\nfor i, (t_idx, v_idx) in enumerate(sgkf.split(agg_train_df, y=agg_train_df.State, groups=agg_train_df.Sampling_Date)):\n    agg_train_df.loc[v_idx, 'fold'] = i\n\n# 3. --- DATASET & MODEL ---\nclass MetaRegressionDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None):\n        self.df = df.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.transform = transform\n        self.target_res = (518, 518)\n\n    def __len__(self): return len(self.df)\n\n    def __getitem__(self, idx):\n        item = self.df.iloc[idx]\n        img_path = os.path.join(self.img_dir, os.path.basename(item['image_path']))\n        image = Image.open(img_path).convert('RGB')\n        \n        meta = torch.tensor([item['Pre_GSHH_NDVI'], item['Height_Ave_cm']], dtype=torch.float32)\n        targets = torch.tensor([item['log_Dry_Green_g'], item['log_Dry_Dead_g'], item['log_Dry_Clover_g']], dtype=torch.float32)\n        \n        w, h = image.size\n        left = self.transform(image.crop((0, 0, w//2, h)).resize(self.target_res, Image.BILINEAR))\n        right = self.transform(image.crop((w//2, 0, w, h)).resize(self.target_res, Image.BILINEAR))\n        return left, right, meta, targets\n\nclass MetaMambaDINO(nn.Module):\n    def __init__(self, model_name='vit_base_patch14_dinov2', num_meta=2):\n        super().__init__()\n        # Ensure model is loaded on CPU\n        self.backbone = timm.create_model(model_name, pretrained=True, num_classes=0)\n        embed_dim = self.backbone.num_features\n        self.meta_head = nn.Sequential(\n            nn.LayerNorm(num_meta),\n            nn.Linear(num_meta, 128),\n            nn.GELU(),\n            nn.Dropout(0.3)\n        )\n        self.regressor = nn.Sequential(\n            nn.Linear((embed_dim * 2) + 128, 512),\n            nn.GELU(),\n            nn.Dropout(0.4),\n            nn.Linear(512, 3) \n        )\n\n    def forward(self, x_l, x_r, meta):\n        feat = torch.cat([self.backbone(x_l), self.backbone(x_r), self.meta_head(meta)], dim=1)\n        return self.regressor(feat)\n\n# 4. --- TRAINING FUNCTION ---\ndef train_fold(fold_idx, img_dir):\n    # FORCE CPU\n    device = torch.device('cpu')\n    print(f\"--- Training Fold {fold_idx} on {device} ---\")\n    \n    train_df = agg_train_df[agg_train_df.fold != fold_idx]\n    val_df = agg_train_df[agg_train_df.fold == fold_idx]\n    \n    \n    train_loader = DataLoader(MetaRegressionDataset(train_df, img_dir, train_aug), batch_size=4, shuffle=True, num_workers=0)\n    val_loader = DataLoader(MetaRegressionDataset(val_df, img_dir, val_aug), batch_size=4, shuffle=False, num_workers=0)\n    \n    model = MetaMambaDINO().to(device)\n    for param in model.backbone.parameters(): param.requires_grad = False\n    \n    optimizer = torch.optim.AdamW(model.parameters(), lr=2e-4, weight_decay=0.05)\n    scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=30)\n    criterion = nn.HuberLoss()\n    \n    best_wr2, early_stop_cnt = -float('inf'), 0\n    \n    for epoch in range(30):\n        if epoch == 5:\n            for param in model.backbone.parameters(): param.requires_grad = True\n            optimizer.param_groups[0]['lr'] = 5e-5\n\n        model.train()\n        total_loss = 0\n        pbar = tqdm(train_loader, desc=f\"Fold {fold_idx} Ep {epoch+1}\")\n        for xl, xr, meta, y in pbar:\n            # Removed .to(device) calls for cleaner CPU execution\n            optimizer.zero_grad()\n            out = model(xl, xr, meta)\n            loss = criterion(out, y)\n            loss.backward()\n            optimizer.step()\n            total_loss += loss.item()\n\n        model.eval()\n        preds, targs = [], []\n        with torch.no_grad():\n            for xl, xr, meta, y in val_loader:\n                out = model(xl, xr, meta)\n                p_g, y_g = torch.expm1(out).numpy(), torch.expm1(y).numpy()\n                p_5 = np.column_stack([p_g, p_g[:,0]+p_g[:,2], p_g[:,0]+p_g[:,2]+p_g[:,1]])\n                y_5 = np.column_stack([y_g, y_g[:,0]+y_g[:,2], y_g[:,0]+y_g[:,2]+y_g[:,1]])\n                preds.append(p_5); targs.append(y_5)\n        \n        wr2, _ = weighted_r2_score(np.vstack(targs), np.vstack(preds))\n        print(f\"Fold {fold_idx} Epoch {epoch+1} | WR2: {wr2:.4f}\")\n        \n        if wr2 > best_wr2:\n            best_wr2 = wr2\n            torch.save(model.state_dict(), f\"best_fold_{fold_idx}.pth\")\n            early_stop_cnt = 0\n        else:\n            early_stop_cnt += 1\n            if early_stop_cnt >= 7: break\n        scheduler.step()\n\n# 5. --- EXECUTION ---\ntrain_aug = transforms.Compose([\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\nval_aug = transforms.Compose([\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])\n])\n\nIMG_DIR = '/kaggle/working/resized_train' \nfor i in range(5):\n    train_fold(i, IMG_DIR)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-30T16:37:55.992751Z","iopub.execute_input":"2025-12-30T16:37:55.994926Z"}},"outputs":[],"execution_count":null}]}