{"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":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31239,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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":"2025-12-31T18:57:11.111669Z","iopub.execute_input":"2025-12-31T18:57:11.112063Z","iopub.status.idle":"2025-12-31T18:57:13.693261Z","shell.execute_reply.started":"2025-12-31T18:57:11.112028Z","shell.execute_reply":"2025-12-31T18:57:13.691702Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom sklearn.preprocessing import LabelEncoder\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T18:58:25.953132Z","iopub.execute_input":"2025-12-31T18:58:25.953439Z","iopub.status.idle":"2025-12-31T18:58:31.339083Z","shell.execute_reply.started":"2025-12-31T18:58:25.953417Z","shell.execute_reply":"2025-12-31T18:58:31.337708Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BASE_PATH = \"/kaggle/input/csiro-biomass\"\nTRAIN_IMG_PATH = os.path.join(BASE_PATH, \"train\")\nTEST_IMG_PATH = os.path.join(BASE_PATH, \"test\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T18:58:51.281108Z","iopub.execute_input":"2025-12-31T18:58:51.281456Z","iopub.status.idle":"2025-12-31T18:58:51.286953Z","shell.execute_reply.started":"2025-12-31T18:58:51.281433Z","shell.execute_reply":"2025-12-31T18:58:51.285928Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv(os.path.join(BASE_PATH, \"train.csv\"))\ntest_df = pd.read_csv(os.path.join(BASE_PATH, \"test.csv\"))\n\nprint(\"Train shape:\", train_df.shape)\nprint(\"Test shape:\", test_df.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T18:59:04.165100Z","iopub.execute_input":"2025-12-31T18:59:04.165428Z","iopub.status.idle":"2025-12-31T18:59:04.180414Z","shell.execute_reply.started":"2025-12-31T18:59:04.165403Z","shell.execute_reply":"2025-12-31T18:59:04.179302Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Pivot train data: long → wide\ntrain_pivot = train_df.pivot_table(\n    index=\"image_path\",\n    columns=\"target_name\",\n    values=\"target\"\n).reset_index()\n\nprint(\"Train pivot shape:\", train_pivot.shape)\ntrain_pivot.head()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T18:59:56.331327Z","iopub.execute_input":"2025-12-31T18:59:56.331690Z","iopub.status.idle":"2025-12-31T18:59:56.389069Z","shell.execute_reply.started":"2025-12-31T18:59:56.331664Z","shell.execute_reply":"2025-12-31T18:59:56.388176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"meta_cols = [\n    \"image_path\",\n    \"Pre_GSHH_NDVI\",\n    \"Height_Ave_cm\",\n    \"State\",\n    \"Species\"\n]\n\ntrain_meta = train_df[meta_cols].drop_duplicates(\"image_path\")\n\ntrain_df = train_meta.merge(train_pivot, on=\"image_path\", how=\"inner\")\n\nprint(\"Final train_df shape:\", train_df.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:00:08.580704Z","iopub.execute_input":"2025-12-31T19:00:08.581050Z","iopub.status.idle":"2025-12-31T19:00:08.594753Z","shell.execute_reply.started":"2025-12-31T19:00:08.581022Z","shell.execute_reply":"2025-12-31T19:00:08.593850Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TARGETS = [\n    \"Dry_Green_g\",\n    \"Dry_Dead_g\",\n    \"Dry_Clover_g\",\n    \"GDM_g\",\n    \"Dry_Total_g\"\n]\n\ny_mean = train_df[TARGETS].mean()\ny_std = train_df[TARGETS].std() + 1e-6\n\ntrain_df[TARGETS] = (train_df[TARGETS] - y_mean) / y_std\n\nprint(\"Target normalization successful\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:00:21.500152Z","iopub.execute_input":"2025-12-31T19:00:21.500446Z","iopub.status.idle":"2025-12-31T19:00:21.511946Z","shell.execute_reply.started":"2025-12-31T19:00:21.500425Z","shell.execute_reply":"2025-12-31T19:00:21.511033Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.preprocessing import LabelEncoder\n\nstate_enc = LabelEncoder()\nspecies_enc = LabelEncoder()\n\nif \"State\" in train_df.columns:\n    state_enc.fit(train_df[\"State\"].astype(str))\nelse:\n    state_enc.fit([\"unknown\"])\n\nif \"Species\" in train_df.columns:\n    species_enc.fit(train_df[\"Species\"].astype(str))\nelse:\n    species_enc.fit([\"unknown\"])\n\nprint(\"Encoders ready\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:01:02.896522Z","iopub.execute_input":"2025-12-31T19:01:02.897463Z","iopub.status.idle":"2025-12-31T19:01:02.904797Z","shell.execute_reply.started":"2025-12-31T19:01:02.897429Z","shell.execute_reply":"2025-12-31T19:01:02.903615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TrainDataset(Dataset):\n    def __init__(self, df):\n        self.df = df.reset_index(drop=True)\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_path = os.path.join(BASE_PATH, row[\"image_path\"])\n        img = cv2.imread(img_path)\n\n        if img is None:\n            img = np.zeros((160, 160, 3), dtype=np.uint8)\n        else:\n            img = cv2.resize(img, (160, 160))\n\n        img = img[:, :, ::-1] / 255.0\n        img = torch.tensor(img, dtype=torch.float32).permute(2, 0, 1)\n        img = (img - 0.5) / 0.5\n\n        ndvi = float(row[\"Pre_GSHH_NDVI\"]) if pd.notna(row[\"Pre_GSHH_NDVI\"]) else 0.0\n        height = float(row[\"Height_Ave_cm\"]) if pd.notna(row[\"Height_Ave_cm\"]) else 0.0\n\n        state = state_enc.transform([str(row[\"State\"])])[0] if \"State\" in row else 0\n        species = species_enc.transform([str(row[\"Species\"])])[0] if \"Species\" in row else 0\n\n        tab = torch.tensor([ndvi, height, state, species], dtype=torch.float32)\n\n        target = torch.tensor(\n            row[TARGETS].values.astype(np.float32),\n            dtype=torch.float32\n        )\n\n        return img, tab, target\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:01:18.899402Z","iopub.execute_input":"2025-12-31T19:01:18.899788Z","iopub.status.idle":"2025-12-31T19:01:18.909226Z","shell.execute_reply.started":"2025-12-31T19:01:18.899764Z","shell.execute_reply":"2025-12-31T19:01:18.907882Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def safe_value(row, col, default=0.0):\n    return float(row[col]) if col in row and pd.notna(row[col]) else default\n\n\ndef safe_encode(series, encoder):\n    known = set(encoder.classes_)\n    return series.apply(lambda x: encoder.transform([x])[0] if str(x) in known else 0)\n\n\ntest_meta = test_df.drop_duplicates(\"image_path\").copy()\n\ntest_meta[\"Pre_GSHH_NDVI\"] = test_meta.apply(lambda r: safe_value(r, \"Pre_GSHH_NDVI\"), axis=1)\ntest_meta[\"Height_Ave_cm\"] = test_meta.apply(lambda r: safe_value(r, \"Height_Ave_cm\"), axis=1)\n\ntest_meta[\"State_enc\"] = safe_encode(test_meta[\"State\"], state_enc) if \"State\" in test_meta.columns else 0\ntest_meta[\"Species_enc\"] = safe_encode(test_meta[\"Species\"], species_enc) if \"Species\" in test_meta.columns else 0\n\nprint(\"Test meta ready:\", test_meta.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:01:33.117596Z","iopub.execute_input":"2025-12-31T19:01:33.117888Z","iopub.status.idle":"2025-12-31T19:01:33.129114Z","shell.execute_reply.started":"2025-12-31T19:01:33.117868Z","shell.execute_reply":"2025-12-31T19:01:33.128126Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class TestDataset(Dataset):\n    def __init__(self, df):\n        self.df = df.reset_index(drop=True)\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_path = os.path.join(BASE_PATH, row[\"image_path\"])\n        img = cv2.imread(img_path)\n\n        if img is None:\n            img = np.zeros((160, 160, 3), dtype=np.uint8)\n        else:\n            img = cv2.resize(img, (160, 160))\n\n        img = img[:, :, ::-1] / 255.0\n        img = torch.tensor(img, dtype=torch.float32).permute(2, 0, 1)\n        img = (img - 0.5) / 0.5\n\n        tab = torch.tensor(\n            [\n                row[\"Pre_GSHH_NDVI\"],\n                row[\"Height_Ave_cm\"],\n                row[\"State_enc\"],\n                row[\"Species_enc\"],\n            ],\n            dtype=torch.float32\n        )\n\n        return img, tab\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:01:49.457171Z","iopub.execute_input":"2025-12-31T19:01:49.457609Z","iopub.status.idle":"2025-12-31T19:01:49.464956Z","shell.execute_reply.started":"2025-12-31T19:01:49.457584Z","shell.execute_reply":"2025-12-31T19:01:49.463828Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torchvision.models as models\n\nclass BiomassModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n\n        self.cnn = models.efficientnet_b0(weights=None)\n        self.cnn.classifier = nn.Identity()\n\n        self.tabular = nn.Sequential(\n            nn.Linear(4, 64),\n            nn.ReLU(),\n            nn.Linear(64, 32)\n        )\n\n        self.head = nn.Sequential(\n            nn.Linear(1280 + 32, 256),\n            nn.ReLU(),\n            nn.Linear(256, 5)\n        )\n\n    def forward(self, img, tab):\n        img_feat = self.cnn(img)\n        tab_feat = self.tabular(tab)\n        x = torch.cat([img_feat, tab_feat], dim=1)\n        return self.head(x)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:02:00.262861Z","iopub.execute_input":"2025-12-31T19:02:00.263186Z","iopub.status.idle":"2025-12-31T19:02:04.936462Z","shell.execute_reply.started":"2025-12-31T19:02:00.263162Z","shell.execute_reply":"2025-12-31T19:02:04.935549Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\nmodel = BiomassModel().to(device)\n\n# Freeze CNN\nfor p in model.cnn.parameters():\n    p.requires_grad = False\n\ncriterion = nn.MSELoss()\noptimizer = torch.optim.Adam(\n    filter(lambda p: p.requires_grad, model.parameters()),\n    lr=1e-3\n)\n\ntrain_loader = DataLoader(\n    TrainDataset(train_df),\n    batch_size=16,\n    shuffle=True,\n    num_workers=0\n)\n\nEPOCHS = 6\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:02:22.747986Z","iopub.execute_input":"2025-12-31T19:02:22.748705Z","iopub.status.idle":"2025-12-31T19:02:23.084727Z","shell.execute_reply.started":"2025-12-31T19:02:22.748676Z","shell.execute_reply":"2025-12-31T19:02:23.083562Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for epoch in range(EPOCHS):\n    model.train()\n    loss_sum = 0.0\n\n    for imgs, tabs, targets in tqdm(train_loader):\n        imgs, tabs, targets = imgs.to(device), tabs.to(device), targets.to(device)\n\n        optimizer.zero_grad()\n        outputs = model(imgs, tabs)\n        loss = criterion(outputs, targets)\n        loss.backward()\n        optimizer.step()\n\n        loss_sum += loss.item()\n\n    print(f\"Epoch {epoch+1}/{EPOCHS} | Loss: {loss_sum/len(train_loader):.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:02:34.666690Z","iopub.execute_input":"2025-12-31T19:02:34.667053Z","iopub.status.idle":"2025-12-31T19:05:28.207505Z","shell.execute_reply.started":"2025-12-31T19:02:34.667028Z","shell.execute_reply":"2025-12-31T19:05:28.206577Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_loader = DataLoader(\n    TestDataset(test_meta),\n    batch_size=16,\n    shuffle=False,\n    num_workers=0\n)\n\nmodel.eval()\npredictions = []\n\nwith torch.no_grad():\n    for imgs, tabs in test_loader:\n        imgs, tabs = imgs.to(device), tabs.to(device)\n        preds = model(imgs, tabs)\n        predictions.append(preds.cpu().numpy())\n\npredictions = np.vstack(predictions)\n\n# Inverse normalization\npredictions = predictions * y_std.values + y_mean.values\n\nprint(\"Predictions shape:\", predictions.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:05:46.182449Z","iopub.execute_input":"2025-12-31T19:05:46.183435Z","iopub.status.idle":"2025-12-31T19:05:46.342715Z","shell.execute_reply.started":"2025-12-31T19:05:46.183407Z","shell.execute_reply":"2025-12-31T19:05:46.341849Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"rows = []\n\nfor i in range(len(test_meta)):\n    img_id = test_meta.iloc[i][\"image_path\"].split(\"/\")[-1].replace(\".jpg\", \"\")\n    for j, t in enumerate(TARGETS):\n        rows.append({\n            \"sample_id\": f\"{img_id}__{t}\",\n            \"target\": float(predictions[i, j])\n        })\n\nsubmission = pd.DataFrame(rows)\nsubmission.to_csv(\"/kaggle/working/submission.csv\", index=False)\n\nprint(\"submission.csv created:\", submission.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-31T19:06:02.932043Z","iopub.execute_input":"2025-12-31T19:06:02.932429Z","iopub.status.idle":"2025-12-31T19:06:02.947027Z","shell.execute_reply.started":"2025-12-31T19:06:02.932404Z","shell.execute_reply":"2025-12-31T19:06:02.946044Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}