{"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":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\n\nimport os\nos.environ[\"TORCHDYNAMO_DISABLE\"] = \"1\"","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:37.838943Z","iopub.execute_input":"2026-01-14T12:13:37.839688Z","iopub.status.idle":"2026-01-14T12:13:37.843343Z","shell.execute_reply.started":"2026-01-14T12:13:37.839665Z","shell.execute_reply":"2026-01-14T12:13:37.842466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:37.844208Z","iopub.execute_input":"2026-01-14T12:13:37.844430Z","iopub.status.idle":"2026-01-14T12:13:37.857692Z","shell.execute_reply.started":"2026-01-14T12:13:37.844410Z","shell.execute_reply":"2026-01-14T12:13:37.857112Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Importing tabular data (csv format)","metadata":{}},{"cell_type":"code","source":"train = pd.read_csv('/kaggle/input/csiro-biomass/train.csv')\ntest = pd.read_csv('/kaggle/input/csiro-biomass/test.csv')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:37.858762Z","iopub.execute_input":"2026-01-14T12:13:37.858975Z","iopub.status.idle":"2026-01-14T12:13:37.879391Z","shell.execute_reply.started":"2026-01-14T12:13:37.858960Z","shell.execute_reply":"2026-01-14T12:13:37.878675Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## EDA for tabular data","metadata":{}},{"cell_type":"code","source":"train.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:37.880070Z","iopub.execute_input":"2026-01-14T12:13:37.880294Z","iopub.status.idle":"2026-01-14T12:13:37.884941Z","shell.execute_reply.started":"2026-01-14T12:13:37.880271Z","shell.execute_reply":"2026-01-14T12:13:37.884228Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:37.886585Z","iopub.execute_input":"2026-01-14T12:13:37.886917Z","iopub.status.idle":"2026-01-14T12:13:37.903775Z","shell.execute_reply.started":"2026-01-14T12:13:37.886893Z","shell.execute_reply":"2026-01-14T12:13:37.903138Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:37.904393Z","iopub.execute_input":"2026-01-14T12:13:37.904652Z","iopub.status.idle":"2026-01-14T12:13:37.920708Z","shell.execute_reply.started":"2026-01-14T12:13:37.904631Z","shell.execute_reply":"2026-01-14T12:13:37.920069Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.describe()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:37.921372Z","iopub.execute_input":"2026-01-14T12:13:37.921582Z","iopub.status.idle":"2026-01-14T12:13:37.943272Z","shell.execute_reply.started":"2026-01-14T12:13:37.921567Z","shell.execute_reply":"2026-01-14T12:13:37.942574Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Target Distributions","metadata":{}},{"cell_type":"code","source":"plt.figure(figsize=(15, 8))\nsns.histplot(data=train, x=\"target\", hue=\"target_name\", element=\"step\", stat=\"density\", common_norm=False)\nplt.title(\"Target Distributions\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:37.944126Z","iopub.execute_input":"2026-01-14T12:13:37.944487Z","iopub.status.idle":"2026-01-14T12:13:38.214344Z","shell.execute_reply.started":"2026-01-14T12:13:37.944463Z","shell.execute_reply":"2026-01-14T12:13:38.213669Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Correlation Between Targets","metadata":{}},{"cell_type":"code","source":"train['image_id'] = train['sample_id'].str.split('__').str[0]\ntargets = train.pivot_table(\n    index=\"image_id\",\n    columns=\"target_name\",\n    values=\"target\",\n    aggfunc='mean'\n)\n\ntargets = targets.dropna()  \n\nplt.figure(figsize=(8,6))\nsns.heatmap(targets.corr(), cmap=\"viridis\", annot=True)\nplt.title(\"Correlation Between Biomass Targets\")\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:38.215185Z","iopub.execute_input":"2026-01-14T12:13:38.215480Z","iopub.status.idle":"2026-01-14T12:13:38.435952Z","shell.execute_reply.started":"2026-01-14T12:13:38.215460Z","shell.execute_reply":"2026-01-14T12:13:38.435313Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Relationship Between NDVI / Height and Targets","metadata":{}},{"cell_type":"code","source":"tmp = train.copy()\n\nfig, axes = plt.subplots(1, 2, figsize=(12, 5))\n\nsns.scatterplot(ax=axes[0], data=tmp, x=\"Pre_GSHH_NDVI\", y=\"target\", hue=\"target_name\")\naxes[0].set_title(\"NDVI vs Biomass\")\n\nsns.scatterplot(ax=axes[1], data=tmp, x=\"Height_Ave_cm\", y=\"target\", hue=\"target_name\")\naxes[1].set_title(\"Height vs Biomass\")\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:38.437883Z","iopub.execute_input":"2026-01-14T12:13:38.438428Z","iopub.status.idle":"2026-01-14T12:13:39.063288Z","shell.execute_reply.started":"2026-01-14T12:13:38.438407Z","shell.execute_reply":"2026-01-14T12:13:39.062611Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train[\"Species\"].value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:39.064020Z","iopub.execute_input":"2026-01-14T12:13:39.064276Z","iopub.status.idle":"2026-01-14T12:13:39.070921Z","shell.execute_reply.started":"2026-01-14T12:13:39.064248Z","shell.execute_reply":"2026-01-14T12:13:39.070277Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Importing images","metadata":{}},{"cell_type":"code","source":"from pathlib import Path\nfrom PIL import Image\nimport random","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:39.071700Z","iopub.execute_input":"2026-01-14T12:13:39.071924Z","iopub.status.idle":"2026-01-14T12:13:39.083528Z","shell.execute_reply.started":"2026-01-14T12:13:39.071910Z","shell.execute_reply":"2026-01-14T12:13:39.082815Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"INPUT_PATH = Path(\"/kaggle/input/csiro-biomass/\")\ntrain_path = INPUT_PATH / 'train'\ntest_path = INPUT_PATH / 'test'\n\ntrain_images = [f for f in os.listdir(train_path)]\ntest_images = [f for f in os.listdir(test_path)]\n\nprint(f\"Total Train Images: {len(train_images)}\")\nprint(f\"Total Test Images: {len(test_images)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:39.084235Z","iopub.execute_input":"2026-01-14T12:13:39.084491Z","iopub.status.idle":"2026-01-14T12:13:39.098326Z","shell.execute_reply.started":"2026-01-14T12:13:39.084469Z","shell.execute_reply":"2026-01-14T12:13:39.097645Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"random_ids = random.sample(list(targets.index), 20)\n\nplt.figure(figsize=(16, 14))\n\nfor i, img_id in enumerate(random_ids):\n    img_file = f\"{img_id}.jpg\"\n    img_path = train_path / img_file\n    \n    img = np.array(Image.open(img_path))\n    \n    row = targets.loc[img_id]\n    target_text = \"\\n\".join(f\"{col}: {row[col]:.2f}\" for col in targets.columns)\n    \n    plt.subplot(4, 5, i + 1)\n    plt.imshow(img)\n    plt.axis(\"off\")\n    plt.title(target_text, fontsize=8)\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:39.098930Z","iopub.execute_input":"2026-01-14T12:13:39.099092Z","iopub.status.idle":"2026-01-14T12:13:45.390041Z","shell.execute_reply.started":"2026-01-14T12:13:39.099079Z","shell.execute_reply":"2026-01-14T12:13:45.389044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ndvi_df = train[['image_id', 'Pre_GSHH_NDVI']].drop_duplicates()\n\nlow_ndvi = ndvi_df.nsmallest(6, \"Pre_GSHH_NDVI\")['image_id']\n\nplt.figure(figsize=(12, 12))\n\nfor i, img_id in enumerate(low_ndvi):\n    img_file = f\"{img_id}.jpg\"\n    img_path = train_path / img_file\n    \n    img = np.array(Image.open(img_path))\n    \n    plt.subplot(3, 3, i + 1)\n    plt.imshow(img)\n    plt.axis(\"off\")\n    plt.title(f\"{img_id}\\nNDVI={train[train.image_id==img_id].Pre_GSHH_NDVI.iloc[0]:.2f}\")\n\nplt.suptitle(\"Lowest NDVI Images\", fontsize=16)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:45.391367Z","iopub.execute_input":"2026-01-14T12:13:45.391888Z","iopub.status.idle":"2026-01-14T12:13:47.216942Z","shell.execute_reply.started":"2026-01-14T12:13:45.391866Z","shell.execute_reply":"2026-01-14T12:13:47.216121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ndvi_df = train[['image_id', 'Pre_GSHH_NDVI']].drop_duplicates()\n\nhigh_ndvi = ndvi_df.nlargest(6, \"Pre_GSHH_NDVI\")['image_id']\n\nplt.figure(figsize=(12, 12))\n\nfor i, img_id in enumerate(high_ndvi):\n    img_file = f\"{img_id}.jpg\"\n    img_path = train_path / img_file\n    \n    img = np.array(Image.open(img_path))\n    \n    plt.subplot(3, 3, i + 1)\n    plt.imshow(img)\n    plt.axis(\"off\")\n    plt.title(f\"{img_id}\\nNDVI={train[train.image_id==img_id].Pre_GSHH_NDVI.iloc[0]:.2f}\")\n\nplt.suptitle(\"Highest NDVI Images\", fontsize=16)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:47.217755Z","iopub.execute_input":"2026-01-14T12:13:47.217952Z","iopub.status.idle":"2026-01-14T12:13:49.330865Z","shell.execute_reply.started":"2026-01-14T12:13:47.217936Z","shell.execute_reply":"2026-01-14T12:13:49.329906Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Data preprocessing","metadata":{}},{"cell_type":"markdown","source":"* categorical features - onehot encoding\n* numerical features - standard scalar\n* images - resize + normalize (ImageNet)\n* all of the steps will be done in Dataset","metadata":{}},{"cell_type":"code","source":"from sklearn.preprocessing import OneHotEncoder, StandardScaler\nimport torchvision.transforms as T\nfrom torch.utils.data import DataLoader, Dataset\nfrom sklearn.model_selection import train_test_split","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:49.331881Z","iopub.execute_input":"2026-01-14T12:13:49.332130Z","iopub.status.idle":"2026-01-14T12:13:49.336323Z","shell.execute_reply.started":"2026-01-14T12:13:49.332110Z","shell.execute_reply":"2026-01-14T12:13:49.335482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"targets_wide = train.pivot_table(\n    index=\"image_id\",\n    columns=\"target_name\",\n    values=\"target\",\n    aggfunc=\"mean\"\n).reset_index()\n\ntrain = train.merge(targets_wide, on=\"image_id\", how=\"inner\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:49.337117Z","iopub.execute_input":"2026-01-14T12:13:49.337343Z","iopub.status.idle":"2026-01-14T12:13:49.358406Z","shell.execute_reply.started":"2026-01-14T12:13:49.337327Z","shell.execute_reply":"2026-01-14T12:13:49.357902Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train[\"Sampling_Date\"] = pd.to_datetime(train[\"Sampling_Date\"])\ntrain[\"sample_month\"] = train[\"Sampling_Date\"].dt.month\ntrain[\"sample_day\"] = train[\"Sampling_Date\"].dt.dayofyear\ntrain = train.drop([\"Sampling_Date\"], axis=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:49.359183Z","iopub.execute_input":"2026-01-14T12:13:49.359424Z","iopub.status.idle":"2026-01-14T12:13:49.373518Z","shell.execute_reply.started":"2026-01-14T12:13:49.359407Z","shell.execute_reply":"2026-01-14T12:13:49.372774Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train.info()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:49.374224Z","iopub.execute_input":"2026-01-14T12:13:49.374456Z","iopub.status.idle":"2026-01-14T12:13:49.390527Z","shell.execute_reply.started":"2026-01-14T12:13:49.374419Z","shell.execute_reply":"2026-01-14T12:13:49.389952Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"NUMERIC_COLS = [\"Pre_GSHH_NDVI\", \"Height_Ave_cm\", \"sample_month\", \"sample_day\"]\nCAT_COLS = [\"State\", \"Species\"]\nTARGETS = [\"Dry_Clover_g\", \"Dry_Dead_g\", \"Dry_Green_g\", \"GDM_g\", \"Dry_Total_g\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:49.391192Z","iopub.execute_input":"2026-01-14T12:13:49.391522Z","iopub.status.idle":"2026-01-14T12:13:49.401096Z","shell.execute_reply.started":"2026-01-14T12:13:49.391500Z","shell.execute_reply":"2026-01-14T12:13:49.400417Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_wide = (\n    train\n    .pivot_table(\n        index=[\"image_id\", \"image_path\"],\n        columns=\"target_name\",\n        values=\"target\"\n    )\n    .reset_index()\n)\n\ntrain_wide = train_wide[[\"image_id\", \"image_path\"] + TARGETS]\n\ntrain = train_wide","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:49.401891Z","iopub.execute_input":"2026-01-14T12:13:49.402107Z","iopub.status.idle":"2026-01-14T12:13:49.423452Z","shell.execute_reply.started":"2026-01-14T12:13:49.402092Z","shell.execute_reply":"2026-01-14T12:13:49.422879Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"img_transforms = T.Compose([\n    T.Resize((224, 224)),\n    T.ToTensor(),\n    T.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std=[0.229, 0.224, 0.225]\n    )\n])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Model parameters","metadata":{}},{"cell_type":"code","source":"class Parameters():\n  lr = 0.001\n  batch_size_train = 8\n  batch_size_val = 16\n  epochs = 15\n  num_targets = 5\n\nparams = Parameters()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:13:49.433877Z","iopub.execute_input":"2026-01-14T12:13:49.434105Z","iopub.status.idle":"2026-01-14T12:13:49.444227Z","shell.execute_reply.started":"2026-01-14T12:13:49.434092Z","shell.execute_reply":"2026-01-14T12:13:49.443480Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Dataset and dataloader","metadata":{}},{"cell_type":"code","source":"class BiomassDataset(Dataset):\n    def __init__(self, df, img_dir, transforms):\n        self.df = df.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n\n        img = Image.open(self.img_dir / row[\"image_path\"]).convert(\"RGB\")\n        img = self.transforms(img)\n\n        targets = torch.tensor(row[TARGETS].values.astype(np.float32))\n\n        return img, targets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:16:25.405284Z","iopub.execute_input":"2026-01-14T12:16:25.405803Z","iopub.status.idle":"2026-01-14T12:16:25.410839Z","shell.execute_reply.started":"2026-01-14T12:16:25.405779Z","shell.execute_reply":"2026-01-14T12:16:25.409953Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train, df_val  = train_test_split(train, test_size=0.2, random_state=42, shuffle=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:16:26.174132Z","iopub.execute_input":"2026-01-14T12:16:26.174395Z","iopub.status.idle":"2026-01-14T12:16:26.179569Z","shell.execute_reply.started":"2026-01-14T12:16:26.174376Z","shell.execute_reply":"2026-01-14T12:16:26.178776Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_dataset = BiomassDataset(df_train, INPUT_PATH, img_transforms)\nval_dataset   = BiomassDataset(df_val, INPUT_PATH, img_transforms)\n\ntrain_loader = DataLoader(train_dataset, batch_size=32, shuffle=True)\nval_loader   = DataLoader(val_dataset, batch_size=32)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:16:28.416133Z","iopub.execute_input":"2026-01-14T12:16:28.416396Z","iopub.status.idle":"2026-01-14T12:16:28.422293Z","shell.execute_reply.started":"2026-01-14T12:16:28.416375Z","shell.execute_reply":"2026-01-14T12:16:28.421608Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Model building","metadata":{}},{"cell_type":"markdown","source":"* 5 targets per image -> multi output regression task\n* tabular + image data","metadata":{}},{"cell_type":"code","source":"import torch\nimport torch.nn as nn\nimport torchvision.models as models\nfrom tqdm import tqdm","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:16:30.713149Z","iopub.execute_input":"2026-01-14T12:16:30.713426Z","iopub.status.idle":"2026-01-14T12:16:30.717275Z","shell.execute_reply.started":"2026-01-14T12:16:30.713405Z","shell.execute_reply":"2026-01-14T12:16:30.716597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class BiomassModel(nn.Module):\n    def __init__(self, num_targets=5):\n        super().__init__()\n\n        self.cnn = models.efficientnet_b0(weights=None)\n        img_dim = self.cnn.classifier[1].in_features\n        self.cnn.classifier = nn.Identity()\n\n        self.head = nn.Sequential(\n            nn.Linear(img_dim, 512),\n            nn.BatchNorm1d(512),\n            nn.ReLU(),\n            nn.Dropout(0.4),\n            nn.Linear(512, 256),\n            nn.ReLU(),\n            nn.Dropout(0.3),\n            nn.Linear(256, num_targets)\n        )\n\n    def forward(self, x):\n        x = self.cnn(x)\n        return self.head(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:16:30.881853Z","iopub.execute_input":"2026-01-14T12:16:30.882264Z","iopub.status.idle":"2026-01-14T12:16:30.887429Z","shell.execute_reply.started":"2026-01-14T12:16:30.882241Z","shell.execute_reply":"2026-01-14T12:16:30.886813Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TARGET_WEIGHTS = {\n    0: 0.1,\n    1: 0.1,\n    2: 0.1,\n    3: 0.2,\n    4: 0.5,\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:16:31.034191Z","iopub.execute_input":"2026-01-14T12:16:31.034777Z","iopub.status.idle":"2026-01-14T12:16:31.037999Z","shell.execute_reply.started":"2026-01-14T12:16:31.034757Z","shell.execute_reply":"2026-01-14T12:16:31.037261Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def weighted_r2_score(y_true, y_pred, weights):\n    weights = np.asarray(weights, dtype=np.float32)\n    weights = weights / weights.sum()\n    \n    y_true_mean = np.average(y_true, axis=0)\n    \n    ss_res = np.sum(weights * np.sum((y_true - y_pred) ** 2, axis=0))\n    ss_tot = np.sum(weights * np.sum((y_true - y_true_mean) ** 2, axis=0))\n    \n    return 1 - ss_res / ss_tot","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:19:17.676734Z","iopub.execute_input":"2026-01-14T12:19:17.677414Z","iopub.status.idle":"2026-01-14T12:19:17.681583Z","shell.execute_reply.started":"2026-01-14T12:19:17.677388Z","shell.execute_reply":"2026-01-14T12:19:17.680868Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_losses = []\nval_losses = []\nval_r2_scores = []\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = BiomassModel().to(device)\n\ncriterion = nn.MSELoss()\noptimizer = torch.optim.Adam(model.parameters(), lr=params.lr)\nscaler_cuda = torch.cuda.amp.GradScaler()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:19:18.275128Z","iopub.execute_input":"2026-01-14T12:19:18.275711Z","iopub.status.idle":"2026-01-14T12:19:18.389528Z","shell.execute_reply.started":"2026-01-14T12:19:18.275687Z","shell.execute_reply":"2026-01-14T12:19:18.388731Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_val_r2 = -np.inf\n\nfor epoch in range(params.epochs):\n\n    # ======================\n    # TRAIN\n    # ======================\n    model.train()\n    total_loss = 0.0\n\n    with tqdm(total=len(train_loader), position=0, leave=True) as pbar:\n        for batch_idx, (image, targets) in enumerate(train_loader):\n\n            image = image.to(device)\n            targets = targets.to(device)\n\n            optimizer.zero_grad()\n\n            with torch.cuda.amp.autocast():\n                preds = model(image)\n                loss = criterion(preds, targets)\n\n            scaler_cuda.scale(loss).backward()\n            scaler_cuda.step(optimizer)\n            scaler_cuda.update()\n\n            total_loss += loss.item()\n\n            pbar.set_postfix_str(\n                f\"Epoch {epoch+1}, Avg train loss: {total_loss / (batch_idx + 1):.4f}\"\n            )\n            pbar.update(1)\n\n    avg_train_loss = total_loss / len(train_loader)\n    train_losses.append(avg_train_loss)\n\n    print(\"=\" * 60)\n    print(f\"Epoch {epoch+1}/{params.epochs} completed. Starting validation...\")\n    print(\"=\" * 60)\n\n    # ======================\n    # VALIDATION\n    # ======================\n    model.eval()\n\n    y_true_all = []\n    y_pred_all = []\n    weights_all = []\n    total_val_loss = 0.0\n    \n    with torch.no_grad():\n        for image, targets in val_loader:\n    \n            image = image.to(device)\n            targets = targets.to(device)\n    \n            preds = model(image)\n            loss = criterion(preds, targets)\n\n            total_val_loss += loss.item()\n            \n            y_true_all.append(targets.cpu().numpy())\n            y_pred_all.append(preds.cpu().numpy())\n    \n\n    avg_val_loss = total_val_loss / len(val_loader)\n    val_losses.append(avg_val_loss)\n    \n    y_true_all = np.concatenate(y_true_all)\n    y_pred_all = np.concatenate(y_pred_all)\n    \n    weights_array = np.array(list(TARGET_WEIGHTS.values()), dtype=np.float32)\n    val_r2 = weighted_r2_score(y_true_all, y_pred_all, weights_array) \n    \n\n    print(\n    f\"Epoch {epoch+1}/{params.epochs} | \"\n    f\"Train Loss: {avg_train_loss:.4f} | \"\n    f\"Val Weighted R²: {val_r2:.4f}\",\n    flush=True)\n    \n    if val_r2 > best_val_r2:\n        best_val_r2 = val_r2\n        torch.save(model.state_dict(), \"best_model.pt\")\n        print(f\"🔥 New best model saved (R² = {val_r2:.4f})\")\n    \n    val_r2_scores.append(val_r2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:19:18.457026Z","iopub.execute_input":"2026-01-14T12:19:18.457682Z","iopub.status.idle":"2026-01-14T12:24:05.641657Z","shell.execute_reply.started":"2026-01-14T12:19:18.457659Z","shell.execute_reply":"2026-01-14T12:24:05.641008Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"epochs_range = list(range(1, params.epochs + 1))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:24:35.473192Z","iopub.execute_input":"2026-01-14T12:24:35.473479Z","iopub.status.idle":"2026-01-14T12:24:35.477126Z","shell.execute_reply.started":"2026-01-14T12:24:35.473458Z","shell.execute_reply":"2026-01-14T12:24:35.476560Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8, 4))\nsns.lineplot(x=epochs_range, y=train_losses, label='Train Loss', palette=\"coolwarm\")\nsns.lineplot(x=epochs_range, y=val_losses, label='Validation Loss', palette=\"coolwarm\", linestyle=\"dashed\")\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.title('Training & Validation Loss')\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:24:39.311306Z","iopub.execute_input":"2026-01-14T12:24:39.311831Z","iopub.status.idle":"2026-01-14T12:24:39.491291Z","shell.execute_reply.started":"2026-01-14T12:24:39.311807Z","shell.execute_reply":"2026-01-14T12:24:39.490752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.figure(figsize=(8, 4))\nplt.plot(epochs_range, val_r2_scores, marker=\"o\")\nplt.xlabel(\"Epochs\")\nplt.ylabel(\"Weighted R²\")\nplt.title(\"Validation Weighted R² over Epochs\")\nplt.grid(True)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:24:42.537379Z","iopub.execute_input":"2026-01-14T12:24:42.537696Z","iopub.status.idle":"2026-01-14T12:24:42.681749Z","shell.execute_reply.started":"2026-01-14T12:24:42.537674Z","shell.execute_reply":"2026-01-14T12:24:42.681179Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Test set","metadata":{}},{"cell_type":"code","source":"class BiomassTestDataset(Dataset):\n    def __init__(self, df, img_dir, transforms):\n        self.df = df.reset_index(drop=True)\n        self.img_dir = img_dir\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img = Image.open(self.img_dir / row[\"image_path\"]).convert(\"RGB\")\n        img = self.transforms(img)\n        image_id = row[\"image_path\"]\n        return img, image_id","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:27:12.937110Z","iopub.execute_input":"2026-01-14T12:27:12.937416Z","iopub.status.idle":"2026-01-14T12:27:12.942589Z","shell.execute_reply.started":"2026-01-14T12:27:12.937395Z","shell.execute_reply":"2026-01-14T12:27:12.941883Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_dataset   = BiomassTestDataset(test, INPUT_PATH, img_transforms)\ntest_loader   = DataLoader(test_dataset, batch_size=32, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:27:13.881399Z","iopub.execute_input":"2026-01-14T12:27:13.882141Z","iopub.status.idle":"2026-01-14T12:27:13.885885Z","shell.execute_reply.started":"2026-01-14T12:27:13.882118Z","shell.execute_reply":"2026-01-14T12:27:13.885215Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load(\"best_model.pt\"))\nmodel.eval()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:27:14.041112Z","iopub.execute_input":"2026-01-14T12:27:14.041721Z","iopub.status.idle":"2026-01-14T12:27:14.132675Z","shell.execute_reply.started":"2026-01-14T12:27:14.041699Z","shell.execute_reply":"2026-01-14T12:27:14.131965Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image_preds = {}\n\nwith torch.no_grad():\n    for imgs, image_ids in test_loader:\n        imgs = imgs.to(device)\n        preds = model(imgs).cpu().numpy()\n\n        for i, img_id in enumerate(image_ids):\n            image_preds[img_id] = preds[i]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:27:20.397857Z","iopub.execute_input":"2026-01-14T12:27:20.398715Z","iopub.status.idle":"2026-01-14T12:27:20.712115Z","shell.execute_reply.started":"2026-01-14T12:27:20.398683Z","shell.execute_reply":"2026-01-14T12:27:20.711482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(test.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:29:49.855960Z","iopub.execute_input":"2026-01-14T12:29:49.856478Z","iopub.status.idle":"2026-01-14T12:29:49.861995Z","shell.execute_reply.started":"2026-01-14T12:29:49.856429Z","shell.execute_reply":"2026-01-14T12:29:49.861341Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"target_map = {\n    \"Dry_Clover_g\": 0,\n    \"Dry_Dead_g\": 1,\n    \"Dry_Green_g\": 2,\n    \"GDM_g\": 3,\n    \"Dry_Total_g\": 4\n}\n\nsubmission = pd.DataFrame({\n    \"sample_id\": test[\"sample_id\"],\n    \"target\": [\n        image_preds[row[\"image_path\"]][target_map[row[\"target_name\"]]]\n        for _, row in test.iterrows()\n    ]\n})","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:31:42.980840Z","iopub.execute_input":"2026-01-14T12:31:42.981124Z","iopub.status.idle":"2026-01-14T12:31:42.986161Z","shell.execute_reply.started":"2026-01-14T12:31:42.981104Z","shell.execute_reply":"2026-01-14T12:31:42.985507Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission.to_csv(\"submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:31:46.819261Z","iopub.execute_input":"2026-01-14T12:31:46.819663Z","iopub.status.idle":"2026-01-14T12:31:46.828288Z","shell.execute_reply.started":"2026-01-14T12:31:46.819631Z","shell.execute_reply":"2026-01-14T12:31:46.827708Z"}},"outputs":[],"execution_count":null}]}