{"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":true,"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-11-10T15:09:00.058860Z","iopub.execute_input":"2025-11-10T15:09:00.059182Z","iopub.status.idle":"2025-11-10T15:09:07.096399Z","shell.execute_reply.started":"2025-11-10T15:09:00.059156Z","shell.execute_reply":"2025-11-10T15:09:07.095172Z"}},"outputs":[],"execution_count":null},{"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:09:07.098790Z","iopub.execute_input":"2025-11-10T15:09:07.099225Z","iopub.status.idle":"2025-11-10T15:11:29.505867Z","shell.execute_reply.started":"2025-11-10T15:09:07.099200Z","shell.execute_reply":"2025-11-10T15:11:29.504040Z"}},"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:11:29.508576Z","iopub.execute_input":"2025-11-10T15:11:29.509051Z","iopub.status.idle":"2025-11-10T15:12:17.360077Z","shell.execute_reply.started":"2025-11-10T15:11:29.509007Z","shell.execute_reply":"2025-11-10T15:12:17.358567Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ROOT_DIR = Path('/kaggle/input/csiro-biomass')\n\ntest_images_df = pd.DataFrame({\n    \"image_path\": [\n        f\"test/{path.name}\" for path in \n        list(Path(ROOT_DIR).glob('test/*'))\n    ]\n})\ntest_images_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:12:17.361578Z","iopub.execute_input":"2025-11-10T15:12:17.361964Z","iopub.status.idle":"2025-11-10T15:12:17.423539Z","shell.execute_reply.started":"2025-11-10T15:12:17.361936Z","shell.execute_reply":"2025-11-10T15:12:17.422499Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create a simple inference dataset (without targets)\nclass InferenceDataset(Dataset):\n    def __init__(self, root_dir: Path, df: pd.DataFrame, transform):\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\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img = np.array(\n            Image.open(self.root_dir / self.df.loc[idx, \"image_path\"]).convert(\"RGB\")\n        )\n        aug = self.transform(image=img)\n        image = aug[\"image\"]\n        return image\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\n\n# Create inference dataset and dataloader\ninference_ds = InferenceDataset(ROOT_DIR, test_images_df, val_transform)\ninference_loader = DataLoader(\n    inference_ds,\n    batch_size=16,\n    shuffle=False,\n    num_workers=2,\n    pin_memory=True,\n)\n\nprint(f\"Number of test images: {len(inference_ds)}\")\n\ninference_ds[0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:12:17.424715Z","iopub.execute_input":"2025-11-10T15:12:17.425060Z","iopub.status.idle":"2025-11-10T15:12:17.817973Z","shell.execute_reply.started":"2025-11-10T15:12:17.425031Z","shell.execute_reply":"2025-11-10T15:12:17.816464Z"}},"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)\n\nclass 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,"execution":{"iopub.status.busy":"2025-11-10T15:12:17.818800Z","iopub.execute_input":"2025-11-10T15:12:17.819121Z","iopub.status.idle":"2025-11-10T15:12:17.838754Z","shell.execute_reply.started":"2025-11-10T15:12:17.819084Z","shell.execute_reply":"2025-11-10T15:12:17.837459Z"}},"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)\nrun = wandb.init()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:12:17.842102Z","iopub.execute_input":"2025-11-10T15:12:17.842465Z","iopub.status.idle":"2025-11-10T15:12:33.710427Z","shell.execute_reply.started":"2025-11-10T15:12:17.842439Z","shell.execute_reply":"2025-11-10T15:12:33.709262Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"artifact = run.use_artifact('yuyuu___/csiro/csiro_regression:best', type='model')\nartifact_dir = artifact.download()\n\nartifact_dir","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:12:33.711455Z","iopub.execute_input":"2025-11-10T15:12:33.711737Z","iopub.status.idle":"2025-11-10T15:12:38.376991Z","shell.execute_reply.started":"2025-11-10T15:12:33.711714Z","shell.execute_reply":"2025-11-10T15:12:38.375771Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"best_model = ZeroInflatedRegressor.load_from_checkpoint(Path(artifact_dir) /\"model.ckpt\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:12:38.378122Z","iopub.execute_input":"2025-11-10T15:12:38.378474Z","iopub.status.idle":"2025-11-10T15:12:39.633811Z","shell.execute_reply.started":"2025-11-10T15:12:38.378437Z","shell.execute_reply":"2025-11-10T15:12:39.629731Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\nbest_model.eval()\nbest_model.to(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n\n# Generate predictions\npredictions = []\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\nwith torch.no_grad():\n    for batch in inference_loader:\n        batch = batch.to(device)\n        pi, mu = best_model(batch)\n        # Zero-inflated prediction: exp(mu) * (1 - pi)\n        y_pred = torch.expm1(mu) * (1 - pi)\n        predictions.append(y_pred.cpu().numpy())\n\n# Concatenate all predictions\nall_predictions = np.concatenate(predictions, axis=0)\nprint(f\"Predictions shape: {all_predictions.shape}\")  # Should be (num_test_images, 5)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:12:39.638101Z","iopub.execute_input":"2025-11-10T15:12:39.640068Z","iopub.status.idle":"2025-11-10T15:12:40.825735Z","shell.execute_reply.started":"2025-11-10T15:12:39.640024Z","shell.execute_reply":"2025-11-10T15:12:40.824327Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Add predictions to test dataframe\ntarget_columns = [\"Dry_Clover_g\", \"Dry_Dead_g\", \"Dry_Green_g\", \"Dry_Total_g\", \"GDM_g\"]\nfor i, col in enumerate(target_columns):\n    test_images_df[col] = all_predictions[:, i]\n\ntest_images_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:12:40.827138Z","iopub.execute_input":"2025-11-10T15:12:40.827457Z","iopub.status.idle":"2025-11-10T15:12:40.853111Z","shell.execute_reply.started":"2025-11-10T15:12:40.827428Z","shell.execute_reply":"2025-11-10T15:12:40.851523Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Convert to long format (required submission format)\n# Extract image ID from image_path (e.g., \"test/ID1001187975.jpg\" -> \"ID1001187975\")\ntest_images_df[\"image_id\"] = test_images_df[\"image_path\"].apply(lambda x: Path(x).stem)\n\n# Melt the dataframe to long format\nsubmission_df = test_images_df.melt(\n    id_vars=[\"image_id\"],\n    value_vars=target_columns,\n    var_name=\"target_name\",\n    value_name=\"target\",\n)\n\n# Create sample_id in the required format: {image_id}__{target_name}\nsubmission_df[\"sample_id\"] = (\n    submission_df[\"image_id\"] + \"__\" + submission_df[\"target_name\"]\n)\n\n# Select only the required columns\nsubmission_df = submission_df[[\"sample_id\", \"target\"]]\n\n# Sort to match expected order\nsubmission_df = submission_df.sort_values(\"sample_id\").reset_index(drop=True)\n\nprint(f\"Submission shape: {submission_df.shape}\")\nsubmission_df.head(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:12:40.854442Z","iopub.execute_input":"2025-11-10T15:12:40.854797Z","iopub.status.idle":"2025-11-10T15:12:40.910743Z","shell.execute_reply.started":"2025-11-10T15:12:40.854769Z","shell.execute_reply":"2025-11-10T15:12:40.909529Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df.to_csv(\"/kaggle/working/submission.csv\", index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-10T15:12:40.911929Z","iopub.execute_input":"2025-11-10T15:12:40.912935Z","iopub.status.idle":"2025-11-10T15:12:40.939653Z","shell.execute_reply.started":"2025-11-10T15:12:40.912904Z","shell.execute_reply":"2025-11-10T15:12:40.938110Z"}},"outputs":[],"execution_count":null}]}