{"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":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"! pip install lightning wandb albumentations==1.4.6 albucore==0.0.11 \"numpy<2.1.0\" -U","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:28:14.396018Z","iopub.execute_input":"2025-11-10T15:28:14.396954Z","iopub.status.idle":"2025-11-10T15:28:19.427873Z","shell.execute_reply.started":"2025-11-10T15:28:14.396905Z","shell.execute_reply":"2025-11-10T15:28:19.426775Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from albumentations.pytorch import ToTensorV2\nfrom kaggle_secrets import UserSecretsClient\nfrom pathlib import Path\nfrom PIL import Image\nfrom pytorch_lightning.callbacks import EarlyStopping, ModelCheckpoint\nfrom pytorch_lightning.loggers import WandbLogger\nfrom sklearn.model_selection import train_test_split\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nimport albumentations as A\nimport matplotlib.pyplot as plt\nimport numpy as np  # linear algebra\nimport os\nimport pandas as pd\nimport pytorch_lightning as pl\nimport torch\nimport torch.nn as nn\nimport wandb\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:28:19.429857Z","iopub.execute_input":"2025-11-10T15:28:19.430492Z","iopub.status.idle":"2025-11-10T15:28:19.437720Z","shell.execute_reply.started":"2025-11-10T15:28:19.430465Z","shell.execute_reply":"2025-11-10T15:28:19.436862Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df = pd.read_csv('/kaggle/input/csiro-biomass/train.csv')\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:28:19.438440Z","iopub.execute_input":"2025-11-10T15:28:19.438608Z","iopub.status.idle":"2025-11-10T15:28:19.475083Z","shell.execute_reply.started":"2025-11-10T15:28:19.438594Z","shell.execute_reply":"2025-11-10T15:28:19.474341Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Target EDA","metadata":{}},{"cell_type":"code","source":"train_df['target_name'].value_counts().plot.barh()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:28:19.476970Z","iopub.execute_input":"2025-11-10T15:28:19.477212Z","iopub.status.idle":"2025-11-10T15:28:19.717584Z","shell.execute_reply.started":"2025-11-10T15:28:19.477195Z","shell.execute_reply":"2025-11-10T15:28:19.716846Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"targets = train_df['target_name'].unique()\ntrain_df = pd.pivot_table(train_df, values='target', index=['image_path', 'Sampling_Date', 'State', 'Species','Pre_GSHH_NDVI', 'Height_Ave_cm'],columns=['target_name'], aggfunc=\"sum\").reset_index()\n\ntrain_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:28:19.718467Z","iopub.execute_input":"2025-11-10T15:28:19.718715Z","iopub.status.idle":"2025-11-10T15:28:19.751529Z","shell.execute_reply.started":"2025-11-10T15:28:19.718695Z","shell.execute_reply":"2025-11-10T15:28:19.750734Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"f, axs = plt.subplots(1, 5, sharey=True, figsize=(15, 3))\nfor i, target_name in enumerate(targets):\n    train_df[target_name].hist(ax=axs[i])\n    axs[i].set_title(target_name)\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:28:19.752334Z","iopub.execute_input":"2025-11-10T15:28:19.752539Z","iopub.status.idle":"2025-11-10T15:28:20.343337Z","shell.execute_reply.started":"2025-11-10T15:28:19.752523Z","shell.execute_reply":"2025-11-10T15:28:20.342644Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"f, axs = plt.subplots(1, 5, sharey=True, figsize=(15, 3))\nfor i, target_name in enumerate(targets):\n    np.log1p(train_df[target_name]).hist(ax=axs[i])\n    axs[i].set_title(target_name)\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:28:20.344106Z","iopub.execute_input":"2025-11-10T15:28:20.344431Z","iopub.status.idle":"2025-11-10T15:28:20.936938Z","shell.execute_reply.started":"2025-11-10T15:28:20.344400Z","shell.execute_reply":"2025-11-10T15:28:20.936227Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"f, axs = plt.subplots(1, 3, figsize=(15, 3))\n\ntrain_df['Pre_GSHH_NDVI'].hist(bins=100, ax=axs[0])\ntrain_df['State'].value_counts().plot.bar( ax=axs[1])\ntrain_df['Species'].value_counts().plot.bar(ax=axs[2])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:28:20.937680Z","iopub.execute_input":"2025-11-10T15:28:20.937935Z","iopub.status.idle":"2025-11-10T15:28:21.573562Z","shell.execute_reply.started":"2025-11-10T15:28:20.937917Z","shell.execute_reply":"2025-11-10T15:28:21.572772Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:28:21.574471Z","iopub.execute_input":"2025-11-10T15:28:21.574691Z","iopub.status.idle":"2025-11-10T15:28:21.590137Z","shell.execute_reply.started":"2025-11-10T15:28:21.574676Z","shell.execute_reply":"2025-11-10T15:28:21.589509Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"unique_states = train_df['State'].unique()\n\nf, axs = plt.subplots(1, 4, sharey=True, figsize=(15, 3))\nfor i, state in enumerate(unique_states):\n    train_df[train_df['State'] == state]['Species'].value_counts().plot.bar(ax=axs[i])\n    axs[i].set_title(state)\n\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:28:21.592932Z","iopub.execute_input":"2025-11-10T15:28:21.593139Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch_train_df, torch_test_df = train_test_split(train_df,  stratify=train_df['State'] + train_df['Species'], random_state=42)\ntorch_train_df, torch_val_df = train_test_split(torch_train_df, stratify=torch_train_df['State'] + torch_train_df['Species'], random_state=42)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ROOT_DIR = Path('/kaggle/input/csiro-biomass')\n\nROOT_DIR.exists()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class PlantDataset(Dataset):\n    def __init__(self,root_dir: Path, df:pd.DataFrame,transform, cache_images=True):\n        if not root_dir.exists():\n            raise(ValueError(root_dir))\n        self.root_dir = root_dir\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n        self.targets = [\"Dry_Clover_g\", \"Dry_Dead_g\", \"Dry_Green_g\", \"Dry_Total_g\", \"GDM_g\"]\n        self.cache_images = cache_images\n        self.cache = {}\n\n        if self.cache_images:\n            print(f\"Caching {len(self.df)} images to RAM...\")\n            for i, row in enumerate(self.df.itertuples()):\n                img = Image.open(self.root_dir / row.image_path).convert(\"RGB\")\n                self.cache[i] = np.array(img)\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img = self.cache[idx] if self.cache_images else np.array(Image.open(self.root_dir / self.df.loc[idx, \"image_path\"]).convert(\"RGB\"))\n        aug = self.transform(image=img)\n        image = aug[\"image\"]\n        y = torch.tensor(self.df.loc[idx, self.targets].astype(float).values, dtype=torch.float32)\n        return image, y\n        \nds = PlantDataset(ROOT_DIR, torch_train_df.iloc[:1], A.Compose([\n    A.LongestMaxSize(max_size=512),\n    A.PadIfNeeded(min_height=512, min_width=512, border_mode=0),\n    A.Normalize(mean=(0.485, 0.456, 0.406),\n                std=(0.229, 0.224, 0.225)),\n    ToTensorV2(),\n]))\n\nX, y = ds[0]\nX.shape","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"torch_train_df.iloc[:1]","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_transform = A.Compose([\n    # --- Resize ---\n    A.LongestMaxSize(max_size=512),\n    A.PadIfNeeded(min_height=512, min_width=512, border_mode=0),\n\n    # --- Photometric ---\n    A.OneOf([\n        A.RandomBrightnessContrast(brightness_limit=0.25, contrast_limit=0.25, p=0.8),\n        A.CLAHE(clip_limit=2.0, p=0.5),\n        A.HueSaturationValue(hue_shift_limit=10, sat_shift_limit=20, val_shift_limit=10, p=0.7),\n    ], p=0.9),\n\n    # --- Geometric ---\n    A.OneOf([\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.1),\n        A.RandomRotate90(p=0.3),\n        A.ShiftScaleRotate(shift_limit=0.05, scale_limit=0.1, rotate_limit=25,\n                           border_mode=0, p=0.8),\n    ], p=0.9),\n\n    # --- Blur/Noise ---\n    A.OneOf([\n        A.MotionBlur(blur_limit=3, p=0.2),\n        A.GaussianBlur(blur_limit=3, p=0.2),\n        A.GaussNoise(var_limit=(10.0, 50.0), p=0.3),\n    ], p=0.5),\n\n    # --- Color style & compression ---\n    A.OneOf([\n        A.ImageCompression(quality_lower=70, quality_upper=100, p=0.3),\n        A.Downscale(scale_min=0.7, scale_max=0.9, p=0.3),\n    ], p=0.3),\n\n    # --- Cutout & Mixup-like ---\n    A.CoarseDropout(max_holes=8, max_height=64, max_width=64, p=0.5),\n\n    # --- Normalize + Tensor ---\n    A.Normalize(mean=(0.485, 0.456, 0.406),\n                std=(0.229, 0.224, 0.225)),\n    ToTensorV2(),\n])\n\nval_transform = A.Compose([\n    A.LongestMaxSize(max_size=512),\n    A.PadIfNeeded(min_height=512, min_width=512, border_mode=0),\n    A.Normalize(mean=(0.485, 0.456, 0.406),\n                std=(0.229, 0.224, 0.225)),\n    ToTensorV2(),\n])\n\ntrain_ds = PlantDataset(ROOT_DIR, torch_train_df, train_transform)\nval_ds = PlantDataset(ROOT_DIR, torch_val_df, val_transform)\ntest_ds = PlantDataset(ROOT_DIR, torch_test_df, val_transform)\n\n\nbatch_size=64\nepochs=10\nlr=1e-4\n\ntrain_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True,\n                          num_workers=2,\n                          pin_memory=True,\n                          persistent_workers=True)\nval_loader = DataLoader(val_ds, batch_size=batch_size, shuffle=False,\n                          num_workers=2,\n                          pin_memory=True,\n                          persistent_workers=True)\ntest_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False,\n                          num_workers=2,\n                          pin_memory=True,\n                          persistent_workers=True)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def weighted_r2(y_true, y_pred):\n    weights = torch.tensor([0.1, 0.1, 0.1, 0.5, 0.2], device=y_true.device)\n    w = weights.unsqueeze(0).expand_as(y_true)\n    y_true_f, y_pred_f, w_f = y_true.reshape(-1), y_pred.reshape(-1), w.reshape(-1)\n    y_mean = torch.sum(w_f * y_true_f) / torch.sum(w_f)\n    ss_res = torch.sum(w_f * (y_true_f - y_pred_f) ** 2)\n    ss_tot = torch.sum(w_f * (y_true_f - y_mean) ** 2)\n    return 1 - ss_res / ss_tot if ss_tot != 0 else torch.tensor(0.0, device=y_true.device)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ZeroInflatedRegressor(pl.LightningModule):\n    def __init__(self, lr=1e-4, freeze_backbone=True, lambda_bce=1.0, lambda_mse=1.0):\n        super().__init__()\n        self.save_hyperparameters()\n        base = models.efficientnet_b3(weights=models.EfficientNet_B3_Weights.DEFAULT)\n\n        if freeze_backbone:\n            for p in base.parameters():\n                p.requires_grad = False\n\n        feature_dim = base.classifier[1].in_features\n        base.classifier = nn.Identity()\n        self.backbone = base\n\n        self.zero_head = nn.Sequential(\n            nn.Linear(feature_dim, 128),\n            nn.ReLU(),\n            nn.Linear(128, 5)\n        )\n\n        self.count_head = nn.Sequential(\n            nn.Linear(feature_dim, 128),\n            nn.ReLU(),\n            nn.Linear(128, 5)\n        )\n\n        self.lambda_bce = lambda_bce\n        self.lambda_mse = lambda_mse\n        self.lr = lr\n\n    def forward(self, x):\n        feats = self.backbone(x)\n        pi = torch.sigmoid(self.zero_head(feats))\n        mu = self.count_head(feats)\n        return pi, mu\n\n    def zero_inflated_loss(self, y, pi, mu):\n        is_zero = (y <= 0)\n        y_log = torch.log1p(torch.clamp(y, min=0))\n\n        bce = nn.functional.binary_cross_entropy(pi, is_zero.float())\n\n        if (~is_zero).any():\n            mse = nn.functional.mse_loss(mu[~is_zero], y_log[~is_zero])\n        else:\n            mse = torch.tensor(0.0, device=y.device)\n\n        return self.lambda_bce * bce + self.lambda_mse * mse\n\n    def step(self, batch, stage):\n        x, y = batch\n        pi, mu = self(x)\n        loss = self.zero_inflated_loss(y, pi, mu)\n\n        y_pred = torch.expm1(mu) * (1 - pi)\n        r2 = weighted_r2(y, y_pred)\n\n        self.log(f\"{stage}_loss\", loss, prog_bar=True)\n        self.log(f\"{stage}_r2_weighted\", r2, prog_bar=True)\n        return loss\n\n    def training_step(self, batch, batch_idx):\n        return self.step(batch, \"train\")\n\n    def validation_step(self, batch, batch_idx):\n        return self.step(batch, \"val\")\n\n    def test_step(self, batch, batch_idx):\n        return self.step(batch, \"test\")\n\n    def configure_optimizers(self):\n        return torch.optim.Adam(self.parameters(), lr=self.lr)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = ZeroInflatedRegressor(\n    lr=1e-3,\n    freeze_backbone=True,\n    lambda_bce=0.3,\n    lambda_mse=1.0\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"user_secrets = UserSecretsClient()\n\nsecret_value_0 = user_secrets.get_secret(\"WANDB_API_KEY\")\nwandb.login(key=secret_value_0)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"wandb_logger = WandbLogger(project=\"csiro\", log_model=\"all\", checkpoint_name='csiro_regression')\nwandb_logger.watch(model)\n\n# Callbacks\nearly_stop = EarlyStopping(\n    monitor=\"val_loss\",\n    mode=\"min\",\n    patience=10,\n    verbose=True\n)\n\ncheckpoint = ModelCheckpoint(\n    monitor=\"val_loss\",\n    mode=\"min\",\n    save_top_k=1,\n    filename=\"best-{epoch:02d}-{val_loss:.3f}\"\n)\n\ntrainer = pl.Trainer(\n    max_epochs=1000,\n    accelerator=\"auto\",\n    devices=\"auto\",\n    logger=wandb_logger,\n    callbacks=[early_stop, checkpoint],\n)\ntrainer.fit(model, train_loader, val_loader)\ntrainer.test(model, test_loader)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}