{"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":31192,"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":"7ec276a2-a695-4e33-9626-6a02ff9e60a3","_cell_guid":"5ecc52f3-b929-451f-9c1c-cbd22d1ceeed","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-12-11T20:41:41.705124Z","iopub.execute_input":"2025-12-11T20:41:41.705940Z","iopub.status.idle":"2025-12-11T20:41:42.505162Z","shell.execute_reply.started":"2025-12-11T20:41:41.705863Z","shell.execute_reply":"2025-12-11T20:41:42.504121Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\ntrain_df = pd.read_csv(\"/kaggle/input/csiro-biomass/train.csv\")\ntest_df  = pd.read_csv(\"/kaggle/input/csiro-biomass/test.csv\")\ntrain_df.head()","metadata":{"_uuid":"aa1f76c3-f14d-4470-bc44-a71159c364af","_cell_guid":"627ca028-e886-4718-b312-daddfe84e9a9","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-12-11T20:41:42.506636Z","iopub.execute_input":"2025-12-11T20:41:42.507209Z","iopub.status.idle":"2025-12-11T20:41:42.570316Z","shell.execute_reply.started":"2025-12-11T20:41:42.507184Z","shell.execute_reply":"2025-12-11T20:41:42.569325Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, math, random\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom PIL import Image\n\n# timm is available on Kaggle\nimport timm\n\nfrom sklearn.model_selection import KFold\nfrom sklearn.metrics import r2_score\n\nSEED = 42\ndef seed_everything(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed_all(seed)\nseed_everything(SEED)\n\nDEVICE = torch.device(\"cpu\")\nDATA_ROOT = \"/kaggle/input/csiro-biomass/\"\n\n# =========================\n# 2) Read CSVs\n# =========================\ntrain_long = pd.read_csv(os.path.join(DATA_ROOT, \"train.csv\"))\ntest_long  = pd.read_csv(os.path.join(DATA_ROOT, \"test.csv\"))\nsub_sample = pd.read_csv(os.path.join(DATA_ROOT, \"sample_submission.csv\"))\n\n# targets are in long format:\n# sample_id, image_path, (maybe metadata...), target_name, target\nTARGET_COL = \"target\"\nTNAME_COL = \"target_name\"\nIMG_COL = \"image_path\"\n\ntarget_names = sorted(train_long[TNAME_COL].unique().tolist())\nK = len(target_names)\nprint(\"Targets:\", target_names, \"K=\", K)","metadata":{"_uuid":"a88edead-fc18-4072-a4e5-43955be04779","_cell_guid":"85e25755-e66f-484f-b611-306d6ad22012","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-12-11T20:41:42.584252Z","iopub.execute_input":"2025-12-11T20:41:42.584610Z","iopub.status.idle":"2025-12-11T20:41:57.572690Z","shell.execute_reply.started":"2025-12-11T20:41:42.584576Z","shell.execute_reply":"2025-12-11T20:41:57.571921Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Per-paper weights (used by competition evaluation): 0.1/0.1/0.1/0.2/0.5\n# Order must match target_names; we'll map by name.\n# (If your target_names order differs, mapping below fixes it.)\nweight_map = {\"Dry_Green_g\":0.1, \"Dry_Dead_g\":0.1, \"Dry_Clover_g\":0.1, \"GDM_g\":0.2, \"Dry_Total_g\":0.5}\nw = np.array([weight_map.get(t, 1.0/K) for t in target_names], dtype=np.float32)\nw = w / w.sum()\nW = torch.tensor(w, device=DEVICE)","metadata":{"_uuid":"d2573247-e36c-492d-ab52-53d511522d30","_cell_guid":"7a45bdba-faa0-40ee-a2d4-141328c60236","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-12-11T20:41:57.574865Z","iopub.execute_input":"2025-12-11T20:41:57.575197Z","iopub.status.idle":"2025-12-11T20:41:57.582182Z","shell.execute_reply.started":"2025-12-11T20:41:57.575174Z","shell.execute_reply":"2025-12-11T20:41:57.581208Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_wide = (\n    train_long.pivot_table(index=IMG_COL, columns=TNAME_COL, values=TARGET_COL, aggfunc=\"first\")\n    .reindex(columns=target_names)\n).reset_index()","metadata":{"_uuid":"c7126159-68ab-421c-84f4-e6bad43acf82","_cell_guid":"d6d4edbf-5161-46dc-9b28-b703cc8d6f1e","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-12-11T20:41:57.617112Z","iopub.execute_input":"2025-12-11T20:41:57.618156Z","iopub.status.idle":"2025-12-11T20:41:57.665421Z","shell.execute_reply.started":"2025-12-11T20:41:57.618123Z","shell.execute_reply":"2025-12-11T20:41:57.664415Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train = y_wide.copy()\n\n# unique test images\ndf_test = pd.DataFrame({IMG_COL: test_long[IMG_COL].unique()})\n\nY = df_train[target_names].values.astype(np.float32)\nY_log = np.log1p(np.clip(Y, 0, None))","metadata":{"_uuid":"d32afdde-bea1-40f6-8df8-fcfda61fa302","_cell_guid":"38ada4b9-c86f-4eb4-b4b5-e549fbb9c393","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-12-11T20:41:57.701966Z","iopub.execute_input":"2025-12-11T20:41:57.702227Z","iopub.status.idle":"2025-12-11T20:41:57.724239Z","shell.execute_reply.started":"2025-12-11T20:41:57.702208Z","shell.execute_reply":"2025-12-11T20:41:57.723175Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import torchvision.transforms as T\nIMG_SIZE = 224\ntrain_tfms = T.Compose([\n    T.RandomResizedCrop(IMG_SIZE, scale=(0.75, 1.0)),\n    T.RandomHorizontalFlip(),\n    T.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.15, hue=0.02),\n    T.ToTensor(),\n    T.Normalize(mean=(0.485,0.456,0.406), std=(0.229,0.224,0.225)),\n])\ntest_tfms = T.Compose([\n    T.Resize((IMG_SIZE, IMG_SIZE)),\n    T.ToTensor(),\n    T.Normalize(mean=(0.485,0.456,0.406), std=(0.229,0.224,0.225)),\n])","metadata":{"_uuid":"cb70b704-672e-4ce8-9703-d574d072e298","_cell_guid":"07302046-1ac0-4562-a660-b5d95e9eb8bb","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-12-11T20:41:57.725344Z","iopub.execute_input":"2025-12-11T20:41:57.725586Z","iopub.status.idle":"2025-12-11T20:41:57.736415Z","shell.execute_reply.started":"2025-12-11T20:41:57.725568Z","shell.execute_reply":"2025-12-11T20:41:57.735285Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ImgOnlyDataset(Dataset):\n    def __init__(self, df, y_log=None, tfms=None):\n        self.df = df.reset_index(drop=True)\n        self.y_log = y_log\n        self.tfms = tfms\n\n    def __len__(self): return len(self.df)\n\n    def __getitem__(self, idx):\n        img_path = os.path.join(DATA_ROOT, self.df.iloc[idx][IMG_COL])\n        img = Image.open(img_path).convert(\"RGB\")\n        img = self.tfms(img)\n        if self.y_log is None:\n            return img\n        y = torch.tensor(self.y_log[idx], dtype=torch.float32)\n        return img, y\n\nclass ImgRegressor(nn.Module):\n    def __init__(self, backbone=\"efficientnet_b0\", out_dim=K):\n        super().__init__()\n        self.backbone = timm.create_model(backbone, pretrained=False, num_classes=0, global_pool=\"avg\")\n        dim = self.backbone.num_features\n        self.head = nn.Sequential(\n            nn.Linear(dim, 256),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.3),\n            nn.Linear(256, out_dim)\n        )\n\n    def forward(self, x):\n        f = self.backbone(x)\n        return self.head(f)\n\ndef weighted_mse(pred, target):\n    return ((pred - target) ** 2 * W.view(1, -1)).mean()\n\ndef weighted_r2_log(y_true_log, y_pred_log):\n    per = [r2_score(y_true_log[:,j], y_pred_log[:,j]) for j in range(y_true_log.shape[1])]\n    per = np.array(per, dtype=np.float32)\n    return float((per * w).sum())","metadata":{"_uuid":"25373444-bafc-4079-874c-6e489710a15d","_cell_guid":"b01a0919-b879-4a7d-aba8-eb62d5822022","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-12-11T20:41:57.737340Z","iopub.execute_input":"2025-12-11T20:41:57.737614Z","iopub.status.idle":"2025-12-11T20:41:57.758461Z","shell.execute_reply.started":"2025-12-11T20:41:57.737592Z","shell.execute_reply":"2025-12-11T20:41:57.757118Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 8\nBATCH  = 16\nLR     = 2e-4\n\nkf = KFold(n_splits=2, shuffle=True, random_state=SEED)\noof_pred  = np.zeros((len(df_train), K), dtype=np.float32)\ntest_pred = np.zeros((len(df_test),  K), dtype=np.float32)\n\nfor fold, (tr_idx, va_idx) in enumerate(kf.split(df_train)):\n    print(f\"\\n===== Fold {fold+1}/1 =====\")\n    tr_df = df_train.iloc[tr_idx].reset_index(drop=True)\n    va_df = df_train.iloc[va_idx].reset_index(drop=True)\n\n    tr_y = Y_log[tr_idx]\n    va_y = Y_log[va_idx]\n\n    train_ds = ImgOnlyDataset(tr_df, y_log=tr_y, tfms=train_tfms)\n    val_ds   = ImgOnlyDataset(va_df, y_log=va_y, tfms=test_tfms)\n    test_ds  = ImgOnlyDataset(df_test, y_log=None, tfms=test_tfms)\n\n    train_loader = DataLoader(train_ds, batch_size=BATCH, shuffle=True, num_workers=0, pin_memory=True)\n    val_loader   = DataLoader(val_ds, batch_size=BATCH*2, shuffle=False, num_workers=0, pin_memory=True)\n    test_loader  = DataLoader(test_ds, batch_size=BATCH*2, shuffle=False, num_workers=0, pin_memory=True)\n\n    model = ImgRegressor(\"efficientnet_b0\", out_dim=K).to(DEVICE)\n    opt = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)\n    sch = torch.optim.lr_scheduler.CosineAnnealingLR(opt, T_max=EPOCHS)\n\n    best = -1e9\n    best_state = None\n\n    for epoch in range(1, EPOCHS+1):\n        model.train()\n        for x, yb in train_loader:\n            x, yb = x.to(DEVICE), yb.to(DEVICE)\n            opt.zero_grad(set_to_none=True)\n            pred = model(x)\n            loss = weighted_mse(pred, yb)\n            loss.backward()\n            opt.step()\n        sch.step()\n\n        model.eval()\n        va_preds = []\n        with torch.no_grad():\n            for x, yb in val_loader:\n                x = x.to(DEVICE)\n                va_preds.append(model(x).cpu().numpy())\n        va_preds = np.concatenate(va_preds, axis=0)\n        score = weighted_r2_log(va_y, va_preds)\n        print(f\"Epoch {epoch:02d} | val weightedR2(log1p)={score:.5f}\")\n\n        if score > best:\n            best = score\n            best_state = {k:v.detach().cpu().clone() for k,v in model.state_dict().items()}\n\n    model.load_state_dict(best_state)\n    model.eval()\n\n    # OOF\n    va_preds = []\n    with torch.no_grad():\n        for x, yb in val_loader:\n            x = x.to(DEVICE)\n            va_preds.append(model(x).cpu().numpy())\n    va_preds = np.concatenate(va_preds, axis=0)\n    oof_pred[va_idx] = va_preds\n\n    # TEST\n    te_preds = []\n    with torch.no_grad():\n        for x in test_loader:\n            x = x.to(DEVICE)\n            te_preds.append(model(x).cpu().numpy())\n    te_preds = np.concatenate(te_preds, axis=0)\n    test_pred += te_preds / kf.get_n_splits()\n\nprint(\"\\nOOF weightedR2(log1p):\", weighted_r2_log(Y_log, oof_pred))\n\n# build submission (long)\ntest_pred_raw = np.expm1(test_pred)\ntest_pred_raw = np.clip(test_pred_raw, 0, None)\n\npred_wide = pd.DataFrame(test_pred_raw, columns=target_names)\npred_wide[IMG_COL] = df_test[IMG_COL].values\n\npred_long = pred_wide.melt(id_vars=[IMG_COL], var_name=TNAME_COL, value_name=\"target\")\npred_long[\"image_id\"] = pred_long[IMG_COL].apply(lambda p: Path(p).stem)\npred_long[\"sample_id\"] = pred_long[\"image_id\"] + \"__\" + pred_long[TNAME_COL]\n\nsubmission = sub_sample[[\"sample_id\"]].merge(\n    pred_long[[\"sample_id\", \"target\"]],\n    on=\"sample_id\",\n    how=\"left\"\n)\nsubmission[\"target\"] = submission[\"target\"].fillna(0.0).astype(np.float32)\nsubmission.to_csv(\"/kaggle/working/submission.csv\", index=False)\nsubmission.head()","metadata":{"_uuid":"b2943814-9549-40b4-a35a-cffc5d787f34","_cell_guid":"646db721-9d6c-43db-abd9-281ad00a4420","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2025-12-11T20:53:32.776172Z","iopub.execute_input":"2025-12-11T20:53:32.776668Z","iopub.status.idle":"2025-12-11T21:28:50.609288Z","shell.execute_reply.started":"2025-12-11T20:53:32.776637Z","shell.execute_reply":"2025-12-11T21:28:50.607980Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"_uuid":"862edfa5-a271-4227-ade3-6f8a9661d81f","_cell_guid":"9704d07c-b668-4c5e-ba83-268acb996150","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null}]}