{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":632794,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":477050,"modelId":492985},{"sourceId":646491,"sourceType":"modelInstanceVersion","isSourceIdPinned":false,"modelInstanceId":487568,"modelId":502978}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport json\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\n# from sklearn.model_selection import GroupKFold\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom datetime import date\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\n# from sklearn.preprocessing import RobustScaler\nimport timm\nfrom tqdm import tqdm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n# from sklearn.preprocessing import StandardScaler\nfrom torch.optim.lr_scheduler import CosineAnnealingWarmRestarts","metadata":{"_uuid":"3c20f29d-6526-461a-b369-8958e0c8873a","_cell_guid":"698f3327-fc91-4776-88eb-a67bdb4f11b5","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-11-15T17:28:46.021128Z","iopub.execute_input":"2025-11-15T17:28:46.021737Z","iopub.status.idle":"2025-11-15T17:29:23.272812Z","shell.execute_reply.started":"2025-11-15T17:28:46.021713Z","shell.execute_reply":"2025-11-15T17:29:23.272031Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## METRIC: Weighted R²","metadata":{"_uuid":"ec9058d1-6452-42d7-8745-1a1ebbd22ad8","_cell_guid":"9ce79386-67f3-4d64-83ff-116595e5fd1e","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"def weighted_r2(y_true, y_pred):\n    scores = []\n    for i in range(y_true.shape[1]):\n        ss_res = np.sum((y_true[:, i] - y_pred[:, i]) ** 2)\n        ss_tot = np.sum((y_true[:, i] - np.mean(y_true[:, i])) ** 2)\n        r2 = 1 - ss_res / (ss_tot + 1e-8)\n        scores.append(r2)\n    return np.mean(scores)\n\ndef log_transform(x):\n    return np.log1p(x)\n\ndef inv_log_transform(x):\n    return np.expm1(x)","metadata":{"_uuid":"917fe19d-26db-4625-a432-b76b053611c1","_cell_guid":"f774a16b-f045-4dc5-9a06-ec017c42e6f5","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 1️⃣ CONFIG","metadata":{}},{"cell_type":"code","source":"ROOT_DIR = \"/kaggle/input/csiro-biomass\"\nLR = 1e-4\nNUM_WORKERS = 4\nDEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nTARGET_ORDER = [\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"Dry_Total_g\", \"GDM_g\"]\nmeta_cols = [\"Pre_GSHH_NDVI\", \"Height_Ave_cm\"]","metadata":{"_uuid":"25b3c433-edb7-424e-b2f2-65d780bf4af5","_cell_guid":"8f18ca31-5995-44fc-859c-2bbd4d94d939","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-11-15T17:30:48.765700Z","iopub.execute_input":"2025-11-15T17:30:48.766111Z","iopub.status.idle":"2025-11-15T17:30:48.826399Z","shell.execute_reply.started":"2025-11-15T17:30:48.766083Z","shell.execute_reply":"2025-11-15T17:30:48.825581Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 2️⃣ DATA PREP","metadata":{}},{"cell_type":"code","source":"train_data = pd.read_csv(f\"{ROOT_DIR}/train.csv\")\ntrain_data.head(5)","metadata":{"_uuid":"7d397f1a-48c1-4af1-9dff-ca3ed775f28a","_cell_guid":"e915c57d-700c-4a67-84cf-2711a86f65af","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"fig, axes = plt.subplots(3, 1, figsize=(8, 15))\n\nfor image in range(3):\n    idx = image * 5\n    image_path = train_data.image_path[idx]\n    img = np.array(Image.open(f'{ROOT_DIR}/{image_path}').convert(\"RGB\"))\n    axes[image].imshow(img)\n    # Display targets and other data in a multi-line title\n    title = f\"image_path: {train_data['image_path'][idx]}\\n\"\n    title += f\"Species: {train_data['Species'][idx]}\\n\"\n    title += f\"NDVI: {train_data['Pre_GSHH_NDVI'][idx]}\\n\"\n    title += f\"Height: {train_data['Height_Ave_cm'][idx]}\\n\"\n    title += f\"Target: {dict(zip(train_data['target_name'][idx:idx+5], train_data['target'][idx:idx+5]))}\"\n\n    axes[image].set_title(title, fontsize=10)\n    axes[image].axis('off')\n    idx += 5\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"images = list(train_data.image_path.unique())\n\ntrainset = {'image': [], 'target':[]}\n\nfor num in range(len(images)):\n    idx = num * 5\n    image_path = train_data.image_path[idx]\n    # print(image_path)\n    # print(torch.tensor(train_data.target[idx:idx+5].values, dtype=torch.float32))\n    trainset['image'].append(image_path)\n    trainset['target'].append(torch.tensor(train_data.target[idx:idx+5].values, dtype=torch.float32))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"traindata = pd.DataFrame(trainset, columns=trainset.keys())\ntraindata","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# split into train/val\ntrain_df, val_df = train_test_split(traindata, test_size=0.2, random_state=42)\nprint('shape of train:--', train_df.shape)\nprint('shape of validation:--', val_df.shape)","metadata":{"_uuid":"22812eff-cf33-4b45-99f7-696890f39faf","_cell_guid":"724ae066-b667-4661-98b5-95af6204884a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.preprocessing import StandardScaler\nscaler = StandardScaler()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 3️⃣ TRANSFORMS","metadata":{}},{"cell_type":"code","source":"train_transforms = A.Compose([\n    A.RandomResizedCrop(size=([224, 224]), scale=([0.8, 1.0])),  # fixed scale range and tuple syntax\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.Normalize(mean=(0.485, 0.456, 0.406),\n                std=(0.229, 0.224, 0.225)),\n    ToTensorV2(),\n])\n\nvalid_transforms = A.Compose([\n    A.Resize(width=224, height=224),\n    A.Normalize(mean=(0.485, 0.456, 0.406),\n                std=(0.229, 0.224, 0.225)),\n    ToTensorV2(),\n])","metadata":{"_uuid":"cff0af06-5397-441b-9c68-7b2c4008bdce","_cell_guid":"9713730d-18a1-4c57-910e-02336e3a5cf8","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-11-15T17:34:00.970859Z","iopub.execute_input":"2025-11-15T17:34:00.971161Z","iopub.status.idle":"2025-11-15T17:34:00.981045Z","shell.execute_reply.started":"2025-11-15T17:34:00.971138Z","shell.execute_reply":"2025-11-15T17:34:00.980473Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 4️⃣ DATASET","metadata":{}},{"cell_type":"code","source":"class PastureDataset(Dataset):\n    def __init__(self, df, root_dir, transforms, scaler=None, is_test=False):\n        self.df = df.reset_index(drop=True)\n        self.root_dir = root_dir\n        self.transforms = transforms\n        self.scaler = scaler\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        if self.is_test:\n            img_path = os.path.join(self.root_dir, row[\"image_path\"])\n            image = np.array(Image.open(img_path).convert(\"RGB\"))\n            image = self.transforms(image=image)[\"image\"]\n            return {\"image\": image}\n        img_path = os.path.join(self.root_dir, row[\"image\"])\n        image = np.array(Image.open(img_path).convert(\"RGB\"))\n        image = self.transforms(image=image)[\"image\"]\n\n        \n\n        return {\"image\": image, \"target\": row[\"target\"]}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-15T17:34:05.098928Z","iopub.execute_input":"2025-11-15T17:34:05.099519Z","iopub.status.idle":"2025-11-15T17:34:05.105527Z","shell.execute_reply.started":"2025-11-15T17:34:05.099492Z","shell.execute_reply":"2025-11-15T17:34:05.104658Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 5️⃣ DATALOADERS","metadata":{}},{"cell_type":"code","source":"BATCH_SIZE = 8\ntrain_ds = PastureDataset(train_df, ROOT_DIR, train_transforms, scaler)\nval_ds = PastureDataset(val_df, ROOT_DIR, valid_transforms, scaler)\n\ntrain_loader = DataLoader(train_ds, batch_size=BATCH_SIZE, shuffle=True, num_workers=NUM_WORKERS)\nval_loader = DataLoader(val_ds, batch_size=BATCH_SIZE, shuffle=False, num_workers=NUM_WORKERS)","metadata":{"_uuid":"fa7b2bfe-9b3e-4cf6-8b8b-73bbae6546d7","_cell_guid":"6631e5d8-8e83-42ae-8b8d-9e3053a6194e","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for batch in train_loader:\n    print(\"TRAIN BATCH:\")\n    print(\" image:\", batch[\"image\"].shape)\n    print(\" target:\", batch[\"target\"].shape)\n    break\n\nfor batch in val_loader:\n    print(\"VAL BATCH:\")\n    print(\" image:\", batch[\"image\"].shape)\n    print(\" target:\", batch[\"target\"].shape)\n    break","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 6️⃣ MODEL","metadata":{}},{"cell_type":"code","source":"class BiomassModel(nn.Module):\n    \"\"\"\n    EfficientNet-based regressor for CSIRO Image2Biomass.\n    Image-only model (no metadata).\n    \"\"\"\n    def __init__(self):\n        super().__init__()\n        \n        # EfficientNet backbone\n        self.backbone = timm.create_model(\n            \"tf_efficientnet_b0\",\n            pretrained=False,\n            num_classes=0,\n            global_pool=\"avg\"\n        )\n\n        # Load EfficientNet weights offline\n        model_path = \"/kaggle/input/efficientnet-model-weight/pytorch/default/1\"\n        state_dict = torch.load(f\"{model_path}/tf_efficientnet_b0_ra-3dd342df.pth\")\n        self.backbone.load_state_dict(state_dict, strict=False)\n\n        feat_dim = self.backbone.num_features\n\n        # Regression head\n        self.regressor = nn.Sequential(\n            nn.Linear(feat_dim, 512),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.3),\n            nn.Linear(512, 128),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.3),\n            nn.Linear(128, len(TARGET_ORDER))  # → 5 outputs\n        )\n\n    def forward(self, x):\n        feat = self.backbone(x)        # (B, 1280)\n        out = self.regressor(feat)     # (B, 5)\n        return out","metadata":{"_uuid":"1c0e28c9-0ffe-4552-8734-4085dd29a9f4","_cell_guid":"4ceba4ec-b783-4115-997b-0ac9bce7b537","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-11-15T17:30:25.987681Z","iopub.execute_input":"2025-11-15T17:30:25.987925Z","iopub.status.idle":"2025-11-15T17:30:25.994083Z","shell.execute_reply.started":"2025-11-15T17:30:25.987908Z","shell.execute_reply":"2025-11-15T17:30:25.993267Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = BiomassModel().to(DEVICE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-15T17:31:28.853454Z","iopub.execute_input":"2025-11-15T17:31:28.853737Z","iopub.status.idle":"2025-11-15T17:31:29.157872Z","shell.execute_reply.started":"2025-11-15T17:31:28.853716Z","shell.execute_reply":"2025-11-15T17:31:29.157234Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 7️⃣ LOSS & OPTIMIZER","metadata":{}},{"cell_type":"code","source":"mse_loss = nn.MSELoss()\nl1_loss = nn.L1Loss()\nSmoothL1Loss = nn.SmoothL1Loss()\n\n# optimizer = torch.optim.AdamW(model.parameters(), lr=LR, weight_decay=1e-4)\noptimizer = torch.optim.RAdam(model.parameters(), lr=1e-4, weight_decay=1e-4)\n# # optimizer = torch.optim.AdamW(model.parameters())\nscheduler = CosineAnnealingWarmRestarts(optimizer, T_0=5, T_mult=2, eta_min=1e-6)\n# scheduler = CosineAnnealingWarmRestarts(optimizer, T_0=5)\n\nscaler_amp = torch.amp.GradScaler('cuda', enabled=(DEVICE == \"cuda\"))\nbest_val_score = -999\nbest_state = None","metadata":{"_uuid":"8e71e37d-987f-43fe-a9a5-481ea2767cfb","_cell_guid":"2f7a3cff-cb4e-43dd-b3ba-3a110735b59d","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## To retrain model run this block","metadata":{}},{"cell_type":"markdown","source":"### 8️⃣ TRAINING LOOP","metadata":{}},{"cell_type":"code","source":"model_path = '/kaggle/input/final-efficient-net-model/pytorch/final/1/biomass_efficientnet_15_11.pt'\nmodel.load_state_dict(torch.load(model_path, map_location=DEVICE))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-15T17:31:32.859776Z","iopub.execute_input":"2025-11-15T17:31:32.860022Z","iopub.status.idle":"2025-11-15T17:31:32.960257Z","shell.execute_reply.started":"2025-11-15T17:31:32.860005Z","shell.execute_reply":"2025-11-15T17:31:32.959484Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"EPOCHS = 5\nfor epoch in range(EPOCHS):\n    model.train()\n    running_loss = 0.0\n    pbar = tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS}\")\n    \n    for batch in pbar:\n        imgs = batch[\"image\"].float().to(DEVICE)\n        targets = batch[\"target\"].float().to(DEVICE)\n\n        optimizer.zero_grad(set_to_none=True)\n\n        with torch.amp.autocast('cuda', enabled=(DEVICE == \"cuda\")):\n            preds = model(imgs)\n            loss = 0.6 * SmoothL1Loss(preds, targets) + 0.4 * mse_loss(preds, targets)\n\n        scaler_amp.scale(loss).backward()\n        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)\n        scaler_amp.step(optimizer)\n        scaler_amp.update()\n        running_loss += loss.item() * imgs.size(0)\n        pbar.set_postfix(loss=loss.item())\n\n    scheduler.step()\n    train_loss = running_loss / len(train_loader.dataset)\n\n    # --- validation ---\n    model.eval()\n    val_preds, val_trues = [], []\n    with torch.no_grad(), torch.amp.autocast('cuda', enabled=(DEVICE == \"cuda\")):\n        for batch in val_loader:\n            imgs = batch[\"image\"].float().to(DEVICE)\n            targets = batch[\"target\"].cpu().numpy()\n            preds = model(imgs).cpu().numpy()\n            val_preds.append(preds)\n            val_trues.append(targets)\n\n    val_preds = np.vstack(val_preds)\n    val_trues = np.vstack(val_trues)\n\n    val_score = weighted_r2(val_trues, val_preds)  # your custom metric\n\n    print(f\"Epoch {epoch+1}/{EPOCHS} - train_loss: {train_loss:.5f}  val_weighted_r2: {val_score:.5f}\")\n\n    # Save best weights\n    if val_score > best_val_score:\n        best_val_score = val_score\n        train_loss = train_loss\n        best_state = model.state_dict().copy()\n\nprint(f\"✅ Training Complete — train_loss: {train_loss:.5f} and Best val Weighted R²: {best_val_score:.5f}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"metric = {\n    'best_train_loss': f'{train_loss:.5f}',\n    'Best_val_Weighted_R²': f'{val_score:.5f}',\n}","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# with open(\"/kaggle/working/best_metric.json\", \"w\") as f:\n#     json.dump(metric, f, indent=4)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### 9️⃣ SAVING MODEL WEIGHTS(MANUALLY)","metadata":{}},{"cell_type":"code","source":"# torch.save(best_state, '/kaggle/working/biomass_efficientnet_15_11.pt')\n# print(\"✅ Model saved to biomass_model_simple.pt\")","metadata":{"_uuid":"9332aa4a-0ae9-4e21-b834-0c6e6da81db0","_cell_guid":"56f1fe8a-fe27-47ce-9842-3ce91cf63ddf","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Build test dataframe","metadata":{"_uuid":"5152b053-3aae-4bf1-93a0-a0684139f4a3","_cell_guid":"43b27513-df0d-4198-8a3b-5ea6fba9b6f2","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# Load test data\ntest_df = pd.read_csv(\"/kaggle/input/csiro-biomass/test.csv\")\n\n# Ensure unique images\ntest_images = test_df.drop_duplicates(\"sample_id\").reset_index(drop=True)\ntest_images","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-15T17:31:38.867951Z","iopub.execute_input":"2025-11-15T17:31:38.868227Z","iopub.status.idle":"2025-11-15T17:31:38.882498Z","shell.execute_reply.started":"2025-11-15T17:31:38.868209Z","shell.execute_reply":"2025-11-15T17:31:38.881783Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"test_ds = PastureDataset(test_images, ROOT_DIR, valid_transforms, is_test=True)\ntest_loader = DataLoader(test_ds, batch_size=8, shuffle=True, num_workers=4)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-15T17:34:56.690329Z","iopub.execute_input":"2025-11-15T17:34:56.691066Z","iopub.status.idle":"2025-11-15T17:34:56.695377Z","shell.execute_reply.started":"2025-11-15T17:34:56.691039Z","shell.execute_reply":"2025-11-15T17:34:56.694500Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.eval()\nval_preds = []\nwith torch.no_grad():\n    for batch in test_loader:\n        imgs = batch[\"image\"].to(DEVICE)\n        with torch.amp.autocast('cuda', enabled=(DEVICE == \"cuda\")):\n            preds = model(imgs).cpu().numpy()\n        val_preds.append(preds)\n\nval_preds = np.hstack(val_preds)","metadata":{"_uuid":"13dd53a2-c4ee-4773-89dc-b20cd250a704","_cell_guid":"0eb551cd-4c0c-4c91-ac7d-5f0ef28f838e","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2025-11-15T17:35:01.324837Z","iopub.execute_input":"2025-11-15T17:35:01.325093Z","iopub.status.idle":"2025-11-15T17:35:02.078227Z","shell.execute_reply.started":"2025-11-15T17:35:01.325075Z","shell.execute_reply":"2025-11-15T17:35:02.077216Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# # Average predictions across folds\ntest_preds = np.mean(val_preds, axis=0)  # shape: (num_test_samples, 5)\ntest_images['target'] = test_preds\nsubmission = test_images[['sample_id', 'target']]\nsubmission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-15T17:37:00.230242Z","iopub.execute_input":"2025-11-15T17:37:00.230555Z","iopub.status.idle":"2025-11-15T17:37:00.242727Z","shell.execute_reply.started":"2025-11-15T17:37:00.230532Z","shell.execute_reply":"2025-11-15T17:37:00.242071Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"image = 0\nidx = image * 5\nimage_path = test_images.image_path[idx]\nimg = np.array(Image.open(f'{ROOT_DIR}/{image_path}').convert(\"RGB\"))\nplt.figure(figsize=(12, 8))\nplt.title(f\"Target: {dict(zip(test_images['target_name'][idx:idx+5], test_images['target'][idx:idx+5]))}\")\nplt.imshow(img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-15T17:49:02.581227Z","iopub.execute_input":"2025-11-15T17:49:02.581491Z","iopub.status.idle":"2025-11-15T17:49:03.335381Z","shell.execute_reply.started":"2025-11-15T17:49:02.581472Z","shell.execute_reply":"2025-11-15T17:49:03.334482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def kaggle_safe_submit(func):\n    \"\"\"\n    Decorator:\n    Wrap any function that generates submission.csv.\n    If Kaggle throws an error (IOError, PermissionError, ValueError, etc),\n    it prints a clear message instead of crashing the notebook.\n    \"\"\"\n    def wrapper(*args, **kwargs):\n        try:\n            return func(*args, **kwargs)\n        except Exception as e:\n            print(\"❌ Submission Failed!\")\n            print(\"-----------------------------------\")\n            print(f\"Error Type   : {type(e).__name__}\")\n            print(f\"Error Detail : {e}\")\n            print(\"-----------------------------------\")\n            print(\"⚠️ Action Needed: Fix the error above before submitting.\")\n            return None\n    return wrapper","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"@kaggle_safe_submit\ndef save_submission(submission):\n    submission.to_csv(\"submission.csv\", index=False)\n    print(\"✅ submission.csv saved successfully!\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"save_submission(submission)","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}