{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import mean_squared_error, r2_score\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nfrom PIL import Image\nimport matplotlib.pyplot as plt","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T16:38:47.430567Z","iopub.execute_input":"2025-10-29T16:38:47.431061Z","iopub.status.idle":"2025-10-29T16:38:47.435227Z","shell.execute_reply.started":"2025-10-29T16:38:47.431042Z","shell.execute_reply":"2025-10-29T16:38:47.434573Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"DATA_DIR = \"/kaggle/input/csiro-biomass\" \nBASE = DATA_DIR\nTRAIN_CSV = os.path.join(BASE, \"train.csv\")\nTEST_CSV = os.path.join(BASE, \"test.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T16:38:47.436446Z","iopub.execute_input":"2025-10-29T16:38:47.437033Z","iopub.status.idle":"2025-10-29T16:38:47.450432Z","shell.execute_reply.started":"2025-10-29T16:38:47.437015Z","shell.execute_reply":"2025-10-29T16:38:47.449857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"target_names = [\"Dry_Green_g\",\"Dry_Dead_g\",\"Dry_Clover_g\",\"GDM_g\",\"Dry_Total_g\"]\ndf = pd.read_csv(TRAIN_CSV)\npiv = df.pivot(index=\"sample_id\", columns=\"target_name\", values=\"target\").reset_index()\nimg = df[[\"sample_id\",\"image_path\"]].drop_duplicates(\"sample_id\")\ndf = piv.merge(img, on=\"sample_id\")\ndf[\"image_path\"] = df[\"image_path\"].apply(lambda p: os.path.join(BASE, p))\ndf[target_names] = df[target_names].fillna(0).clip(lower=0)\ny = df[target_names].values.astype(np.float32)\npaths = df[\"image_path\"].values\ntr_idx, val_idx = train_test_split(range(len(df)), test_size=0.2, random_state=42)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T16:38:47.451112Z","iopub.execute_input":"2025-10-29T16:38:47.451373Z","iopub.status.idle":"2025-10-29T16:38:47.483107Z","shell.execute_reply.started":"2025-10-29T16:38:47.451353Z","shell.execute_reply":"2025-10-29T16:38:47.482654Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ImgDataset(Dataset):\n    def __init__(self, paths, targets=None, tf=None):\n        self.paths, self.targets, self.tf = paths, targets, tf\n    def __len__(self): return len(self.paths)\n    def __getitem__(self, i):\n        img = Image.open(self.paths[i]).convert(\"RGB\")\n        if self.tf: img = self.tf(img)\n        img = torch.nan_to_num(img)\n        if self.targets is not None:\n            tgt = torch.tensor(self.targets[i], dtype=torch.float32)\n            return img, tgt\n        return img\ntf_train = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.RandomHorizontalFlip(),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])\n])\ntf_val = transforms.Compose([\n    transforms.Resize((224,224)),\n    transforms.ToTensor(),\n    transforms.Normalize(mean=[0.485,0.456,0.406], std=[0.229,0.224,0.225])\n])\ntrain_ds = ImgDataset(paths[tr_idx], y[tr_idx], tf_train)\nval_ds = ImgDataset(paths[val_idx], y[val_idx], tf_val)\nBATCH = 32\ntrain_dl = DataLoader(train_ds, batch_size=BATCH, shuffle=True, num_workers=0)\nval_dl = DataLoader(val_ds, batch_size=BATCH, shuffle=False, num_workers=0)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T16:38:47.484427Z","iopub.execute_input":"2025-10-29T16:38:47.484649Z","iopub.status.idle":"2025-10-29T16:38:47.494657Z","shell.execute_reply.started":"2025-10-29T16:38:47.484634Z","shell.execute_reply":"2025-10-29T16:38:47.493930Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class SimpleCNN(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.conv = nn.Sequential(\n            nn.Conv2d(3,32,3,padding=1), nn.ReLU(), nn.MaxPool2d(2),\n            nn.Conv2d(32,64,3,padding=1), nn.ReLU(), nn.MaxPool2d(2),\n            nn.Conv2d(64,128,3,padding=1), nn.ReLU(), nn.MaxPool2d(2),\n            nn.AdaptiveAvgPool2d((4,4))\n        )\n        self.fc_img = nn.Linear(128*4*4, 128)\n        self.head = nn.Sequential(nn.Linear(128, 64), nn.ReLU(), nn.Linear(64,5))\n    def forward(self, img):\n        f = self.conv(img).flatten(1)\n        f = torch.relu(self.fc_img(f))\n        out = self.head(f)\n        return torch.clamp(out, min=0.0) # <<< NON-NEGATIVE\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nmodel = SimpleCNN().to(device)\nclass RMSELoss(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.mse = nn.MSELoss()\n    def forward(self, pred, true):\n        return torch.sqrt(self.mse(pred, true) + 1e-8)\ncriterion = RMSELoss()\nopt = optim.Adam(model.parameters(), lr=1e-3)\nsched = optim.lr_scheduler.ReduceLROnPlateau(opt, patience=2, factor=0.5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T16:38:47.495302Z","iopub.execute_input":"2025-10-29T16:38:47.495465Z","iopub.status.idle":"2025-10-29T16:38:47.518041Z","shell.execute_reply.started":"2025-10-29T16:38:47.495453Z","shell.execute_reply":"2025-10-29T16:38:47.517397Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_all(dl, train=False):\n    if train: model.train()\n    else: model.eval()\n    preds, tgts = [], []\n    with torch.no_grad() if not train else torch.enable_grad():\n        for batch in dl:\n            img = batch[0].to(device)\n            if len(batch) == 2:\n                tgt = batch[1].to(device)\n            else:\n                tgt = None\n            if train and tgt is not None:\n                opt.zero_grad()\n            pred = model(img)\n            if train and tgt is not None:\n                loss = criterion(pred, tgt)\n                loss.backward()\n                opt.step()\n            preds.append(pred.detach().cpu().numpy())\n            if tgt is not None:\n                tgts.append(tgt.detach().cpu().numpy())\n    return np.vstack(preds), np.vstack(tgts)\ndef calc_metrics(pred, true):\n    rmse_per = np.sqrt(mean_squared_error(true, pred, multioutput='raw_values'))\n    rmse_tot = np.sqrt(mean_squared_error(true, pred))\n    r2_per = r2_score(true, pred, multioutput='raw_values')\n    r2_tot = r2_score(true, pred)\n    return rmse_per, rmse_tot, r2_per, r2_tot","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T16:38:47.518826Z","iopub.execute_input":"2025-10-29T16:38:47.519132Z","iopub.status.idle":"2025-10-29T16:38:47.527979Z","shell.execute_reply.started":"2025-10-29T16:38:47.519109Z","shell.execute_reply":"2025-10-29T16:38:47.527388Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 50\ntrain_rmse_hist, val_rmse_hist = [], []\nbest_val_rmse = float('inf')\nfor ep in range(1, EPOCHS+1):\n    tr_pred, tr_true = get_all(train_dl, train=True)\n    tr_rmse = np.sqrt(mean_squared_error(tr_true, tr_pred))\n    train_rmse_hist.append(tr_rmse)\n    val_pred, val_true = get_all(val_dl, train=False)\n    val_rmse = np.sqrt(mean_squared_error(val_true, val_pred))\n    val_rmse_hist.append(val_rmse)\n    sched.step(val_rmse)\n    print(f\"Ep {ep:02d} | Train RMSE {tr_rmse:6.4f} | Val RMSE {val_rmse:6.4f}\")\n    if val_rmse < best_val_rmse:\n        best_val_rmse = val_rmse\n        torch.save(model.state_dict(), \"best_simple.pth\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T16:38:47.603172Z","iopub.execute_input":"2025-10-29T16:38:47.603333Z","iopub.status.idle":"2025-10-29T17:58:41.628954Z","shell.execute_reply.started":"2025-10-29T16:38:47.603322Z","shell.execute_reply":"2025-10-29T17:58:41.627951Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_state_dict(torch.load(\"best_simple.pth\", map_location=device))\nmodel.eval()\ntr_pred, tr_true = get_all(train_dl, train=False)\nval_pred, val_true = get_all(val_dl, train=False)\ntr_rmse_per, tr_rmse_tot, tr_r2_per, tr_r2_tot = calc_metrics(tr_pred, tr_true)\nval_rmse_per, val_rmse_tot, val_r2_per, val_r2_tot = calc_metrics(val_pred, val_true)\nmetrics = pd.DataFrame({\n    \"Target\": target_names + [\"TOTAL\"],\n    \"Train RMSE\": np.append(tr_rmse_per, tr_rmse_tot),\n    \"Val RMSE\": np.append(val_rmse_per, val_rmse_tot),\n    \"Train R²\" : np.append(tr_r2_per, tr_r2_tot),\n    \"Val R²\" : np.append(val_r2_per, val_r2_tot)\n}).round(4)\nprint(\"\\nFINAL METRICS\")\nprint(metrics.to_string(index=False))\n# ---- plots ----\nplt.figure(figsize=(14,5))\n# loss curve\nplt.subplot(1,3,1)\nplt.plot(train_rmse_hist, label=\"Train RMSE\", marker='o')\nplt.plot(val_rmse_hist, label=\"Val RMSE\", marker='s')\nplt.title(\"RMSE per Epoch\")\nplt.xlabel(\"Epoch\")\nplt.ylabel(\"RMSE\")\nplt.legend()\nplt.grid(True, alpha=0.3)\n# RMSE bar\nax = plt.subplot(1,3,2)\nmetrics.set_index(\"Target\")[[\"Train RMSE\",\"Val RMSE\"]].plot(kind='bar', ax=ax, color=['#66c2a5','#fc8d62'])\nax.set_title(\"RMSE per Target\")\nax.set_ylabel(\"RMSE\")\nax.tick_params(axis='x', rotation=45)\n# R² bar\nax2 = plt.subplot(1,3,3)\nmetrics.set_index(\"Target\")[[\"Train R²\",\"Val R²\"]].plot(kind='bar', ax=ax2, color=['#8da0cb','#e78ac3'])\nax2.set_title(\"R² per Target\")\nax2.set_ylabel(\"R²\")\nax2.tick_params(axis='x', rotation=45)\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T17:59:06.774226Z","iopub.execute_input":"2025-10-29T17:59:06.774953Z","iopub.status.idle":"2025-10-29T18:00:56.935492Z","shell.execute_reply.started":"2025-10-29T17:59:06.774927Z","shell.execute_reply":"2025-10-29T18:00:56.934590Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_df = pd.read_csv(TEST_CSV)\ntest_grp = test_df.groupby(\"image_path\").first().reset_index()\ntest_paths = [os.path.join(BASE, p) for p in test_grp[\"image_path\"]]\ntest_ds = ImgDataset(test_paths, tf=tf_val)\ntest_dl = DataLoader(test_ds, batch_size=BATCH, shuffle=False, num_workers=0)\npreds = []\nwith torch.no_grad():\n    for img in test_dl:\n        img = img.to(device)\n        p = model(img).cpu().numpy()\n        preds.append(p)\nall_preds = np.vstack(preds)\nimg2idx = {p:i for i,p in enumerate(test_grp[\"image_path\"])}\ntgt2idx = {t:i for i,t in enumerate(target_names)}\nrows = []\nfor _,r in test_df.iterrows():\n    idx = img2idx[r[\"image_path\"]]\n    col = tgt2idx[r[\"target_name\"]]\n    rows.append({\"sample_id\": r[\"sample_id\"], \"target\": float(all_preds[idx,col])})\nsub = pd.DataFrame(rows)\nsub.to_csv(\"submission.csv\", index=False)\nprint(f\"\\nsubmission.csv → {len(sub)} rows\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-29T18:01:10.141011Z","iopub.execute_input":"2025-10-29T18:01:10.141322Z","iopub.status.idle":"2025-10-29T18:01:10.235144Z","shell.execute_reply.started":"2025-10-29T18:01:10.141300Z","shell.execute_reply":"2025-10-29T18:01:10.234158Z"}},"outputs":[],"execution_count":null}]}