{"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":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":14502460,"sourceType":"datasetVersion","datasetId":9262645},{"sourceId":293031629,"sourceType":"kernelVersion"},{"sourceId":268942,"sourceType":"modelInstanceVersion","modelInstanceId":230141,"modelId":251887},{"sourceId":711299,"sourceType":"modelInstanceVersion","modelInstanceId":540408,"modelId":553604},{"sourceId":716648,"sourceType":"modelInstanceVersion","modelInstanceId":544792,"modelId":557864}],"dockerImageVersionId":31236,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport gc\nimport sys\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom pathlib import Path\nfrom tqdm.auto import tqdm\nimport cv2\nimport warnings\n\n# --- FIX 1: OPTIMIZE MEMORY ALLOCATION ---\n# This must be set before torch is imported to handle fragmentation better\nos.environ[\"PYTORCH_CUDA_ALLOC_CONF\"] = \"expandable_segments:True\"\nos.environ[\"ALBUMENTATIONS_CHECK_VERSION\"] = \"false\"\n\nimport torch\nimport torch.nn as nn\nfrom torch.optim import AdamW\nfrom torch.utils.data import Dataset, DataLoader\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nimport timm\nfrom transformers import get_cosine_schedule_with_warmup\nfrom sklearn.model_selection import GroupKFold\nfrom sklearn.metrics import r2_score\n\n# Suppress warnings\nwarnings.filterwarnings('ignore')\n\n# Enable performance optimizations\ntorch.backends.cuda.matmul.allow_tf32 = True\ntorch.backends.cudnn.allow_tf32 = True\ntorch.backends.cudnn.benchmark = True\n\n# =========================================================================================\n# CONFIGURATION\n# =========================================================================================\nclass CFG:\n    DATA_PATH = Path(\"/kaggle/input/csiro-biomass/\")\n    TRAIN_CSV = Path(\"/kaggle/input/csiro-datasplit/csiro_data_split.csv\")\n    TEST_CSV = Path(\"/kaggle/input/csiro-biomass/test.csv\")\n    BACKBONE_WEIGHTS = Path(\"/kaggle/input/csiro-vit-large-dinov3/pytorch/default/1/csiro-dinov3-models/vit_large_patch16_dinov3_qkvb.pth\")\n    \n    MODEL_ARCH = \"vit_large_patch16_224\"\n    IMG_SIZE = 512\n    N_FOLDS = 5\n    SEED = 42\n    \n    # --- MEMORY SETTINGS ---\n    # If you still OOM, change BATCH_SIZE to 1 and GRAD_ACCUM_STEPS to 8\n    BATCH_SIZE = 2         \n    GRAD_ACCUM_STEPS = 4   \n    \n    # --- TRAINING STRATEGY ---\n    WARMUP_EPOCHS = 10      \n    WARMUP_LR = 1e-3       \n    \n    FINETUNE_EPOCHS = 0    \n    FINETUNE_LR = 5e-6     \n    \n    WEIGHT_DECAY = 1e-4\n    MAX_GRAD_NORM = 1.0\n    DEVICE = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    NUM_WORKERS = 4\n    \n    ALL_TARGET_COLS = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    USE_AMP = True\n\ndef seed_everything(seed=42):\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = False\n    torch.backends.cudnn.benchmark = True\n\nseed_everything(CFG.SEED)\n\n# =========================================================================================\n# HELPER: MEMORY CLEANUP\n# =========================================================================================\ndef cleanup():\n    \"\"\"Forcibly frees memory variables and clears GPU cache\"\"\"\n    gc.collect()\n    torch.cuda.empty_cache()\n\n# =========================================================================================\n# DATASET\n# =========================================================================================\ndef clean_image(img):\n    h, w = img.shape[:2]\n    img = img[0:int(h*0.90), :]\n    hsv = cv2.cvtColor(img, cv2.COLOR_RGB2HSV)\n    lower = np.array([5, 150, 150])\n    upper = np.array([25, 255, 255])\n    mask = cv2.inRange(hsv, lower, upper)\n    mask = cv2.dilate(mask, np.ones((3,3), np.uint8), iterations=2)\n    if np.sum(mask) > 0:\n        img = cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA)\n    return img\n\nclass BiomassDataset(Dataset):\n    def __init__(self, df, transform, img_dir, is_train=True):\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n        self.img_dir = img_dir\n        self.is_train = is_train\n        if is_train:\n            self.labels = df[CFG.ALL_TARGET_COLS].values.astype(np.float32)\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        if 'image_path' in row and pd.notna(row['image_path']):\n            img_name = os.path.basename(str(row['image_path']))\n        else:\n            img_name = str(row['sample_id']) + '.jpg'\n        \n        img_path = os.path.join(self.img_dir, img_name)\n        try:\n            img = cv2.imread(img_path)\n            if img is None: raise ValueError\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            img = clean_image(img)\n        except:\n            # Fallback to black image if load fails\n            img = np.zeros((1000, 2000, 3), dtype=np.uint8)\n        \n        h, w = img.shape[:2]\n        mid = w // 2\n        left = self.transform(image=img[:, :mid])['image']\n        right = self.transform(image=img[:, mid:])['image']\n        \n        if self.is_train:\n            return left, right, torch.from_numpy(self.labels[idx])\n        else:\n            return left, right, torch.zeros(5)\n\ndef get_train_transforms():\n    return A.Compose([\n        A.Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        A.HorizontalFlip(p=0.5),\n        A.VerticalFlip(p=0.5),\n        A.RandomRotate90(p=0.5),\n        A.ColorJitter(brightness=0.1, contrast=0.1, saturation=0.1, hue=0.05, p=0.5),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        A.ToTensorV2()\n    ])\n\ndef get_val_transforms():\n    return A.Compose([\n        A.Resize(CFG.IMG_SIZE, CFG.IMG_SIZE),\n        A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),\n        A.ToTensorV2()\n    ])\n\n# =========================================================================================\n# MODEL\n# =========================================================================================\nclass CrossAttentionBlock(nn.Module):\n    def __init__(self, dim, num_heads=8, dropout=0.1):\n        super().__init__()\n        self.mha = nn.MultiheadAttention(dim, num_heads, batch_first=True, dropout=dropout)\n        self.norm1 = nn.LayerNorm(dim)\n        self.norm2 = nn.LayerNorm(dim)\n        self.ffn = nn.Sequential(\n            nn.Linear(dim, dim * 2), nn.GELU(), nn.Dropout(dropout), nn.Linear(dim * 2, dim)\n        )\n    \n    def forward(self, query, key_value):\n        attn_out, _ = self.mha(query, key_value, key_value)\n        x = self.norm1(query + attn_out)\n        x = self.norm2(x + self.ffn(x))\n        return x\n\nclass BiomassModel(nn.Module):\n    def __init__(self, model_name, pretrained=True, backbone_path=None):\n        super().__init__()\n        # Allow 512x512 inputs\n        self.backbone = timm.create_model(\n            model_name, pretrained=False, num_classes=0, global_pool='', dynamic_img_size=True\n        )\n        # Enable Gradient Checkpointing (Crucial for 512px on T4)\n        self.backbone.set_grad_checkpointing(True)\n        \n        if pretrained and backbone_path and os.path.exists(backbone_path):\n            print(f\"Loading weights: {backbone_path}\")\n            try:\n                sd = torch.load(backbone_path, map_location='cpu')\n                if 'teacher' in sd: sd = sd['teacher']\n                elif 'model' in sd: sd = sd['model']\n                elif 'state_dict' in sd: sd = sd['state_dict']\n                sd = {k.replace(\"module.\", \"\"): v for k, v in sd.items()}\n                self.backbone.load_state_dict(sd, strict=False)\n                print(\"✓ Weights loaded\")\n            except Exception as e:\n                print(f\"Warning loading weights: {e}\")\n        \n        nf = self.backbone.num_features\n        self.cross_attn_L2R = CrossAttentionBlock(nf)\n        self.cross_attn_R2L = CrossAttentionBlock(nf)\n        self.pool = nn.AdaptiveAvgPool1d(1)\n        \n        def make_head():\n            return nn.Sequential(\n                nn.Linear(nf * 2, nf // 2), nn.GELU(), nn.Dropout(0.2),\n                nn.Linear(nf // 2, 1), nn.Softplus()\n            )\n        \n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n    \n    def forward(self, left, right):\n        x_l = self.backbone(left)\n        x_r = self.backbone(right)\n        \n        x_l_fused = self.cross_attn_L2R(x_l, x_r)\n        x_r_fused = self.cross_attn_R2L(x_r, x_l)\n        \n        x_l_pool = self.pool(x_l_fused.transpose(1, 2)).flatten(1)\n        x_r_pool = self.pool(x_r_fused.transpose(1, 2)).flatten(1)\n        \n        combined = torch.cat([x_l_pool, x_r_pool], dim=1)\n        \n        green = self.head_green(combined)\n        clover = self.head_clover(combined)\n        dead = self.head_dead(combined)\n        gdm = green + clover\n        total = gdm + dead\n        \n        return total, gdm, green, clover, dead\n\n# =========================================================================================\n# METRIC & LOSS\n# =========================================================================================\ndef weighted_r2_score_global(y_true, y_pred):\n    weights = np.array([0.1, 0.1, 0.1, 0.2, 0.5])\n    r2_scores = []\n    for i in range(y_true.shape[1]):\n        yt, yp = y_true[:, i], y_pred[:, i]\n        ss_res = np.sum((yt - yp) ** 2)\n        ss_tot = np.sum((yt - np.mean(yt)) ** 2)\n        r2_scores.append(1 - ss_res / ss_tot if ss_tot > 0 else 0.0)\n    return np.sum(np.array(r2_scores) * weights), np.array(r2_scores)\n\ndef biomass_loss(outputs, labels):\n    total, gdm, green, clover, dead = outputs\n    criterion = nn.SmoothL1Loss()\n    losses = torch.stack([\n        criterion(green.squeeze(), labels[:, 0]),\n        criterion(dead.squeeze(), labels[:, 1]),\n        criterion(clover.squeeze(), labels[:, 2]),\n        criterion(gdm.squeeze(), labels[:, 3]),\n        criterion(total.squeeze(), labels[:, 4])\n    ])\n    return (losses * torch.tensor([0.1, 0.1, 0.1, 0.2, 0.5], device=losses.device)).sum()\n\n# =========================================================================================\n# TRAIN / VAL LOOPS\n# =========================================================================================\ndef train_one_epoch(model, loader, optimizer, scheduler, device, scaler):\n    model.train()\n    # Ensure frozen backbone stays frozen in train mode (BatchNorm statistics)\n    frozen = not next(model.backbone.parameters()).requires_grad\n    if frozen: \n        model.backbone.eval() \n        \n    total_loss = 0\n    optimizer.zero_grad()\n    \n    for step, (left, right, labels) in enumerate(loader):\n        left, right, labels = left.to(device), right.to(device), labels.to(device)\n        \n        with torch.amp.autocast('cuda', enabled=CFG.USE_AMP):\n            outputs = model(left, right)\n            loss = biomass_loss(outputs, labels) / CFG.GRAD_ACCUM_STEPS\n        \n        scaler.scale(loss).backward()\n        \n        if (step + 1) % CFG.GRAD_ACCUM_STEPS == 0:\n            scaler.unscale_(optimizer)\n            torch.nn.utils.clip_grad_norm_(model.parameters(), CFG.MAX_GRAD_NORM)\n            scaler.step(optimizer)\n            scaler.update()\n            optimizer.zero_grad()\n            if scheduler: scheduler.step()\n            \n        total_loss += loss.item() * CFG.GRAD_ACCUM_STEPS\n        \n    return total_loss / len(loader)\n\n@torch.no_grad()\ndef validate(model, loader, device):\n    model.eval()\n    all_preds, all_labels = [], []\n    total_loss = 0\n    \n    for left, right, labels in loader:\n        left, right, labels = left.to(device), right.to(device), labels.to(device)\n        with torch.amp.autocast('cuda', enabled=CFG.USE_AMP):\n            total, gdm, green, clover, dead = model(left, right)\n            loss = biomass_loss((total, gdm, green, clover, dead), labels)\n            total_loss += loss.item()\n        \n        batch_pred = torch.cat([\n            green.view(-1, 1), dead.view(-1, 1), clover.view(-1, 1), \n            gdm.view(-1, 1), total.view(-1, 1)\n        ], dim=1)\n        all_preds.append(batch_pred.cpu().numpy())\n        all_labels.append(labels.cpu().numpy())\n        \n    y_pred = np.concatenate(all_preds)\n    y_true = np.concatenate(all_labels)\n    score, per_r2 = weighted_r2_score_global(y_true, y_pred)\n    return total_loss / len(loader), score, per_r2\n\n# =========================================================================================\n# MAIN EXECUTION\n# =========================================================================================\ndef run():\n    # --- FIX 2: PRE-RUN CLEANUP ---\n    cleanup()\n    \n    print(\"=\"*70)\n    print(\"CSIRO Biomass - Optimized Freeze/Unfreeze Strategy (Fixed)\")\n    print(f\"Phase 1 (Frozen): {CFG.WARMUP_EPOCHS} epochs | Phase 2 (Unfrozen): {CFG.FINETUNE_EPOCHS} epochs\")\n    print(\"=\"*70)\n    \n    if CFG.TRAIN_CSV.exists():\n        df_raw = pd.read_csv(CFG.TRAIN_CSV)\n    else:\n        df_raw = pd.read_csv(CFG.DATA_PATH / 'train.csv')\n    \n    if 'image_path' not in df_raw.columns:\n        df_raw['image_path'] = df_raw['sample_id'].astype(str) + '.jpg'\n\n    if 'target_name' in df_raw.columns:\n        df_wide = df_raw.pivot(index='image_path', columns='target_name', values='target').reset_index()\n        meta = df_raw[['image_path', 'Sampling_Date']].drop_duplicates() if 'Sampling_Date' in df_raw.columns else None\n        if meta is not None: df_wide = df_wide.merge(meta, on='image_path', how='left')\n    else:\n        df_wide = df_raw\n\n    groups = df_wide['Sampling_Date'] if 'Sampling_Date' in df_wide.columns else df_wide.index // (len(df_wide) // CFG.N_FOLDS)\n    gkf = GroupKFold(n_splits=CFG.N_FOLDS)\n    fold_scores = []\n    \n    for fold, (train_idx, val_idx) in enumerate(gkf.split(df_wide, groups=groups)):\n        # --- FIX 3: PER-FOLD CLEANUP ---\n        cleanup()\n        print(f\"\\n{'='*30} FOLD {fold + 1}/{CFG.N_FOLDS} {'='*30}\")\n        \n        train_ds = BiomassDataset(df_wide.iloc[train_idx], get_train_transforms(), str(CFG.DATA_PATH/'train'), True)\n        val_ds = BiomassDataset(df_wide.iloc[val_idx], get_val_transforms(), str(CFG.DATA_PATH/'train'), True)\n        \n        train_loader = DataLoader(train_ds, CFG.BATCH_SIZE, True, num_workers=CFG.NUM_WORKERS, drop_last=True)\n        val_loader = DataLoader(val_ds, CFG.BATCH_SIZE, False, num_workers=CFG.NUM_WORKERS)\n        \n        model = BiomassModel(CFG.MODEL_ARCH, True, str(CFG.BACKBONE_WEIGHTS)).to(CFG.DEVICE)\n        scaler = torch.amp.GradScaler('cuda', enabled=CFG.USE_AMP)\n        best_score = -float('inf')\n        \n        # --- PHASE 1: FROZEN BACKBONE ---\n        print(f\"\\n--- Phase 1: Frozen Backbone ({CFG.WARMUP_EPOCHS} Epochs) ---\")\n        for param in model.backbone.parameters(): param.requires_grad = False\n        optimizer = AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=CFG.WARMUP_LR, weight_decay=CFG.WEIGHT_DECAY)\n        steps = len(train_loader) * CFG.WARMUP_EPOCHS // CFG.GRAD_ACCUM_STEPS\n        scheduler = get_cosine_schedule_with_warmup(optimizer, int(steps*0.1), steps)\n        \n        for epoch in range(CFG.WARMUP_EPOCHS):\n            t_loss = train_one_epoch(model, train_loader, optimizer, scheduler, CFG.DEVICE, scaler)\n            v_loss, score, _ = validate(model, val_loader, CFG.DEVICE)\n            print(f\"P1 Ep{epoch+1}: TrainLoss={t_loss:.4f} ValLoss={v_loss:.4f} R2={score:.4f}\")\n            \n            if score > best_score:\n                best_score = score\n                torch.save(model.state_dict(), f\"fold_{fold}.pth\")\n\n        # --- PHASE 2: UNFROZEN BACKBONE ---\n        print(f\"\\n--- Phase 2: Unfrozen Backbone ({CFG.FINETUNE_EPOCHS} Epochs) ---\")\n        # Load best model from Phase 1\n        if Path(f\"fold_{fold}.pth\").exists():\n            model.load_state_dict(torch.load(f\"fold_{fold}.pth\"))\n        else:\n            print(\"Warning: No checkpoint found from Phase 1. Continuing with current weights.\")\n            \n        for param in model.backbone.parameters(): param.requires_grad = True\n        \n        # Re-initialize optimizer for full model\n        optimizer = AdamW(model.parameters(), lr=CFG.FINETUNE_LR, weight_decay=CFG.WEIGHT_DECAY)\n        steps = len(train_loader) * CFG.FINETUNE_EPOCHS // CFG.GRAD_ACCUM_STEPS\n        scheduler = get_cosine_schedule_with_warmup(optimizer, int(steps*0.1), steps)\n        \n        for epoch in range(CFG.FINETUNE_EPOCHS):\n            t_loss = train_one_epoch(model, train_loader, optimizer, scheduler, CFG.DEVICE, scaler)\n            v_loss, score, _ = validate(model, val_loader, CFG.DEVICE)\n            print(f\"P2 Ep{epoch+1}: TrainLoss={t_loss:.4f} ValLoss={v_loss:.4f} R2={score:.4f} {'✓' if score > best_score else ''}\")\n            \n            if score > best_score:\n                best_score = score\n                torch.save(model.state_dict(), f\"fold_{fold}.pth\")\n        \n        fold_scores.append(best_score)\n        \n        # --- FIX 4: DELETE VARIABLES TO FREE MEMORY ---\n        del model, optimizer, scheduler, scaler, train_loader, val_loader\n        cleanup()\n\n    print(f\"\\nCV Score: {np.mean(fold_scores):.4f} ± {np.std(fold_scores):.4f}\")\n\n    # --- INFERENCE ---\n    print(\"\\n--- Inference ---\")\n    cleanup() # Clean before inference\n    \n    test_df_raw = pd.read_csv(CFG.TEST_CSV)\n    test_df_unique = test_df_raw.drop_duplicates(subset=['sample_id']).reset_index(drop=True)\n    test_ds = BiomassDataset(test_df_unique, get_val_transforms(), str(CFG.DATA_PATH/'test'), False)\n    test_loader = DataLoader(test_ds, CFG.BATCH_SIZE, False, num_workers=CFG.NUM_WORKERS)\n    \n    all_preds = []\n    for fold in range(CFG.N_FOLDS):\n        if not Path(f\"fold_{fold}.pth\").exists(): continue\n        \n        # Load fresh model for inference\n        model = BiomassModel(CFG.MODEL_ARCH, False).to(CFG.DEVICE)\n        model.load_state_dict(torch.load(f\"fold_{fold}.pth\"))\n        model.eval()\n        \n        preds = []\n        with torch.no_grad():\n            for left, right, _ in tqdm(test_loader, desc=f\"Fold {fold} Inference\"):\n                left, right = left.to(CFG.DEVICE), right.to(CFG.DEVICE)\n                with torch.amp.autocast('cuda', enabled=CFG.USE_AMP):\n                    total, gdm, green, clover, dead = model(left, right)\n                preds.append(torch.cat([\n                    green.view(-1,1), dead.view(-1,1), clover.view(-1,1), \n                    gdm.view(-1,1), total.view(-1,1)\n                ], dim=1).cpu().numpy())\n        \n        all_preds.append(np.concatenate(preds))\n        \n        del model\n        cleanup()\n\n    avg_preds = np.maximum(np.mean(all_preds, axis=0), 0)\n    preds_wide = pd.DataFrame(avg_preds, columns=CFG.ALL_TARGET_COLS)\n    preds_wide.insert(0, 'image_path', test_df_unique['image_path'])\n    preds_long = preds_wide.melt(id_vars=['image_path'], value_vars=CFG.ALL_TARGET_COLS, var_name='target_name', value_name='target')\n    submission = pd.merge(test_df_raw[['sample_id', 'image_path', 'target_name']], preds_long, on=['image_path', 'target_name'], how='left')\n    submission = submission[['sample_id', 'target']].sort_values('sample_id').fillna(0.0)\n    submission.to_csv('submission.csv', index=False)\n    print(\"✓ Submission saved\")\n\nif __name__ == \"__main__\":\n    run()","metadata":{"_uuid":"9f94529e-f86a-4cbc-b888-ddf4a0cdd65e","_cell_guid":"74db7b18-7744-4aaf-be53-e2c88afcce77","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false},"execution":{"iopub.status.busy":"2026-01-21T12:41:16.423523Z","iopub.execute_input":"2026-01-21T12:41:16.424224Z","execution_failed":"2026-01-21T13:59:50.921Z"}},"outputs":[{"name":"stderr","text":"/usr/local/lib/python3.12/dist-packages/albumentations/check_version.py:147: UserWarning: Error fetching version info <urlopen error [Errno -3] Temporary failure in name resolution>\n  data = fetch_version_info()\n/usr/local/lib/python3.12/dist-packages/pydantic/_internal/_generate_schema.py:2249: UnsupportedFieldAttributeWarning: The 'repr' attribute with value False was provided to the `Field()` function, which has no effect in the context it was used. 'repr' is field-specific metadata, and can only be attached to a model field using `Annotated` metadata or by assignment. This may have happened because an `Annotated` type alias using the `type` statement was used, or if the `Field()` function was attached to a single member of a union type.\n  warnings.warn(\n/usr/local/lib/python3.12/dist-packages/pydantic/_internal/_generate_schema.py:2249: UnsupportedFieldAttributeWarning: The 'frozen' attribute with value True was provided to the `Field()` function, which has no effect in the context it was used. 'frozen' is field-specific metadata, and can only be attached to a model field using `Annotated` metadata or by assignment. This may have happened because an `Annotated` type alias using the `type` statement was used, or if the `Field()` function was attached to a single member of a union type.\n  warnings.warn(\n","output_type":"stream"},{"name":"stdout","text":"======================================================================\nCSIRO Biomass - Optimized Freeze/Unfreeze Strategy (Fixed)\nPhase 1 (Frozen): 10 epochs | Phase 2 (Unfrozen): 0 epochs\n======================================================================\n\n============================== FOLD 1/5 ==============================\n\n--- Phase 1: Frozen Backbone (10 Epochs) ---\nP1 Ep1: TrainLoss=20.5817 ValLoss=12.5996 R2=0.0007\nP1 Ep2: TrainLoss=17.8538 ValLoss=13.6673 R2=-0.1149\nP1 Ep3: TrainLoss=17.4233 ValLoss=14.2942 R2=-0.1416\n","output_type":"stream"}],"execution_count":null},{"cell_type":"code","source":"print(\"working\")","metadata":{"trusted":true,"execution":{"execution_failed":"2026-01-21T13:59:50.922Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}