{"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":[{"sourceType":"competition","sourceId":112509,"databundleVersionId":14254895,"isSourceIdPinned":false}],"dockerImageVersionId":31260,"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-04-23T20:19:47.335926Z","iopub.execute_input":"2026-04-23T20:19:47.336648Z","iopub.status.idle":"2026-04-23T20:19:48.100428Z","shell.execute_reply.started":"2026-04-23T20:19:47.336619Z","shell.execute_reply":"2026-04-23T20:19:48.099722Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os, cv2, gc, torch, glob\nimport pandas as pd\nimport numpy as np\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nimport timm \nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom sklearn.model_selection import KFold\nfrom tqdm.auto import tqdm\n\n# --- 1. CONFIGURATION ---\nCFG = {\n    \"model\": \"vit_base_patch14_dinov2\",\n    \"img_size\": 518,\n    \"batch_size\": 4, \n    \"epochs\": 5,\n    \"nfolds\": 5,\n    \"lr\": 8e-6, # Lower LR for stability\n    \"target_cols\": ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g'],\n    \"weights\": torch.tensor([0.1, 0.1, 0.1, 0.2, 0.5])\n}\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n\n# --- 2. FOLDER DISCOVERY ---\nall_files = glob.glob('/kaggle/input/**/*.jpg', recursive=True) + glob.glob('/kaggle/input/**/*.JPG', recursive=True)\npath_map = {os.path.basename(f).split('.')[0]: f for f in all_files}\nprint(f\"Found {len(path_map)} images.\")\n\n# --- 3. DATA PREPARATION ---\ndef get_data():\n    df = pd.read_csv('/kaggle/input/csiro-biomass/train.csv')\n    df['img_id_clean'] = df['image_path'].apply(lambda x: str(x).split('/')[-1].split('.')[0])\n    df_wide = df.pivot_table(index=['img_id_clean', 'Pre_GSHH_NDVI', 'Height_Ave_cm'], \n                             columns='target_name', values='target').reset_index()\n    for col in CFG['target_cols']:\n        df_wide[col] = pd.to_numeric(df_wide[col], errors='coerce').fillna(0.0).astype(np.float32)\n    \n    df_wide['fold'] = -1\n    kf = KFold(n_splits=CFG['nfolds'], shuffle=True, random_state=42)\n    for f, (t_, v_) in enumerate(kf.split(df_wide)):\n        df_wide.loc[v_, 'fold'] = f\n    return df_wide\n\n# --- 4. DATASET CLASS (Robust against Missing Columns) ---\nclass BiomassDataset(Dataset):\n    def __init__(self, df, mode='train'):\n        self.df, self.mode = df, mode\n        self.transform = A.Compose([\n            A.Resize(CFG['img_size'], CFG['img_size']),\n            A.HorizontalFlip(p=0.5) if mode=='train' else A.NoOp(),\n            A.Normalize(),\n            ToTensorV2()\n        ])\n    def __len__(self): return len(self.df)\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        path = path_map.get(row['img_id_clean'])\n        img = cv2.imread(path)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        img = self.transform(image=img)['image']\n        \n        # Meta fix: Safe get column values\n        ndvi = row.get('Pre_GSHH_NDVI', 0.0)\n        height = row.get('Height_Ave_cm', 0.0)\n        meta = torch.tensor([float(ndvi), float(height)], dtype=torch.float32)\n        \n        if self.mode != 'test':\n            targets = torch.tensor(row[CFG['target_cols']].values.astype(np.float32))\n            return img, meta, targets\n        return img, meta, row['img_id_clean']\n\n# --- 5. MODEL & TRAINING ---\nclass BiomassModel(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.backbone = timm.create_model(CFG['model'], pretrained=True, num_classes=0)\n        self.meta_head = nn.Sequential(nn.Linear(2, 32), nn.ReLU(), nn.Linear(32, 32))\n        self.final = nn.Sequential(nn.Linear(self.backbone.num_features + 32, 128), nn.ReLU(), nn.Linear(128, 5))\n    def forward(self, img, meta):\n        return self.final(torch.cat([self.backbone(img), self.meta_head(meta)], dim=1))\n\ntrain_df = get_data()\ntest_csv = pd.read_csv('/kaggle/input/csiro-biomass/test.csv')\ntest_csv['img_id_clean'] = test_csv['image_path'].apply(lambda x: str(x).split('/')[-1].split('.')[0])\nfinal_test_preds = np.zeros((len(test_csv), 5))\n\nfor fold in range(CFG['nfolds']):\n    print(f\"\\n--- Fold {fold} ---\")\n    t_df = train_df[train_df.fold != fold].reset_index(drop=True)\n    train_loader = DataLoader(BiomassDataset(t_df), batch_size=CFG['batch_size'], shuffle=True)\n    \n    model = BiomassModel().to(device)\n    optimizer = torch.optim.AdamW(model.parameters(), lr=CFG['lr'])\n    # Scheduler to lower LR if loss spikes\n    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(optimizer, mode='min', factor=0.5, patience=1)\n\n    for epoch in range(CFG['epochs']):\n        model.train()\n        pbar = tqdm(train_loader, desc=f\"Epoch {epoch}\")\n        epoch_loss = 0\n        for img, meta, targets in pbar:\n            img, meta, targets = img.to(device), meta.to(device), targets.to(device)\n            optimizer.zero_grad()\n            outputs = model(img, meta)\n            loss = (torch.square(outputs - targets) * CFG['weights'].to(device)).mean()\n            loss.backward()\n            # Gradient clipping to prevent 1.27e+3 spikes\n            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)\n            optimizer.step()\n            epoch_loss += loss.item()\n            pbar.set_postfix({'loss': loss.item()})\n        scheduler.step(epoch_loss)\n\n    model.eval()\n    test_loader = DataLoader(BiomassDataset(test_csv, mode='test'), batch_size=CFG['batch_size'], shuffle=False)\n    fold_preds = []\n    with torch.no_grad():\n        for img, meta, _ in tqdm(test_loader, desc=\"Inference\"):\n            fold_preds.append(model(img.to(device), meta.to(device)).cpu().numpy())\n    final_test_preds += np.vstack(fold_preds) / CFG['nfolds']\n    del model; gc.collect(); torch.cuda.empty_cache()\n\n# --- 6. SUBMISSION ---\nresults = []\nfor i, path in enumerate(test_csv['image_path']):\n    for j, col in enumerate(CFG['target_cols']):\n        results.append({'sample_id': f\"{path}__{col}\", 'target': max(0.0, final_test_preds[i, j])})\npd.DataFrame(results).to_csv(\"submission.csv\", index=False)\nprint(\"Submission complete!\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-23T20:19:48.101865Z","iopub.execute_input":"2026-04-23T20:19:48.102287Z"}},"outputs":[],"execution_count":null}]}