{"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"},{"sourceId":663667,"sourceType":"modelInstanceVersion","modelInstanceId":502209,"modelId":517372},{"sourceId":663696,"sourceType":"modelInstanceVersion","modelInstanceId":502235,"modelId":517398},{"sourceId":664798,"sourceType":"modelInstanceVersion","modelInstanceId":503154,"modelId":518292}],"dockerImageVersionId":31192,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## 🔴 **Import**","metadata":{}},{"cell_type":"code","source":"import os\nimport math\nimport random\nfrom pprint import pprint\nfrom tqdm.auto import tqdm\nfrom collections import Counter\nfrom dataclasses import dataclass\n\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import KFold\n\nimport torch\nfrom torch import nn\nimport torchvision.transforms as T\nfrom torchvision import models\nfrom torch.utils.data import Dataset, DataLoader\n\nfrom torchmetrics import MeanMetric","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:45.822559Z","iopub.execute_input":"2025-11-28T20:46:45.822836Z","iopub.status.idle":"2025-11-28T20:46:57.754884Z","shell.execute_reply.started":"2025-11-28T20:46:45.822816Z","shell.execute_reply":"2025-11-28T20:46:57.754071Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🔴 **Args**","metadata":{}},{"cell_type":"code","source":"@dataclass\nclass Config:\n    base_path: str\n    seed: int = 1337\n    batch_size: int = 8\n    img_size: int = 512\n    epochs: int = 20\n    learning_rate: float = 1e-4\n    backbone: str = \"efficientnet_b0\"\n    train: bool = True\n    n_folds: int = 5\n    weight_decay: float = 0.01\n    device: bool = 'cuda' if torch.cuda.is_available() else 'cpu'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:57.756646Z","iopub.execute_input":"2025-11-28T20:46:57.757035Z","iopub.status.idle":"2025-11-28T20:46:57.812488Z","shell.execute_reply.started":"2025-11-28T20:46:57.757014Z","shell.execute_reply":"2025-11-28T20:46:57.811854Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cfg = Config(\n    base_path = \"/kaggle/input/csiro-biomass\"\n)\npprint(cfg)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:57.813472Z","iopub.execute_input":"2025-11-28T20:46:57.813846Z","iopub.status.idle":"2025-11-28T20:46:57.830883Z","shell.execute_reply.started":"2025-11-28T20:46:57.813819Z","shell.execute_reply":"2025-11-28T20:46:57.830240Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🔴 **Functions**","metadata":{}},{"cell_type":"markdown","source":"### 🟠 **Utils**","metadata":{}},{"cell_type":"code","source":"def calc_parameters(model, scale=1e6):\n    return sum([p.numel() for p in model.parameters() if p.requires_grad]) / scale","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:57.832154Z","iopub.execute_input":"2025-11-28T20:46:57.832384Z","iopub.status.idle":"2025-11-28T20:46:57.845705Z","shell.execute_reply.started":"2025-11-28T20:46:57.832368Z","shell.execute_reply":"2025-11-28T20:46:57.845049Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def weighted_r2_multi_target(y_true, y_pred):\n    \"\"\"\n    Calculates Weighted R2 strictly according to the formula, implemented in PyTorch.\n    \n    Args:\n        y_true: Ground truth tensor, shape (N, 5)\n        y_pred: Prediction tensor, shape (N, 5)\n        \n    Returns:\n        weighted_r2: Scalar tensor containing the weighted R2 score.\n    \"\"\"\n    \n    # 1. Define weights\n    # Note: We use the device and dtype of the input to ensure compatibility (e.g., CUDA, float32/64)\n    weights_per_col = torch.tensor([0.1, 0.1, 0.1, 0.2, 0.5], device=y_true.device, dtype=y_true.dtype)\n    \n    # 2. Align weights dimensions with data\n    # Expand weights to shape (N, 5) so each row gets the same column weights\n    weights_expanded = weights_per_col.unsqueeze(0).expand_as(y_true)\n    \n    # 3. Flatten all tensors\n    # The formula sums over all 'j' (data points), treating the matrix as a long vector\n    y_true_flat = y_true.flatten()\n    y_pred_flat = y_pred.flatten()\n    weights_flat = weights_expanded.flatten()\n    \n    # 4. Calculate Global Weighted Mean (y_bar_w)\n    # Formula: sum(w * y) / sum(w)\n    sum_weights = torch.sum(weights_flat)\n    y_bar_w = torch.sum(weights_flat * y_true_flat) / sum_weights\n    \n    # 5. Calculate Weighted Residual Sum of Squares (SS_res)\n    # Formula: sum(w * (y - y_hat)^2)\n    ss_res = torch.sum(weights_flat * (y_true_flat - y_pred_flat) ** 2)\n    \n    # 6. Calculate Weighted Total Sum of Squares (SS_tot)\n    # Formula: sum(w * (y - y_bar_w)^2)\n    ss_tot = torch.sum(weights_flat * (y_true_flat - y_bar_w) ** 2)\n    \n    # 7. Compute final R2\n    # Check for division by zero (if variance is zero)\n    if ss_tot == 0:\n        return torch.tensor(0.0, device=y_true.device, dtype=y_true.dtype)\n        \n    weighted_r2 = 1 - (ss_res / ss_tot)\n    \n    return weighted_r2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:57.846362Z","iopub.execute_input":"2025-11-28T20:46:57.846580Z","iopub.status.idle":"2025-11-28T20:46:57.861490Z","shell.execute_reply.started":"2025-11-28T20:46:57.846554Z","shell.execute_reply":"2025-11-28T20:46:57.860681Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_true = torch.randn(100, 5)\ny_pred = torch.randn(100, 5)\n\nweighted_r2_multi_target(y_true, y_pred)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:57.862281Z","iopub.execute_input":"2025-11-28T20:46:57.862621Z","iopub.status.idle":"2025-11-28T20:46:57.904388Z","shell.execute_reply.started":"2025-11-28T20:46:57.862577Z","shell.execute_reply":"2025-11-28T20:46:57.903730Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🟠 **Train**","metadata":{}},{"cell_type":"code","source":"def train_one_epoch(model, train_loader, loss_fn, optimizer, scheduler=None, epoch=None):\n    model.train()  # Set the model to training mode\n    loss_train, metric_train = MeanMetric().to(cfg.device), MeanMetric().to(cfg.device)  # Initialize a metric to track average training loss\n    all_preds, all_targets = [], []  # Lists to collect predictions and targets for metric\n\n    # Create a progress bar for the training loop\n    with tqdm(train_loader, unit='batch') as tepoch:\n        for inputs, targets in tepoch:\n            if epoch is not None:\n                tepoch.set_description(f'Epoch {epoch}')  # Show current epoch in progress bar\n\n            inputs = inputs.to(cfg.device) \n            targets = targets.to(cfg.device)\n\n            preds = model(inputs) \n\n            loss = loss_fn(preds, targets) \n\n            loss.backward() \n            \n            optimizer.step() \n            optimizer.zero_grad() \n\n            if scheduler is not None:\n                scheduler.step()  # Update learning rate if scheduler is provided\n\n            loss_train.update(loss.item(), inputs.shape[0])  # Update running average of loss\n\n            # Compute weighted R2 score\n            r2 = weighted_r2_multi_target(targets, preds)\n            metric_train.update(r2, inputs.shape[0])\n\n            # Display the current average loss in the progress bar\n            tepoch.set_postfix(loss=loss_train.compute().item(), r2=metric_train.compute().item())\n\n    return loss_train.compute().item(), metric_train.compute().item()  # Return average loss and R2 for the epoch","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:57.905198Z","iopub.execute_input":"2025-11-28T20:46:57.905560Z","iopub.status.idle":"2025-11-28T20:46:57.911899Z","shell.execute_reply.started":"2025-11-28T20:46:57.905538Z","shell.execute_reply":"2025-11-28T20:46:57.911171Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🟠 **Evaluate**","metadata":{}},{"cell_type":"code","source":"def evaluate(model, valid_loader, loss_fn):\n    model.eval()  # Set the model to evaluation mode (disables dropout, etc.)\n    loss_eval, metric_eval = MeanMetric().to(cfg.device), MeanMetric().to(cfg.device)  # Initialize a metric to track average validation loss\n    all_preds, all_targets = [], []  # Lists to collect predictions and targets for accuracy\n\n    with torch.inference_mode():  # Disable gradient computation for faster inference and less memory usage\n        for inputs, targets in valid_loader:\n            inputs = inputs.to(cfg.device)\n            targets = targets.to(cfg.device)\n\n            preds = model(inputs)\n\n            loss = loss_fn(preds, targets)\n            loss_eval.update(loss.item(), inputs.shape[0])\n\n            # Compute hierarchical weighted R2 score\n            r2 = weighted_r2_multi_target(targets, preds)\n            metric_eval.update(r2, inputs.shape[0])\n\n    return loss_eval.compute().item(), metric_eval.compute().item() # Return average validation loss and R2","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:57.912709Z","iopub.execute_input":"2025-11-28T20:46:57.912998Z","iopub.status.idle":"2025-11-28T20:46:57.929738Z","shell.execute_reply.started":"2025-11-28T20:46:57.912981Z","shell.execute_reply":"2025-11-28T20:46:57.928950Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🔴 **Dataset**","metadata":{}},{"cell_type":"code","source":"df_train = pd.read_csv(f\"{cfg.base_path}/train.csv\")\ndf_train.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:57.930714Z","iopub.execute_input":"2025-11-28T20:46:57.931441Z","iopub.status.idle":"2025-11-28T20:46:57.978482Z","shell.execute_reply.started":"2025-11-28T20:46:57.931421Z","shell.execute_reply":"2025-11-28T20:46:57.977913Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# rows = df_train[df_train.image_path == \"train/ID1011485656.jpg\"]\n# (rows.target.values - np.array(list(target_mean.values()))) / np.array(list(target_std.values()))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:57.980756Z","iopub.execute_input":"2025-11-28T20:46:57.980932Z","iopub.status.idle":"2025-11-28T20:46:57.983983Z","shell.execute_reply.started":"2025-11-28T20:46:57.980918Z","shell.execute_reply":"2025-11-28T20:46:57.983228Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🟠 **EDA**","metadata":{}},{"cell_type":"code","source":"# Number of unique images you want to show\nn_samples = 9\n\n# Group by image_path for fast access\ngroups = df_train.groupby(\"image_path\")\n\n# Randomly choose N image IDs\nsampled_image_ids = df_train[\"image_path\"].drop_duplicates().sample(n=n_samples, random_state=42)\n\n# One subplot per image → grid 3x3 for n=9\nn_cols = 3\nn_rows = (n_samples + n_cols - 1) // n_cols   # automatic row count\n\nplt.figure(figsize=(18, 12))\n\nfor idx, img_path in enumerate(sampled_image_ids, start=1):\n\n    # Load image once\n    img = Image.open(f\"{cfg.base_path}/{img_path}\")\n\n    # All target rows for this image\n    target_rows = groups.get_group(img_path)\n\n    # Build caption containing ALL target names + values\n    caption_lines = [\n        f\"{row['target_name']}: {row['target']:.2f}\"\n        for _, row in target_rows.iterrows()\n    ]\n    caption = \"\\n\".join(caption_lines)\n\n    # Plot\n    plt.subplot(n_rows, n_cols, idx)\n    plt.imshow(img)\n    plt.axis(\"off\")\n    plt.title(caption, fontsize=9)\n\nplt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:46:57.984683Z","iopub.execute_input":"2025-11-28T20:46:57.984941Z","iopub.status.idle":"2025-11-28T20:47:02.894432Z","shell.execute_reply.started":"2025-11-28T20:46:57.984923Z","shell.execute_reply":"2025-11-28T20:47:02.893653Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# List to store image sizes\nsizes = []\ntrain_path = f\"{cfg.base_path}/train\"\n\n# Walk through all files in the folder\nfor root, dirs, files in os.walk(train_path):\n    for file in files:\n        # Check for image extensions\n        if file.lower().endswith((\".jpg\", \".jpeg\", \".png\")):\n            # Open the image (using cfg.train_path for consistency with user code)\n            img = Image.open(f\"{train_path}/{file}\")\n            # Append image size as (width, height)\n            sizes.append(img.size)\n\n# Count how many times each size appears\nsize_counts = Counter(sizes)\n\nprint(f\"📊 Total number of images: {len(sizes)}\")\nprint(f\"🧩 Number of unique sizes: {len(size_counts)}\\n\")\n\n# Show the 10 most common sizes\nprint(\"🖼️ Most common sizes:\")\nfor size, count in size_counts.most_common(10):\n    print(f\"  {size} → {count} images\")\n\n# If you want extra statistics:\nwidths = [w for w, h in sizes]\nheights = [h for w, h in sizes]\n\nprint(\"\\n📐 Overall statistics:\")\nprint(f\"  Average width: {sum(widths)/len(widths):.1f}\")\nprint(f\"  Average height: {sum(heights)/len(heights):.1f}\")\nprint(f\"  Max width: {max(widths)}, Max height: {max(heights)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:02.895290Z","iopub.execute_input":"2025-11-28T20:47:02.895519Z","iopub.status.idle":"2025-11-28T20:47:11.346829Z","shell.execute_reply.started":"2025-11-28T20:47:02.895503Z","shell.execute_reply":"2025-11-28T20:47:11.346170Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🟠 **Preparation**","metadata":{}},{"cell_type":"code","source":"df_train.isna().sum()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.347594Z","iopub.execute_input":"2025-11-28T20:47:11.347819Z","iopub.status.idle":"2025-11-28T20:47:11.354750Z","shell.execute_reply.started":"2025-11-28T20:47:11.347801Z","shell.execute_reply":"2025-11-28T20:47:11.354178Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Assume df_train is the main training dataframe (with columns: target_name, target)\n# Compute mean and std for each target group\nstats = df_train.groupby(\"target_name\")[\"target\"].agg([\"mean\", \"std\"]).reset_index()\ndisplay(stats)\n\n# Convert mean and std values into dictionaries for easy lookup\ntarget_mean = dict(zip(stats.target_name, stats[\"mean\"]))\ntarget_std  = dict(zip(stats.target_name, stats[\"std\"]))\nprint(target_mean, target_std, sep=\"\\n\\n\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.355545Z","iopub.execute_input":"2025-11-28T20:47:11.355793Z","iopub.status.idle":"2025-11-28T20:47:11.384495Z","shell.execute_reply.started":"2025-11-28T20:47:11.355771Z","shell.execute_reply":"2025-11-28T20:47:11.383880Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 🟠 **Custom Dataset**","metadata":{}},{"cell_type":"code","source":"class BiomassDataset(Dataset):\n    def __init__(self, df, img_root, transforms=None, img_size=512,\n                 target_mean=None, target_std=None, submission=False):\n        self.df = df\n        self.image_files = df.image_path.unique()\n        self.target_names = sorted(df.target_name.unique())\n        self.img_root = img_root\n        self.transforms = transforms\n        self.img_size = img_size\n        self.target_mean = target_mean\n        self.target_std = target_std\n        self.submission = submission\n\n    def __len__(self):\n        return len(self.image_files)\n\n    def __getitem__(self, idx):\n        img_id = self.image_files[idx]\n        # Filter dataframe rows belonging to this image\n        rows = self.df[self.df.image_path == img_id]\n\n        # Build full image path\n        img_path = os.path.join(self.img_root, rows.iloc[0][\"image_path\"])\n        \n        # Load image and convert to RGB\n        img = Image.open(img_path).convert(\"RGB\")\n\n        # Apply transforms (e.g., resizing, normalization)\n        img = self.transforms(img)\n\n        if self.submission:\n            ids = [item for item in rows[\"sample_id\"].values]\n            return img, ids\n\n        # Convert target vector to a torch tensor\n        mean = np.array(list(target_mean.values()))\n        std = np.array(list(target_std.values()))\n        targets = torch.FloatTensor((rows.target.values - mean) / std)\n        \n        return img, targets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.385513Z","iopub.execute_input":"2025-11-28T20:47:11.385755Z","iopub.status.idle":"2025-11-28T20:47:11.392333Z","shell.execute_reply.started":"2025-11-28T20:47:11.385734Z","shell.execute_reply":"2025-11-28T20:47:11.391857Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"weights = models.EfficientNet_B3_Weights.IMAGENET1K_V1\nmean = weights.transforms().mean\nstd = weights.transforms().std\nprint(mean, std)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.393119Z","iopub.execute_input":"2025-11-28T20:47:11.393450Z","iopub.status.idle":"2025-11-28T20:47:11.407052Z","shell.execute_reply.started":"2025-11-28T20:47:11.393427Z","shell.execute_reply":"2025-11-28T20:47:11.406327Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_transforms(img_size, mean, std, train=True):\n    if train:\n        return T.Compose([\n            T.Resize((img_size, img_size)),\n            T.RandomHorizontalFlip(p=0.5),\n            T.RandomVerticalFlip(p=0.2),\n            T.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.02),\n            T.RandomRotation(15),\n            T.ToTensor(),\n            T.Normalize(mean=mean, std=std),\n        ])\n    else:\n        return T.Compose([\n            T.Resize((img_size, img_size)),\n            T.ToTensor(),\n            T.Normalize(mean=mean, std=std),\n        ])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.407802Z","iopub.execute_input":"2025-11-28T20:47:11.408048Z","iopub.status.idle":"2025-11-28T20:47:11.420982Z","shell.execute_reply.started":"2025-11-28T20:47:11.408027Z","shell.execute_reply":"2025-11-28T20:47:11.420330Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train_tfms = get_transforms(cfg.img_size, mean, std, train=True)\n# valid_tfms = get_transforms(cfg.img_size, mean, std, train=False)\n\n# test_set = BiomassDataset(df_train, cfg.base_path, valid_tfms, cfg.img_size, target_mean, target_std, submission=False)\n# img, targets = test_set[0]\n# img.min(), img.max(), targets","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.421771Z","iopub.execute_input":"2025-11-28T20:47:11.422024Z","iopub.status.idle":"2025-11-28T20:47:11.436264Z","shell.execute_reply.started":"2025-11-28T20:47:11.422001Z","shell.execute_reply":"2025-11-28T20:47:11.435587Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def make_folds(df, n_splits=5, seed=42):\n    # Make image paths unique\n    image_files = df[\"image_path\"].unique()\n    kf = KFold(n_splits=n_splits, shuffle=True, random_state=seed)\n\n    # Dictionary to store the fold index for each image\n    fold_map = {}\n    for fold, (train_idx, val_idx) in enumerate(kf.split(image_files)):\n        for i in val_idx:\n            fold_map[image_files[i]] = fold\n\n    # Add the fold column to the main dataframe\n    df[\"fold\"] = df[\"image_path\"].map(fold_map)\n    return df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.437032Z","iopub.execute_input":"2025-11-28T20:47:11.437665Z","iopub.status.idle":"2025-11-28T20:47:11.448928Z","shell.execute_reply.started":"2025-11-28T20:47:11.437642Z","shell.execute_reply":"2025-11-28T20:47:11.448169Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_dataloaders(df, fold, img_path, transforms_train, transforms_valid, target_mean, target_std, img_size, batch_size):\n    # Split the dataframe into train/validation based on the fold index\n    train_df = df[df.fold != fold].reset_index(drop=True)\n    valid_df = df[df.fold == fold].reset_index(drop=True)\n\n    train_set = BiomassDataset(train_df, img_path, transforms_train, img_size, target_mean, target_std)\n    valid_set = BiomassDataset(valid_df, img_path, transforms_valid, img_size, target_mean, target_std)\n\n    train_loader = DataLoader(train_set, batch_size=batch_size, shuffle=True)\n    valid_loader = DataLoader(valid_set, batch_size=batch_size, shuffle=False)\n    return train_loader, valid_loader","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.450363Z","iopub.execute_input":"2025-11-28T20:47:11.450561Z","iopub.status.idle":"2025-11-28T20:47:11.461900Z","shell.execute_reply.started":"2025-11-28T20:47:11.450546Z","shell.execute_reply":"2025-11-28T20:47:11.461211Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# train_tfms = get_transforms(IMG_SIZE, train=True)\n# valid_tfms = get_transforms(IMG_SIZE, train=False)\n\n# for fold in range(5):\n#     print(f\"\\n========== Fold {fold} ==========\")\n#     train_dl, valid_dl = get_dataloaders(\n#         df_folds, fold, img_root=BASE_PATH,\n#         transforms_train=train_tfms, transforms_valid=valid_tfms,\n#         target_mean=target_mean, target_std=target_std,\n#         img_size=IMG_SIZE, batch_size=BATCH_SIZE\n#     )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.463216Z","iopub.execute_input":"2025-11-28T20:47:11.463446Z","iopub.status.idle":"2025-11-28T20:47:11.476550Z","shell.execute_reply.started":"2025-11-28T20:47:11.463431Z","shell.execute_reply":"2025-11-28T20:47:11.475938Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🔴 **Model**","metadata":{}},{"cell_type":"code","source":"class RegModel(nn.Module):\n    def __init__(self, backbone_name=\"efficientnet_b0\", n_targets=5, pretrained=True):\n        super().__init__()\n\n        # Mapping of model name → (constructor, weights enum)\n        backbone_map = {\n            \"efficientnet_b0\": (models.efficientnet_b0, models.EfficientNet_B0_Weights),\n            \"efficientnet_b1\": (models.efficientnet_b1, models.EfficientNet_B1_Weights),\n            \"efficientnet_b2\": (models.efficientnet_b2, models.EfficientNet_B2_Weights),\n            \"efficientnet_b3\": (models.efficientnet_b3, models.EfficientNet_B3_Weights),\n            \"efficientnet_b4\": (models.efficientnet_b4, models.EfficientNet_B4_Weights),\n        }\n\n        # Validate name\n        if backbone_name not in backbone_map:\n            raise ValueError(f\"Backbone '{backbone_name}' not supported.\")\n\n        constructor, weights_enum = backbone_map[backbone_name]\n\n        # Load with or without pretrained weights\n        backbone = constructor(\n            weights=weights_enum.DEFAULT if pretrained else None\n        )\n\n        # Extract feature dim\n        feat_dim = backbone.classifier[1].in_features\n\n        # Replace classifier with your regression head\n        backbone.classifier = nn.Sequential(\n            nn.Dropout(0.2),\n            nn.Linear(feat_dim, n_targets)\n        )\n\n        self.backbone = backbone\n\n    def forward(self, x):\n        return self.backbone(x)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.477134Z","iopub.execute_input":"2025-11-28T20:47:11.477331Z","iopub.status.idle":"2025-11-28T20:47:11.485643Z","shell.execute_reply.started":"2025-11-28T20:47:11.477317Z","shell.execute_reply":"2025-11-28T20:47:11.484937Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🔴 **Train**","metadata":{}},{"cell_type":"code","source":"torch.manual_seed(1337)\nnp.random.seed(1337)\nrandom.seed(1337)\ntorch.backends.cudnn.deterministic = True\ntorch.backends.cudnn.benchmark = False","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.486341Z","iopub.execute_input":"2025-11-28T20:47:11.486566Z","iopub.status.idle":"2025-11-28T20:47:11.505716Z","shell.execute_reply.started":"2025-11-28T20:47:11.486543Z","shell.execute_reply":"2025-11-28T20:47:11.505060Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cfg = Config(\n    base_path = \"/kaggle/input/csiro-biomass\",\n    backbone=\"efficientnet_b1\",\n    train = False\n)\npprint(cfg)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.506451Z","iopub.execute_input":"2025-11-28T20:47:11.506777Z","iopub.status.idle":"2025-11-28T20:47:11.511431Z","shell.execute_reply.started":"2025-11-28T20:47:11.506748Z","shell.execute_reply":"2025-11-28T20:47:11.510685Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_tfms = get_transforms(cfg.img_size, mean, std, train=True)\nvalid_tfms = get_transforms(cfg.img_size, mean, std, train=False)\n\ndf_train = make_folds(df_train, n_splits=5, seed=42)\n\nhistory = {}\n\nif cfg.train:\n    for fold in range(cfg.n_folds):\n        print(f\"➱ Fold {fold}\", 50*\"-\")\n\n        history[fold] = {\n            \"train_loss\": [],\n            \"valid_loss\": [],\n            \"train_metric\": [],\n            \"valid_metric\": [],\n        }\n        \n        train_loader, valid_loader = get_dataloaders(\n            df_train, fold, img_path=cfg.base_path,\n            transforms_train=train_tfms, transforms_valid=valid_tfms,\n            target_mean=target_mean, target_std=target_std,\n            img_size=cfg.img_size, batch_size=cfg.batch_size\n        )\n    \n        model = RegModel(cfg.backbone, n_targets=5)\n        model = model.to(cfg.device)\n    \n        optimizer = torch.optim.AdamW(model.parameters(), lr=cfg.learning_rate, weight_decay=cfg.weight_decay)\n        loss_fn = nn.MSELoss()\n    \n        best_loss = np.inf\n        for epoch in range(cfg.epochs):\n            train_loss, train_metric = train_one_epoch(model, train_loader, loss_fn, optimizer)\n            valid_loss, valid_metric = evaluate(model, valid_loader, loss_fn)\n\n            # Save loss/metrics for this epoch and fold\n            history[fold][\"train_loss\"].append(train_loss)\n            history[fold][\"valid_loss\"].append(valid_loss)\n            history[fold][\"train_metric\"].append(train_metric)\n            history[fold][\"valid_metric\"].append(valid_metric)\n    \n            print(f\"🔹 Fold {fold} ‖ Epoch {epoch} ‖ train_loss={train_loss:.4f} ‖ train_metric={train_metric:.4f}\")\n            print(f\"🔹 Fold {fold} ‖ Epoch {epoch} ‖ valid_loss={valid_loss:.4f} ‖ valid_metric={valid_metric:.4f}\")\n    \n            if valid_loss < best_loss:\n                best_loss = valid_loss\n                torch.save(model.state_dict(), f\"best_fold{fold}.pt\")\n                print(\"✅ Model Saved!\")\n            print()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.512171Z","iopub.execute_input":"2025-11-28T20:47:11.512424Z","iopub.status.idle":"2025-11-28T20:47:11.527046Z","shell.execute_reply.started":"2025-11-28T20:47:11.512403Z","shell.execute_reply":"2025-11-28T20:47:11.526373Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🟠 Plot","metadata":{}},{"cell_type":"code","source":"if cfg.train:\n    num_folds = len(history)\n    fig, axes = plt.subplots(num_folds, 2, figsize=(12, 4 * num_folds))\n    \n    # Make the axes array consistent even if num_folds = 1\n    if num_folds == 1:\n        axes = axes.reshape(1, -1)\n    \n    for idx, (fold_name, metrics) in enumerate(history.items()):\n        train_loss = metrics[\"train_loss\"]\n        valid_loss = metrics[\"valid_loss\"]\n        train_metric = metrics[\"train_metric\"]\n        valid_metric = metrics[\"valid_metric\"]\n    \n        epochs = range(1, len(train_loss) + 1)\n    \n        # Loss subplot\n        ax_loss = axes[idx, 0]\n        ax_loss.plot(epochs, train_loss, label=\"Train\")\n        ax_loss.plot(epochs, valid_loss, label=\"Valid\")\n        ax_loss.set_title(f\"Fold {fold_name} ‖ Loss\")\n        ax_loss.set_xlabel(\"Epoch\")\n        ax_loss.set_ylabel(\"Loss\")\n        ax_loss.legend()\n    \n        # Metric subplot\n        ax_metric = axes[idx, 1]\n        ax_metric.plot(epochs, train_metric, label=\"Train\")\n        ax_metric.plot(epochs, valid_metric, label=\"Validation\")\n        ax_metric.set_title(f\"Fold {fold_name} ‖ Weighted R2 Score\")\n        ax_metric.set_xlabel(\"Epoch\")\n        ax_metric.set_ylabel(\"R2\")\n        ax_metric.legend()\n    \n    plt.tight_layout()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.528040Z","iopub.execute_input":"2025-11-28T20:47:11.528243Z","iopub.status.idle":"2025-11-28T20:47:11.544939Z","shell.execute_reply.started":"2025-11-28T20:47:11.528221Z","shell.execute_reply":"2025-11-28T20:47:11.544392Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 🔴 **Submit**","metadata":{}},{"cell_type":"code","source":"df_test = pd.read_csv(f\"{cfg.base_path}/test.csv\")\ndf_test.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.545792Z","iopub.execute_input":"2025-11-28T20:47:11.546460Z","iopub.status.idle":"2025-11-28T20:47:11.573261Z","shell.execute_reply.started":"2025-11-28T20:47:11.546437Z","shell.execute_reply":"2025-11-28T20:47:11.572621Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_set = BiomassDataset(df_test, cfg.base_path, valid_tfms, cfg.img_size, target_mean, target_std, submission=True)\ntest_loader = DataLoader(test_set, batch_size=1, shuffle=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:11.575783Z","iopub.execute_input":"2025-11-28T20:47:11.576012Z","iopub.status.idle":"2025-11-28T20:47:11.579986Z","shell.execute_reply.started":"2025-11-28T20:47:11.575996Z","shell.execute_reply":"2025-11-28T20:47:11.579309Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_models = []\nfor f in range(5):\n    print(f\"Loading fold {f}...\")\n    model = RegModel(cfg.backbone, n_targets=5, pretrained=False)\n\n    state_dict = torch.load(\n        f\"/kaggle/input/ens5-effnetb1/pytorch/default/1/best_fold{f}.pt\",\n        map_location=cfg.device\n    )\n    model.load_state_dict(state_dict)\n    model.to(cfg.device)\n    model.eval()\n    all_models.append(model)\n\nprint(\"All models loaded.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:47:41.260424Z","iopub.execute_input":"2025-11-28T20:47:41.260687Z","iopub.status.idle":"2025-11-28T20:47:46.377817Z","shell.execute_reply.started":"2025-11-28T20:47:41.260671Z","shell.execute_reply":"2025-11-28T20:47:46.376993Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_preds = []\nall_ids = []\n\nprint(\"Starting inference...\")\n\nwith torch.no_grad():\n    for img, ids in test_loader:\n        img = img.to(cfg.device)\n        ids = [item[0] for item in ids]\n\n        # Accumulator for 5 folds\n        fold_preds = []\n        for model in all_models:\n            p = model(img)  # shape: (batch, 5)\n            p = p.squeeze().cpu().numpy()\n            fold_preds.append(p)\n\n        # Stack and average along fold dimension\n        # shape example: (5 folds, batch_size, 5 targets)\n        fold_preds = np.stack(fold_preds, axis=0)\n        preds = fold_preds.mean(axis=0)  # shape: (batch_size, 5)\n\n        # De-normalize prediction\n        for i, idd in enumerate(ids):\n            target_name = idd.split('__')[1]\n            preds[i] = target_std[target_name] * preds[i] + target_mean[target_name]\n\n        all_preds.extend(preds)\n        all_ids.extend(ids)\n\nprint(\"Inference finished.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:51:39.607861Z","iopub.execute_input":"2025-11-28T20:51:39.608405Z","iopub.status.idle":"2025-11-28T20:51:40.367126Z","shell.execute_reply.started":"2025-11-28T20:51:39.608379Z","shell.execute_reply":"2025-11-28T20:51:40.366171Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df = pd.DataFrame({\n    'sample_id': all_ids,\n    'target': all_preds\n})\nsubmission_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:51:43.208299Z","iopub.execute_input":"2025-11-28T20:51:43.208570Z","iopub.status.idle":"2025-11-28T20:51:43.216831Z","shell.execute_reply.started":"2025-11-28T20:51:43.208552Z","shell.execute_reply":"2025-11-28T20:51:43.216075Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"submission_df.to_csv('submission.csv', index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-28T20:51:44.939652Z","iopub.execute_input":"2025-11-28T20:51:44.940276Z","iopub.status.idle":"2025-11-28T20:51:44.948211Z","shell.execute_reply.started":"2025-11-28T20:51:44.940250Z","shell.execute_reply":"2025-11-28T20:51:44.947558Z"}},"outputs":[],"execution_count":null}]}