{"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,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":136850482,"sourceType":"kernelVersion"},{"sourceId":218740338,"sourceType":"kernelVersion"},{"sourceId":282646796,"sourceType":"kernelVersion"}],"dockerImageVersionId":31236,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport cv2\nimport numpy as np\nimport pandas as pd\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.cuda.amp import autocast, GradScaler\nimport timm\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\nfrom sklearn.preprocessing import StandardScaler, LabelEncoder\nfrom tqdm.auto import tqdm\nimport warnings\n\n# إخفاء التحذيرات المزعجة\nwarnings.filterwarnings('ignore')\n\n# CONFIGURATION & PATHS \nCONFIG = {\n    'seed': 42,\n    'img_size': 224,\n    'batch_size': 16,  \n    'epochs': 10,\n    'lr': 1e-4,\n    'weight_decay': 1e-2,\n    'device': torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n}\n\n# مسارات البيانات في Kaggle\nBASE_DIR = '/kaggle/input/csiro-biomass'\nTRAIN_IMG_DIR = os.path.join(BASE_DIR, 'train')\nTEST_IMG_DIR = os.path.join(BASE_DIR, 'test')\n\n# مسار الأوزان (تأكد من هذا المسار في الـ Input لديك)\n# هذا المسار يفترض أنك أنشأت الداتاسيت باسم my-dinov2-weights\n# إذا كان الاسم مختلفاً، عدله هنا\nLOCAL_WEIGHTS_PATH = '/kaggle/input/my-dinov2-weights/dinov2_base.pth' \n\ndef seed_everything(seed):\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n\nseed_everything(CONFIG['seed'])\n\n# --- 2. DATA PREPARATION & CLEANING ---\nprint(\"📂 Loading & Cleaning Data...\")\n\n# تحميل ملفات CSV\ntrain_df_raw = pd.read_csv(os.path.join(BASE_DIR, 'train.csv'))\ntest_df_raw = pd.read_csv(os.path.join(BASE_DIR, 'test.csv'))\n\n# [CRITICAL FIX] إصلاح مسارات الصور (إزالة اسم المجلد المكرر)\n# يحول 'train/ID123.jpg' إلى 'ID123.jpg'\ntrain_df_raw['image_path'] = train_df_raw['image_path'].apply(os.path.basename)\ntest_df_raw['image_path'] = test_df_raw['image_path'].apply(os.path.basename)\n\n# تحويل البيانات من Long إلى Wide (صف واحد لكل صورة)\npivot_df = train_df_raw.pivot_table(\n    index=['image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm'],\n    columns='target_name',\n    values='target'\n).reset_index()\n\n# ترتيب الأعمدة المستهدفة\ntarget_cols = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\npivot_df = pivot_df[['image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm'] + target_cols]\npivot_df[target_cols] = pivot_df[target_cols].fillna(0.0)\n\n# تحويل القيم المستهدفة (Log Transform) لتناسب توزيع الكتلة الحيوية\npivot_df[target_cols] = np.log1p(pivot_df[target_cols])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T01:17:57.673789Z","iopub.execute_input":"2026-02-18T01:17:57.674407Z","iopub.status.idle":"2026-02-18T01:17:57.706672Z","shell.execute_reply.started":"2026-02-18T01:17:57.674376Z","shell.execute_reply":"2026-02-18T01:17:57.706076Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# FEATURE ENGINEERING\n# ترميز الولاية\nle_state = LabelEncoder()\npivot_df['State_Code'] = le_state.fit_transform(pivot_df['State'])\n\n# معالجة الأنواع (Species) - Multi-Hot Encoding\nall_species = set()\nfor s in pivot_df['Species'].unique():\n    all_species.update(s.split('_'))\nspecies_map = {name: i for i, name in enumerate(sorted(all_species))}\nnum_species = len(species_map)\n\ndef encode_species(species_str):\n    vec = np.zeros(num_species, dtype=np.float32)\n    for part in species_str.split('_'):\n        if part in species_map:\n            vec[species_map[part]] = 1.0\n    return vec\n\npivot_df['Species_Vec'] = pivot_df['Species'].apply(encode_species)\n\n# تطبيع البيانات الرقمية (Scaling)\nscaler = StandardScaler()\npivot_df[['Pre_GSHH_NDVI', 'Height_Ave_cm']] = scaler.fit_transform(pivot_df[['Pre_GSHH_NDVI', 'Height_Ave_cm']])\n\n# حفظ قيم المتوسط لاستخدامها لاحقاً في بيانات الاختبار المفقودة\nMEAN_NDVI = pivot_df['Pre_GSHH_NDVI'].mean()\nMEAN_HEIGHT = pivot_df['Height_Ave_cm'].mean()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T01:18:01.915702Z","iopub.execute_input":"2026-02-18T01:18:01.916276Z","iopub.status.idle":"2026-02-18T01:18:01.928905Z","shell.execute_reply.started":"2026-02-18T01:18:01.916250Z","shell.execute_reply":"2026-02-18T01:18:01.928331Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n#  4. DATASET CLASS \nclass BiomassDataset(Dataset):\n    def __init__(self, df, img_dir, transform=None, is_train=True):\n        self.df = df\n        self.img_dir = img_dir\n        self.transform = transform\n        self.is_train = is_train\n        \n    def __len__(self):\n        return len(self.df)\n    \n    def clean_timestamp(self, img):\n        # إزالة التوقيت البرتقالي (Inpainting)\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=1)\n        if np.sum(mask) > 0:\n            img = cv2.inpaint(img, mask, 3, cv2.INPAINT_TELEA)\n        return img\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        \n        # تحميل الصورة\n        img_path = os.path.join(self.img_dir, row['image_path'])\n        image = cv2.imread(img_path)\n        \n        # التأكد من وجود الصورة\n        if image is None:\n            # صورة سوداء احتياطية في حالة الخطأ\n            image = np.zeros((CONFIG['img_size'], CONFIG['img_size'], 3), dtype=np.uint8)\n        else:\n            image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n            image = self.clean_timestamp(image)\n        \n        if self.transform:\n            augmented = self.transform(image=image)\n            image = augmented['image']\n            \n        # تجهيز البيانات الجدولية\n        ndvi = row['Pre_GSHH_NDVI'] if 'Pre_GSHH_NDVI' in row else MEAN_NDVI\n        height = row['Height_Ave_cm'] if 'Height_Ave_cm' in row else MEAN_HEIGHT\n        species_vec = row['Species_Vec'] if 'Species_Vec' in row else np.zeros(num_species)\n        state_idx = row['State_Code'] if 'State_Code' in row else 0\n        \n        tab_dense = np.concatenate(([ndvi, height], species_vec)).astype(np.float32)\n        \n        if self.is_train:\n            targets = row[target_cols].values.astype(np.float32)\n            return image, tab_dense, state_idx, targets\n        else:\n            return image, tab_dense, state_idx","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T01:18:05.100665Z","iopub.execute_input":"2026-02-18T01:18:05.100935Z","iopub.status.idle":"2026-02-18T01:18:05.110148Z","shell.execute_reply.started":"2026-02-18T01:18:05.100910Z","shell.execute_reply":"2026-02-18T01:18:05.109546Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Augmentations\ntrain_aug = A.Compose([\n    A.Resize(CONFIG['img_size'], CONFIG['img_size']),\n    A.HorizontalFlip(p=0.5),\n    A.VerticalFlip(p=0.5),\n    A.Rotate(limit=30, p=0.5),\n    A.ColorJitter(brightness=0.2, contrast=0.2, p=0.5),\n    A.Normalize(),\n    ToTensorV2(),\n])\n\nval_aug = A.Compose([\n    A.Resize(CONFIG['img_size'], CONFIG['img_size']),\n    A.Normalize(),\n    ToTensorV2(),\n])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T01:18:14.342552Z","iopub.execute_input":"2026-02-18T01:18:14.342827Z","iopub.status.idle":"2026-02-18T01:18:14.352422Z","shell.execute_reply.started":"2026-02-18T01:18:14.342803Z","shell.execute_reply":"2026-02-18T01:18:14.351781Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import math\nimport torch.nn.functional as F\n\n\n\nclass CrossAttentionFusion(nn.Module):\n    def __init__(self, img_dim, tab_dim, embed_dim=256):\n        super().__init__()\n        # تحويل البيانات الجدولية إلى Vector بنفس حجم الصور\n        self.tab_proj = nn.Sequential(\n            nn.Linear(tab_dim, embed_dim),\n            nn.LayerNorm(embed_dim),\n            nn.GELU()\n        )\n        \n        # تضمين الولاية (State Embedding)\n        self.state_embed = nn.Embedding(20, embed_dim) # نفترض 20 ولاية كحد أقصى\n        \n        # طبقة لتجهيز البيانات الجدولية للدمج\n        self.final_tab_proj = nn.Linear(embed_dim, img_dim)\n        \n        # انتباه متعدد الرؤوس (Cross-Attention)\n        self.attn = nn.MultiheadAttention(embed_dim=img_dim, num_heads=8, batch_first=True)\n        self.norm = nn.LayerNorm(img_dim)\n\n    def forward(self, img_feats, tab_dense, state_idx):\n        # تجهيز البيانات الجدولية\n        tab_emb = self.tab_proj(tab_dense) + self.state_embed(state_idx)\n        tab_query = self.final_tab_proj(tab_emb).unsqueeze(1) # [Batch, 1, Dim]\n        \n        # الانتباه: الجدول يسأل الصورة (Query=Tabular, Key/Value=Image)\n        attn_out, _ = self.attn(query=tab_query, key=img_feats, value=img_feats)\n        \n        # إضافة المتبقي (Residual Connection) والمعايرة\n        return self.norm(tab_query + attn_out).squeeze(1)\n        \nclass CSIROModel(nn.Module):\n    def __init__(self, tab_dim, weights_path=None):\n        super().__init__()\n        \n        # إعداد النموذج بحجم 224\n        self.backbone = timm.create_model(\n            'vit_base_patch14_dinov2.lvd142m', \n            pretrained=False, \n            num_classes=0,\n            img_size=CONFIG['img_size'] # 224\n        )\n        \n        # تحميل الأوزان بذكاء (Interpolation)\n        if weights_path and os.path.exists(weights_path):\n            print(f\"📥 Loading Weights with Interpolation from: {weights_path}\")\n            state_dict = torch.load(weights_path, map_location='cpu')\n            \n            # --- السحر يبدأ هنا: تعديل حجم pos_embed ---\n            if 'pos_embed' in state_dict and state_dict['pos_embed'].shape != self.backbone.pos_embed.shape:\n                print(\"🔄 Resizing positional embeddings to match 224x224...\")\n                # استخراج الأوزان القديمة\n                pos_embed_checkpoint = state_dict['pos_embed']\n                embedding_size = pos_embed_checkpoint.shape[-1]\n                num_patches = self.backbone.patch_embed.num_patches\n                num_extra_tokens = self.backbone.pos_embed.shape[-2] - num_patches\n                \n                # فصل التوكنز الإضافية (مثل CLS token)\n                orig_num_tokens = pos_embed_checkpoint.shape[-2] - num_extra_tokens\n                grid_size = int(math.sqrt(orig_num_tokens))\n                \n                # تغيير الحجم (Interpolation)\n                pos_tokens = pos_embed_checkpoint[:, num_extra_tokens:]\n                pos_tokens = pos_tokens.reshape(1, grid_size, grid_size, embedding_size).permute(0, 3, 1, 2)\n                \n                new_grid_size = int(math.sqrt(num_patches))\n                new_pos_tokens = F.interpolate(\n                    pos_tokens, size=(new_grid_size, new_grid_size), mode='bicubic', align_corners=False\n                )\n                \n                new_pos_tokens = new_pos_tokens.permute(0, 2, 3, 1).flatten(1, 2)\n                new_pos_embed = torch.cat((pos_embed_checkpoint[:, :num_extra_tokens], new_pos_tokens), dim=1)\n                \n                # تحديث الـ state_dict\n                state_dict['pos_embed'] = new_pos_embed\n            # -------------------------------------------\n\n            # الآن يمكننا التحميل بأمان\n            self.backbone.load_state_dict(state_dict, strict=False)\n        else:\n            print(\"⚠️ WARNING: Random weights initialized!\")\n            \n        self.img_dim = self.backbone.embed_dim\n        self.fusion = CrossAttentionFusion(self.img_dim, tab_dim)\n        \n        # Heads\n        self.head_total = nn.Sequential(\n            nn.Linear(self.img_dim * 2, 512), nn.GELU(), nn.Dropout(0.2), nn.Linear(512, 1)\n        )\n        self.head_components = nn.Sequential(\n            nn.Linear(self.img_dim * 2, 512), nn.GELU(), nn.Dropout(0.2), nn.Linear(512, 4)\n        )\n\n    def forward(self, img, tab_dense, state_idx):\n        img_feats = self.backbone.forward_features(img)\n        img_gap = img_feats.mean(dim=1)\n        fused = self.fusion(img_feats, tab_dense, state_idx)\n        features = torch.cat([img_gap, fused], dim=1)\n        total = self.head_total(features)\n        comps = self.head_components(features)\n        return torch.cat([comps, total], dim=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T01:29:02.738836Z","iopub.execute_input":"2026-02-18T01:29:02.739606Z","iopub.status.idle":"2026-02-18T01:29:02.752091Z","shell.execute_reply.started":"2026-02-18T01:29:02.739568Z","shell.execute_reply":"2026-02-18T01:29:02.751306Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# LOSS FUNCTION\nclass BiomassLoss(nn.Module):\n    def __init__(self):\n        super().__init__()\n        self.weights = torch.tensor([0.1, 0.1, 0.1, 0.2, 0.5], device=CONFIG['device'])\n        self.huber = nn.HuberLoss(reduction='none', delta=1.0)\n    \n    def forward(self, preds, targets):\n        # Huber Loss\n        raw_loss = self.huber(preds, targets)\n        weighted_loss = (raw_loss * self.weights).sum(dim=1).mean()\n        \n        # Consistency Constraint (Sum vs Total)\n        # Convert log -> linear approx for consistency check\n        preds_lin = torch.expm1(preds)\n        sum_components = preds_lin[:, 0] + preds_lin[:, 1] + preds_lin[:, 2]\n        pred_total = preds_lin[:, 4]\n        consistency_loss = torch.mean(torch.abs(sum_components - pred_total))\n        \n        return weighted_loss + (0.05 * consistency_loss)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T01:29:06.362105Z","iopub.execute_input":"2026-02-18T01:29:06.362466Z","iopub.status.idle":"2026-02-18T01:29:06.369303Z","shell.execute_reply.started":"2026-02-18T01:29:06.362439Z","shell.execute_reply":"2026-02-18T01:29:06.368450Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# TRAINING UTILS\ndef train_one_epoch(model, loader, optimizer, criterion, scaler):\n    model.train()\n    running_loss = 0\n    for img, tab, state, target in tqdm(loader, desc=\"Training\"):\n        img = img.to(CONFIG['device'])\n        tab = tab.to(CONFIG['device'])\n        state = state.to(CONFIG['device'])\n        target = target.to(CONFIG['device'])\n        \n        optimizer.zero_grad()\n        with autocast():\n            preds = model(img, tab, state)\n            loss = criterion(preds, target)\n        \n        scaler.scale(loss).backward()\n        scaler.step(optimizer)\n        scaler.update()\n        running_loss += loss.item()\n    return running_loss / len(loader)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T01:29:08.471560Z","iopub.execute_input":"2026-02-18T01:29:08.471832Z","iopub.status.idle":"2026-02-18T01:29:08.477447Z","shell.execute_reply.started":"2026-02-18T01:29:08.471810Z","shell.execute_reply":"2026-02-18T01:29:08.476784Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# EXECUTION\n# إعداد النموذج\ndummy_tab_dim = 2 + num_species\nmodel = CSIROModel(tab_dim=dummy_tab_dim, weights_path=LOCAL_WEIGHTS_PATH).to(CONFIG['device'])\n\noptimizer = optim.AdamW(model.parameters(), lr=CONFIG['lr'], weight_decay=CONFIG['weight_decay'])\nscheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=CONFIG['epochs'])\ncriterion = BiomassLoss()\nscaler = GradScaler()\n\n# إعداد البيانات\nds = BiomassDataset(pivot_df, img_dir=TRAIN_IMG_DIR, transform=train_aug)\nloader = DataLoader(\n    ds, \n    batch_size=CONFIG['batch_size'], \n    shuffle=True, \n    num_workers=2, \n    pin_memory=True,\n    persistent_workers=True\n)\n\n# حلقة التدريب\nprint(\"🚀 Starting Training Process...\")\nfor epoch in range(CONFIG['epochs']):\n    loss = train_one_epoch(model, loader, optimizer, criterion, scaler)\n    scheduler.step()\n    print(f\"Epoch {epoch+1} | Loss: {loss:.5f} | LR: {scheduler.get_last_lr()[0]:.2e}\")\n\n# حفظ النموذج\ntorch.save(model.state_dict(), 'csiro_model_final.pth')\n\n# --- 9. SUBMISSION GENERATION ---\nprint(\"🔮 Generating Submission...\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T01:29:10.590660Z","iopub.execute_input":"2026-02-18T01:29:10.590918Z","iopub.status.idle":"2026-02-18T01:32:29.231802Z","shell.execute_reply.started":"2026-02-18T01:29:10.590896Z","shell.execute_reply":"2026-02-18T01:32:29.230692Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def generate_submission(model, test_df_raw, submission_template):\n    model.eval()\n    \n    # 1. تجهيز بيانات الاختبار الفريدة\n    test_unique = test_df_raw[['image_path']].drop_duplicates().reset_index(drop=True)\n    \n    # [FIX] Apply path fix here too\n    test_unique['image_path'] = test_unique['image_path'].apply(os.path.basename)\n    \n    # 2. تعويض البيانات الناقصة (Mean Imputation)\n    test_unique['Pre_GSHH_NDVI'] = MEAN_NDVI\n    test_unique['Height_Ave_cm'] = MEAN_HEIGHT\n    test_unique['State_Code'] = 0 \n    test_unique['Species_Vec'] = [np.zeros(num_species, dtype=np.float32) for _ in range(len(test_unique))]\n\n    # 3. Loader\n    test_ds = BiomassDataset(test_unique, img_dir=TEST_IMG_DIR, transform=val_aug, is_train=False)\n    test_loader = DataLoader(test_ds, batch_size=CONFIG['batch_size']*2, shuffle=False, num_workers=2)\n    \n    predictions = []\n    \n    with torch.no_grad():\n        for img, tab, state in tqdm(test_loader, desc=\"Predicting\"):\n            img = img.to(CONFIG['device'])\n            tab = tab.to(CONFIG['device'])\n            state = state.to(CONFIG['device'])\n            \n            # Predict & Inverse Log\n            preds_log = model(img, tab, state)\n            preds_lin = torch.expm1(preds_log)\n            predictions.append(preds_lin.cpu().numpy())\n            \n    all_preds = np.vstack(predictions)\n    \n    # 4. Format Submission\n    target_cols = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    test_unique[target_cols] = all_preds\n    \n    submission_rows = []\n    for _, row in test_unique.iterrows():\n        img_id = row['image_path'].replace('.jpg', '')\n        for col in target_cols:\n            sample_id = f\"{img_id}__{col}\"\n            # Ensure non-negative\n            value = max(0.0, float(row[col]))\n            submission_rows.append({'sample_id': sample_id, 'target': value})\n            \n    return pd.DataFrame(submission_rows)\n\nsub_df = generate_submission(model, test_df_raw, None)\nsub_df.to_csv('submission.csv', index=False)\nprint(\"✅ Submission Saved: submission.csv\")\nprint(sub_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-02-18T01:32:29.234175Z","iopub.execute_input":"2026-02-18T01:32:29.234484Z","iopub.status.idle":"2026-02-18T01:32:29.611834Z","shell.execute_reply.started":"2026-02-18T01:32:29.234450Z","shell.execute_reply":"2026-02-18T01:32:29.611026Z"}},"outputs":[],"execution_count":null}]}