{"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,"sourceType":"competition"}],"dockerImageVersionId":31154,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nfrom PIL import Image\nimport torch\nimport torch.nn as nn\nfrom torchvision import models, transforms\nfrom torch.utils.data import Dataset, DataLoader\nfrom sklearn.model_selection import KFold\nfrom sklearn.linear_model import Ridge\nimport xgboost as xgb\nimport warnings\nimport os\nimport glob\nimport gc\n\n# --- CONFIGURATION & SETUP ---\nwarnings.filterwarnings('ignore')\ndevice = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\n\n# --- HELPER: ResNet Caching ---\ndef _find_resnet18_cache_path():\n    cache_dir = \"/root/.cache/torch/hub/checkpoints\"\n    if os.path.isdir(cache_dir):\n        cand = glob.glob(os.path.join(cache_dir, \"resnet18*.pth\"))\n        if len(cand) > 0: return cand[0]\n    cand2 = glob.glob(\"/root/.cache/torch/hub/*/resnet18*.pth\")\n    return cand2[0] if len(cand2) > 0 else None\n\ndef _load_resnet18_weights_to_backbone(backbone, pth_path):\n    state = torch.load(pth_path, map_location='cpu')\n    state_dict = state['state_dict'] if 'state_dict' in state else state\n    new_state = {}\n    for k, v in state_dict.items():\n        new_k = k[len('module.'):] if k.startswith('module.') else k\n        new_state[new_k] = v\n    filtered_state = {k: v for k, v in new_state.items() if not k.startswith('fc.')}\n    backbone.load_state_dict(filtered_state, strict=False)\n\ndef _try_load_cached_resnet18(backbone):\n    pth = _find_resnet18_cache_path()\n    if pth is None: return False\n    try:\n        _load_resnet18_weights_to_backbone(backbone, pth)\n        return True\n    except Exception: return False\n\n# --- DATASET 1: CNN Training (Robust) ---\nclass BiomassDataset(Dataset):\n    def __init__(self, df, image_dir, transform=None, targets_col=None, is_test=False):\n        self.df = df.reset_index(drop=True)\n        self.image_dir = image_dir\n        self.transform = transform\n        self.targets_col = targets_col\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        rel_path = str(self.df.loc[idx, 'image_path'])\n        \n        # Defense 1: Path Fallback\n        path_a = os.path.join(self.image_dir, rel_path)\n        path_b = os.path.join(self.image_dir, rel_path.split('/')[-1])\n        img_path = path_a if os.path.exists(path_a) else path_b\n        \n        # Defense 2: Corrupt Image Handling\n        try:\n            image = Image.open(img_path).convert('RGB')\n        except Exception:\n            image = Image.new('RGB', (224, 224), (0, 0, 0))\n            \n        if self.transform:\n            image = self.transform(image)\n            \n        if self.is_test:\n            return image\n        else:\n            targets = self.df.loc[idx, self.targets_col].values.astype('float32')\n            return image, torch.tensor(targets)\n\n# --- DATASET 2: Parallel Feature Extraction (Optimized) ---\nclass FeatureExtractionDataset(Dataset):\n    def __init__(self, image_paths, base_dir):\n        self.image_paths = image_paths\n        self.base_dir = base_dir\n\n    def __len__(self):\n        return len(self.image_paths)\n\n    def __getitem__(self, idx):\n        rel_path = str(self.image_paths[idx])\n        path_a = os.path.join(self.base_dir, rel_path)\n        path_b = os.path.join(self.base_dir, rel_path.split('/')[-1])\n        final_path = path_a if os.path.exists(path_a) else path_b\n        \n        # Default zero features\n        dummy_features = torch.zeros(8, dtype=torch.float32)\n        \n        try:\n            if not os.path.exists(final_path): return dummy_features\n            \n            img = Image.open(final_path).convert('RGB')\n            img_array = np.array(img) / 255.0\n            \n            feats = []\n            for i in range(3): # RGB\n                feats.append(np.mean(img_array[:,:,i]))\n                feats.append(np.std(img_array[:,:,i]))\n            gray = np.mean(img_array, axis=2) # Gray\n            feats.append(np.mean(gray))\n            feats.append(np.std(gray))\n            return torch.tensor(feats, dtype=torch.float32)\n        except Exception:\n            return dummy_features\n\ndef get_features_parallel(image_paths, base_dir):\n    dataset = FeatureExtractionDataset(image_paths, base_dir)\n    # Defense 3: Multiprocessing to solve 1hr Timeout\n    loader = DataLoader(dataset, batch_size=64, num_workers=2, shuffle=False)\n    all_feats = []\n    for batch_feats in loader:\n        all_feats.append(batch_feats.numpy())\n    return np.vstack(all_feats)\n\n# --- MODEL: ResNet18 ---\nclass BiomassModel(nn.Module):\n    def __init__(self, num_targets=5):\n        super().__init__()\n        self.backbone = models.resnet18(pretrained=False)\n        num_features = self.backbone.fc.in_features\n        self.backbone.fc = nn.Linear(num_features, num_targets)\n        _try_load_cached_resnet18(self.backbone)\n    def forward(self, x): return self.backbone(x)\n\n# --- EXECUTION START ---\nprint(\"Loading Data...\")\ntrain_df = pd.read_csv('/kaggle/input/csiro-biomass/train.csv')\ntest_df = pd.read_csv('/kaggle/input/csiro-biomass/test.csv')\n\ntarget_cols = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\ndf_wide = train_df.pivot(index='image_path', columns='target_name', values='target')\nmeta_cols = ['image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm']\nmeta = train_df[meta_cols].drop_duplicates(subset='image_path')\ntrain_final = pd.merge(df_wide, meta,  on='image_path', how='left').reset_index(drop=True)\n\nimg_base_dir = '/kaggle/input/csiro-biomass/train'\ntest_img_base = '/kaggle/input/csiro-biomass/test'\n\n# --- STEP 1: Feature Extraction (Parallel) ---\nprint(\"Extracting features (Parallel Mode)...\")\ntrain_image_features = get_features_parallel(train_final['image_path'].tolist(), img_base_dir)\ntest_features = get_features_parallel(test_df['image_path'].tolist(), test_img_base)\n\n# --- STEP 2: Training & Inference Loop ---\ntrain_transform = transforms.Compose([\n    transforms.Resize((224, 224)), transforms.RandomHorizontalFlip(p=0.5),\n    transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\nval_transform = transforms.Compose([\n    transforms.Resize((224, 224)),\n    transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])\n])\n\nn_splits = 5\nkf = KFold(n_splits=n_splits, shuffle=True, random_state=42)\nnum_epochs = 15 # Adjusted for balance\n\noof_cnn = np.zeros((len(train_final), 5))\noof_xgb = np.zeros((len(train_final), 5))\ntest_cnn_preds_sum = np.zeros((len(test_df), 5))\ntest_xgb_preds_sum = np.zeros((len(test_df), 5))\n\nfor fold, (tr_idx, val_idx) in enumerate(kf.split(train_final)):\n    print(f\"--- Fold {fold+1}/{n_splits} ---\")\n    train_fold = train_final.loc[tr_idx].reset_index(drop=True)\n    val_fold = train_final.loc[val_idx].reset_index(drop=True)\n    \n    # 1. Train CNN\n    tr_ds = BiomassDataset(train_fold, img_base_dir, train_transform, target_cols)\n    val_ds = BiomassDataset(val_fold, img_base_dir, val_transform, target_cols)\n    tr_loader = DataLoader(tr_ds, batch_size=32, num_workers=2, shuffle=True)\n    val_loader = DataLoader(val_ds, batch_size=32, num_workers=2, shuffle=False)\n    \n    model = BiomassModel(5).to(device)\n    optimizer = torch.optim.Adam(model.parameters(), lr=1e-3)\n    criterion = nn.MSELoss()\n    scaler = torch.cuda.amp.GradScaler()\n    \n    # Quick Training Loop\n    for epoch in range(num_epochs):\n        model.train()\n        for imgs, tars in tr_loader:\n            imgs, tars = imgs.to(device), tars.to(device)\n            optimizer.zero_grad()\n            with torch.cuda.amp.autocast():\n                loss = criterion(model(imgs), tars)\n            scaler.scale(loss).backward()\n            scaler.step(optimizer)\n            scaler.update()\n            \n    # CNN Inference\n    model.eval()\n    val_preds = []\n    with torch.no_grad():\n        for imgs, _ in val_loader:\n            val_preds.append(model(imgs.to(device)).cpu().numpy())\n    oof_cnn[val_idx] = np.vstack(val_preds)\n    \n    # Test Inference (Defense 4: Batch Size 1 for safety)\n    test_ds = BiomassDataset(test_df, test_img_base, val_transform, is_test=True)\n    test_loader = DataLoader(test_ds, batch_size=1, shuffle=False, num_workers=0)\n    test_fold_preds = []\n    with torch.no_grad():\n        for imgs in test_loader:\n            test_fold_preds.append(model(imgs.to(device)).cpu().numpy())\n    test_cnn_preds_sum += np.vstack(test_fold_preds) / n_splits\n    \n    # 2. Train XGBoost\n    X_tr, X_val = train_image_features[tr_idx], train_image_features[val_idx]\n    y_tr_all = train_final.loc[tr_idx, target_cols].values\n    \n    fold_xgb_val, fold_xgb_test = [], []\n    for i in range(5):\n        xgb_model = xgb.XGBRegressor(n_estimators=100, max_depth=5, learning_rate=0.1, n_jobs=4, random_state=42)\n        xgb_model.fit(X_tr, y_tr_all[:, i])\n        fold_xgb_val.append(xgb_model.predict(X_val))\n        fold_xgb_test.append(xgb_model.predict(test_features))\n    \n    oof_xgb[val_idx] = np.column_stack(fold_xgb_val)\n    test_xgb_preds_sum += np.column_stack(fold_xgb_test) / n_splits\n    \n    # Cleanup\n    del model, optimizer, scaler, tr_loader, val_loader\n    gc.collect()\n    torch.cuda.empty_cache()\n\n# --- STEP 3: Meta-Stacking ---\nprint(\"Stacking Models...\")\nmeta_features = np.concatenate([oof_cnn, oof_xgb], axis=1)\ntest_meta_features = np.concatenate([test_cnn_preds_sum, test_xgb_preds_sum], axis=1)\ntrain_targets = train_final[target_cols].values\n\nfinal_preds = []\nfor i in range(5):\n    meta = Ridge(alpha=1.0)\n    meta.fit(meta_features[:, [i, i+5]], train_targets[:, i])\n    final_preds.append(meta.predict(test_meta_features[:, [i, i+5]]))\nfinal_preds = np.column_stack(final_preds)\n\n# --- STEP 4: Submission (Defense 5: Safe Merge) ---\nprint(\"Generating Safe Submission...\")\n\n# Prepare Predictions\npred_df = pd.DataFrame(final_preds, columns=target_cols)\npred_df['image_path'] = test_df['image_path']\npred_long = pred_df.melt(id_vars=['image_path'], value_vars=target_cols, \n                         var_name='target_name', value_name='predicted_value')\n\n# Load official test layout (to get correct sample_ids)\ntest_lookup = pd.read_csv('/kaggle/input/csiro-biomass/test.csv')\n\n# Merge Strategy\nif 'target_name' in test_lookup.columns:\n    full_pred = pd.merge(test_lookup[['sample_id', 'image_path', 'target_name']], \n                         pred_long, on=['image_path', 'target_name'], how='left')\nelse:\n    # Fallback for unexpected test.csv format\n    pred_long['clean_id'] = pred_long['image_path'].apply(lambda x: str(x).split('.')[0].split('/')[-1])\n    pred_long['sample_id'] = pred_long['clean_id'] + '__' + pred_long['target_name']\n    full_pred = pred_long\n\n# Final Merge with Template\nsubmission = pd.read_csv('/kaggle/input/csiro-biomass/sample_submission.csv')\nsubmission_final = submission[['sample_id']].merge(\n    full_pred[['sample_id', 'predicted_value']], on='sample_id', how='left'\n)\n\n# Sanity Fixes\nsubmission_final['target'] = submission_final['predicted_value'].fillna(0).clip(lower=0)\nsubmission_final = submission_final.drop_duplicates(subset=['sample_id'], keep='first')\n\n# Force Alignment (The ultimate safety check)\nif len(submission_final) != len(submission):\n    print(\"Aligning row counts...\")\n    submission_final = submission_final.set_index('sample_id').reindex(submission['sample_id']).fillna(0).reset_index()\n\nsubmission_final[['sample_id', 'target']].to_csv('submission.csv', index=False)\nprint(\"Done! Submission saved.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-13T08:34:25.518792Z","iopub.execute_input":"2025-12-13T08:34:25.519058Z","iopub.status.idle":"2025-12-13T08:45:57.626156Z","shell.execute_reply.started":"2025-12-13T08:34:25.519038Z","shell.execute_reply":"2025-12-13T08:45:57.625346Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}