{"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":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":14622907,"sourceType":"datasetVersion","datasetId":9340347},{"sourceId":293691576,"sourceType":"kernelVersion"},{"sourceId":268942,"sourceType":"modelInstanceVersion","modelInstanceId":230141,"modelId":251887},{"sourceId":711299,"sourceType":"modelInstanceVersion","modelInstanceId":540408,"modelId":553604},{"sourceId":719660,"sourceType":"modelInstanceVersion","modelInstanceId":547330,"modelId":560139}],"dockerImageVersionId":31193,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# CSIRO Biomass: The \"Tri-Stream\" Ensemble (Target 0.80+)\n\n**Strategy:**\n1. **Stream A (Restored 0.72):** SigLIP Semantic Regression (Robust PLS handling).\n2. **Stream B (Restored 0.72):** DINOv2 Visual Texture Analysis (Dual GPU Inference).\n3. **Stream C (New):** Hybrid KNN Retrieval (Visual + Metadata) as an independent voter.\n\n**Weights:** `0.60 DINO + 0.30 SigLIP + 0.10 KNN`","metadata":{}},{"cell_type":"code","source":"import os\n# --- CRITICAL FIX: Force python protobuf implementation ---\nos.environ[\"PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION\"] = \"python\"\n\nimport gc\nimport random\nimport warnings\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom PIL import Image\nfrom tqdm.auto import tqdm\nfrom pathlib import Path\nfrom copy import deepcopy\nfrom dataclasses import dataclass\n\n# Sklearn & Models\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.decomposition import PCA\nfrom sklearn.cross_decomposition import PLSRegression\nfrom sklearn.mixture import GaussianMixture\nfrom sklearn.ensemble import GradientBoostingRegressor, HistGradientBoostingRegressor\nfrom lightgbm import LGBMRegressor\nfrom catboost import CatBoostRegressor\nfrom transformers import AutoModel, AutoImageProcessor, AutoTokenizer\n\n# Suppress warnings\nwarnings.filterwarnings('ignore')\nos.environ[\"TOKENIZERS_PARALLELISM\"] = \"false\"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================================================\n# 1. CONFIGURATION\n# =========================================================================================\n@dataclass\nclass Config:\n    DATA_PATH: Path = Path(\"/kaggle/input/csiro-biomass/\")\n    SPLIT_PATH: Path = Path(\"/kaggle/input/csiro-datasplit/csiro_data_split.csv\")\n    SIGLIP_PATH: str = \"/kaggle/input/google-siglip-so400m-patch14-384/transformers/default/1\"\n    \n    SEED: int = 42\n    DEVICE: str = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    PATCH_SIZE: int = 520\n    OVERLAP: int = 16\n    \n    # Target definitions\n    TARGET_NAMES = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\n    TARGET_MAX = {\n        \"Dry_Clover_g\": 71.7865,\n        \"Dry_Dead_g\": 83.8407,\n        \"Dry_Green_g\": 157.9836,\n        \"Dry_Total_g\": 185.70,\n        \"GDM_g\": 157.9836,\n    }\n    \n    # KNN Config\n    KNN_K: int = 5\n\ncfg = Config()\n\ndef seeding(SEED):\n    np.random.seed(SEED)\n    random.seed(SEED)\n    os.environ['PYTHONHASHSEED'] = str(SEED)\n    torch.manual_seed(SEED)\n    if torch.cuda.is_available(): \n        torch.cuda.manual_seed(SEED)\n        torch.backends.cudnn.deterministic = True\n        torch.backends.cudnn.benchmark = False\n\nseeding(cfg.SEED)\nprint(f\"System: {torch.cuda.device_count()} GPUs available\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================================================\n# 2. DATA LOADING & PRE-PROCESSING\n# =========================================================================================\ndef pivot_table(df: pd.DataFrame) -> pd.DataFrame:\n    if 'target' in df.columns.tolist():\n        df_pt = pd.pivot_table(\n            df, \n            values='target', \n            index=['image_path', 'Sampling_Date', 'State', 'Species', 'Pre_GSHH_NDVI', 'Height_Ave_cm'], \n            columns='target_name', \n            aggfunc='mean'\n        ).reset_index()\n    else:\n        df['target'] = 0\n        df_pt = pd.pivot_table(\n            df, \n            values='target', \n            index='image_path', \n            columns='target_name', \n            aggfunc='mean'\n        ).reset_index()\n    return df_pt\n\ndef melt_table(df: pd.DataFrame) -> pd.DataFrame:\n    melted = df.melt(\n        id_vars='image_path',\n        value_vars=cfg.TARGET_NAMES,\n        var_name='target_name',\n        value_name='target'\n    )\n    melted['sample_id'] = (\n        melted['image_path']\n        .str.replace(r'^.*/', '', regex=True)\n        .str.replace('.jpg', '', regex=False)\n        + '__' + melted['target_name']\n    )\n    return melted[['sample_id', 'target']]\n\ndef post_process_biomass(df_preds):\n    ordered_cols = [\"Dry_Green_g\", \"Dry_Clover_g\", \"Dry_Dead_g\", \"GDM_g\", \"Dry_Total_g\"]\n    for c in ordered_cols:\n        if c not in df_preds.columns: df_preds[c] = 0.0\n    \n    df_out = df_preds.copy()\n    df_out['Dry_Clover_g'] = 0.0 \n    df_out['GDM_g'] = df_out['Dry_Green_g'] + df_out['Dry_Clover_g']\n    df_out['Dry_Total_g'] = df_out['GDM_g'] + df_out['Dry_Dead_g']\n    \n    df_out['GDM_g'] = df_out['GDM_g'].clip(lower=0.0)\n    df_out['Dry_Total_g'] = df_out['Dry_Total_g'].clip(lower=0.0)\n    return df_out\n\nprint(\"Loading Data...\")\ntrain_df = pd.read_csv(cfg.SPLIT_PATH)\ncols_to_keep = [c for c in train_df.columns if not c.startswith('emb')]\ntrain_df = train_df[cols_to_keep]\n\nif not str(train_df['image_path'].iloc[0]).startswith('/'):\n     train_df['image_path'] = train_df['image_path'].apply(lambda p: str(cfg.DATA_PATH / 'train' / os.path.basename(p)))\n\ntest_df_raw = pd.read_csv(cfg.DATA_PATH / 'test.csv')\ntest_df = pivot_table(test_df_raw)\ntest_df['image_path'] = test_df['image_path'].apply(lambda p: str(cfg.DATA_PATH / p))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================================================\n# 3. FEATURE EXTRACTION: SIGLIP (Single GPU to avoid Attribute Errors)\n# =========================================================================================\ndef split_image(image, patch_size=520, overlap=16):\n    h, w, c = image.shape\n    stride = patch_size - overlap\n    patches = []\n    for y in range(0, h, stride):\n        for x in range(0, w, stride):\n            y2 = min(y + patch_size, h)\n            x2 = min(x + patch_size, w)\n            y1 = max(0, y2 - patch_size)\n            x1 = max(0, x2 - patch_size)\n            patch = image[y1:y2, x1:x2, :]\n            patches.append(patch)\n    return patches\n\ndef compute_embeddings(model_path, df):\n    print(f\"Computing Embeddings for {len(df)} images...\")\n    # FIX: Use single GPU for SigLIP to ensure get_image_features works\n    model = AutoModel.from_pretrained(model_path, local_files_only=True).eval().to(cfg.DEVICE)\n    processor = AutoImageProcessor.from_pretrained(model_path)\n    \n    EMBEDDINGS = []\n    for _, row in tqdm(df.iterrows(), total=len(df)):\n        try:\n            img = cv2.imread(row['image_path'])\n            if img is None: raise ValueError(\"Image not found\")\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n            patches = split_image(img, patch_size=cfg.PATCH_SIZE, overlap=cfg.OVERLAP)\n            images = [Image.fromarray(p) for p in patches]\n            \n            inputs = processor(images=images, return_tensors=\"pt\").to(cfg.DEVICE)\n            with torch.no_grad():\n                features = model.get_image_features(**inputs)\n            \n            avg_embed = features.mean(dim=0).cpu().numpy()\n            EMBEDDINGS.append(avg_embed)\n        except Exception as e:\n            print(f\"Error processing {row['image_path']}: {e}\")\n            EMBEDDINGS.append(np.zeros(1152))\n        \n    torch.cuda.empty_cache()\n    return np.stack(EMBEDDINGS)\n\ntrain_embeddings = compute_embeddings(cfg.SIGLIP_PATH, train_df)\ntest_embeddings = compute_embeddings(cfg.SIGLIP_PATH, test_df)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================================================\n# 4. SEMANTIC FEATURES (For SigLIP Stream)\n# =========================================================================================\ndef generate_semantic_features(image_embeddings_np, model_path):\n    print(\"Generating Semantic Features...\")\n    model = AutoModel.from_pretrained(model_path).to(cfg.DEVICE)\n    tokenizer = AutoTokenizer.from_pretrained(model_path)\n    \n    concept_groups = {\n        \"bare\": [\"bare soil\", \"dirt ground\", \"sparse vegetation\", \"exposed earth\"],\n        \"sparse\": [\"low density pasture\", \"thin grass\", \"short clipped grass\"],\n        \"medium\": [\"average pasture cover\", \"medium height grass\", \"grazed pasture\"],\n        \"dense\": [\"dense tall pasture\", \"thick grassy volume\", \"high biomass\", \"overgrown vegetation\"],\n        \"green\": [\"lush green vibrant pasture\", \"photosynthesizing leaves\", \"fresh growth\"],\n        \"dead\": [\"dry brown dead grass\", \"yellow straw\", \"senesced material\", \"standing hay\"],\n        \"clover\": [\"white clover\", \"trifolium repens\", \"broadleaf legume\", \"clover flowers\"],\n        \"grass\": [\"ryegrass\", \"blade-like leaves\", \"fescue\", \"grassy sward\"]\n    }\n    \n    concept_vectors = {}\n    with torch.no_grad():\n        for name, prompts in concept_groups.items():\n            inputs = tokenizer(prompts, padding=\"max_length\", return_tensors=\"pt\").to(cfg.DEVICE)\n            emb = model.get_text_features(**inputs)\n            emb = emb / emb.norm(p=2, dim=-1, keepdim=True)\n            concept_vectors[name] = emb.mean(dim=0, keepdim=True)\n            \n    img_tensor = torch.tensor(image_embeddings_np, dtype=torch.float32).to(cfg.DEVICE)\n    img_tensor = img_tensor / img_tensor.norm(p=2, dim=-1, keepdim=True)\n    \n    scores = {}\n    for name, vec in concept_vectors.items():\n        scores[name] = torch.matmul(img_tensor, vec.T).cpu().numpy().flatten()\n        \n    df_scores = pd.DataFrame(scores)\n    df_scores['ratio_greenness'] = df_scores['green'] / (df_scores['green'] + df_scores['dead'] + 1e-6)\n    df_scores['ratio_clover'] = df_scores['clover'] / (df_scores['clover'] + df_scores['grass'] + 1e-6)\n    df_scores['ratio_cover'] = (df_scores['dense'] + df_scores['medium']) / (df_scores['bare'] + df_scores['sparse'] + 1e-6)\n    \n    torch.cuda.empty_cache()\n    return df_scores.values\n\nall_emb = np.vstack([train_embeddings, test_embeddings])\nall_semantic = generate_semantic_features(all_emb, cfg.SIGLIP_PATH)\nsem_train = all_semantic[:len(train_df)]\nsem_test = all_semantic[len(train_df):]\n\nemb_cols = [f\"emb{i}\" for i in range(train_embeddings.shape[1])]\ntrain_feat_df = pd.concat([train_df, pd.DataFrame(train_embeddings, columns=emb_cols)], axis=1)\ntest_feat_df = pd.concat([test_df, pd.DataFrame(test_embeddings, columns=emb_cols)], axis=1)\n\nprint(f\"Train Features Shape: {train_feat_df.shape}\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================================================\n# 5. STREAM A: SIGLIP REGRESSION (Robust PLS Fix)\n# =========================================================================================\nclass SupervisedEmbeddingEngine:\n    def __init__(self, n_pca=0.80, n_pls=8, n_gmm=6, random_state=42):\n        self.scaler = StandardScaler()\n        self.pca = PCA(n_components=n_pca, random_state=random_state)\n        self.pls = PLSRegression(n_components=n_pls, scale=False)\n        self.gmm = GaussianMixture(n_components=n_gmm, covariance_type='diag', random_state=random_state)\n        self.pls_fitted_ = False\n\n    def fit(self, X, y=None, X_semantic=None):\n        X_scaled = self.scaler.fit_transform(X)\n        self.pca.fit(X_scaled)\n        try:\n            self.gmm.fit(X_scaled)\n        except Exception:\n            pass # GMM might fail on tiny datasets, ignore if so\n            \n        if y is not None:\n            # FIX: Robust PLS fitting to handle SVD convergence errors\n            try:\n                self.pls.fit(X_scaled, y)\n                self.pls_fitted_ = True\n            except Exception as e:\n                print(f\"Warning: PLS fit failed ({e}). Skipping PLS features for this fold.\")\n                self.pls_fitted_ = False\n        return self\n\n    def transform(self, X, X_semantic=None):\n        X_scaled = self.scaler.transform(X)\n        features = [self.pca.transform(X_scaled)]\n        if self.pls_fitted_:\n            features.append(self.pls.transform(X_scaled))\n        features.append(self.gmm.predict_proba(X_scaled))\n        if X_semantic is not None:\n            sem_norm = (X_semantic - np.mean(X_semantic, axis=0)) / (np.std(X_semantic, axis=0) + 1e-6)\n            features.append(sem_norm)\n        return np.hstack(features)\n\ndef cross_validate_predict(model_cls, model_params, train_data, test_data, sem_tr, sem_te, feature_engine):\n    target_max_arr = np.array([cfg.TARGET_MAX[t] for t in cfg.TARGET_NAMES], dtype=float)\n    y_pred_test_accum = np.zeros([len(test_data), len(cfg.TARGET_NAMES)], dtype=float)\n    n_splits = int(train_data['fold'].nunique())\n    \n    X_train_full = train_data[emb_cols].values.astype(np.float32)\n    X_test_raw = test_data[emb_cols].values.astype(np.float32)\n    y_train_full = train_data[cfg.TARGET_NAMES].values.astype(np.float32)\n    \n    for fold in range(n_splits):\n        print(f\"Processing Fold {fold}...\")\n        train_mask = train_data['fold'] != fold\n        X_tr = X_train_full[train_mask]\n        y_tr = y_train_full[train_mask] / target_max_arr\n        sem_tr_fold = sem_tr[train_mask]\n        \n        engine = deepcopy(feature_engine)\n        engine.fit(X_tr, y=y_tr, X_semantic=sem_tr_fold)\n        \n        x_tr_eng = engine.transform(X_tr, X_semantic=sem_tr_fold)\n        x_te_eng = engine.transform(X_test_raw, X_semantic=sem_te)\n        \n        fold_test_pred = np.zeros([len(test_data), len(cfg.TARGET_NAMES)])\n        \n        for k in range(len(cfg.TARGET_NAMES)):\n            target_name = cfg.TARGET_NAMES[k]\n            if target_name == 'Dry_Clover_g':\n                fold_test_pred[:, k] = 0.0\n            else:\n                model = model_cls(**model_params)\n                model.fit(x_tr_eng, y_tr[:, k])\n                pred_raw = model.predict(x_te_eng)\n                fold_test_pred[:, k] = pred_raw * target_max_arr[k]\n            \n        y_pred_test_accum += fold_test_pred\n        \n    return y_pred_test_accum / n_splits\n\nparams_cat = {'iterations': 1900, 'learning_rate': 0.045, 'depth': 4, 'l2_leaf_reg': 0.56, 'verbose': 0, 'random_state': 42}\nparams_xgb = {'n_estimators': 1354, 'learning_rate': 0.010, 'max_depth': 3, 'subsample': 0.60, 'random_state': 42}\nparams_lgbm = {'n_estimators': 807, 'learning_rate': 0.014, 'num_leaves': 48, 'verbose': -1, 'random_state': 42}\nparams_hist = {'max_iter': 300, 'learning_rate': 0.05, 'l2_regularization': 0.44, 'random_state': 42}\n\nfeat_engine = SupervisedEmbeddingEngine(n_pca=0.80, n_pls=8, n_gmm=6)\n\nprint(\"Training Models...\")\npred_hist = cross_validate_predict(HistGradientBoostingRegressor, params_hist, train_feat_df, test_feat_df, sem_train, sem_test, feat_engine)\npred_gb = cross_validate_predict(GradientBoostingRegressor, params_xgb, train_feat_df, test_feat_df, sem_train, sem_test, feat_engine)\npred_cat = cross_validate_predict(CatBoostRegressor, params_cat, train_feat_df, test_feat_df, sem_train, sem_test, feat_engine)\npred_lgbm = cross_validate_predict(LGBMRegressor, params_lgbm, train_feat_df, test_feat_df, sem_train, sem_test, feat_engine)\n\nfinal_pred = (pred_hist + pred_gb + pred_cat + pred_lgbm) / 4.0\ntest_feat_df[cfg.TARGET_NAMES] = final_pred\ntest_processed = post_process_biomass(test_feat_df)\nsub_df = melt_table(test_processed)\nsub_df.to_csv(\"submission_siglip.csv\", index=False)\nprint(f\"✓ Siglip submission generated\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================================================\n# 6. STREAM B: DINO INFERENCE (Dual GPU via DataParallel)\n# =========================================================================================\ninference_script = r\"\"\"\nfrom PIL import Image\nimport pandas as pd\nimport numpy as np\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms as T\nfrom pathlib import Path\nimport timm\nimport warnings \nimport os\nwarnings.filterwarnings('ignore')\n\nclass RegressionDataset(Dataset):\n    def __init__(self, data, transform=None):\n        self.data = data\n        self.transform = transform\n    def __len__(self): return self.data.shape[0]\n    def __getitem__(self, idx):\n        item = self.data.iloc[idx]\n        image = item.image\n        targets = [0.0, 0.0, 0.0]\n        width, height = image.size\n        mid_point = width // 2\n        left_image = image.crop((0, 0, mid_point, height))\n        right_image = image.crop((mid_point, 0, width, height))\n        if self.transform is not None:\n            left_image = self.transform(left_image)\n            right_image = self.transform(right_image)\n        return left_image, right_image, targets\n\ndef get_test_dataloaders(data, image_size, batch_size):\n    res = []\n    for trans in [None, T.RandomHorizontalFlip(p=1.0), T.RandomVerticalFlip(p=1.0)]:\n        base = [T.Resize(image_size), T.ToTensor(), T.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])]\n        if trans: base.insert(1, trans)\n        transform = T.Compose(base)\n        dataset = RegressionDataset(data, transform=transform)\n        # 4 workers per GPU usually safe\n        res.append(DataLoader(dataset, batch_size=batch_size, shuffle=False, num_workers=4))\n    return res\n\nclass FiLM(nn.Module):\n    def __init__(self, feat_dim):\n        super().__init__()\n        self.mlp = nn.Sequential(nn.Linear(feat_dim, feat_dim // 2), nn.ReLU(inplace=True), nn.Linear(feat_dim // 2, feat_dim * 2))\n    def forward(self, context):\n        gamma_beta = self.mlp(context)\n        return torch.chunk(gamma_beta, 2, dim=1)\n\nclass CSIROModelRegressor(nn.Module):\n    def __init__(self, model_name, pretrained=True, num_classes=3, dropout=0.0):\n        super().__init__()\n        self.backbone = timm.create_model(model_name, pretrained=pretrained, num_classes=0, global_pool='avg')\n        self.film = FiLM(self.backbone.num_features)\n        def make_head(): return nn.Sequential(nn.Linear(self.backbone.num_features * 2, 8), nn.ReLU(inplace=True), nn.Dropout(dropout), nn.Linear(8, 1))\n        self.head_green = make_head()\n        self.head_clover = make_head()\n        self.head_dead = make_head()\n        self.softplus = nn.Softplus(beta=1.0)\n    def forward(self, left_img, right_img):\n        left_feat = self.backbone(left_img)\n        right_feat = self.backbone(right_img)\n        context = (left_feat + right_feat) / 2\n        gamma, beta = self.film(context)\n        left_feat_modulated = left_feat * (1 + gamma) + beta\n        right_feat_modulated = right_feat * (1 + gamma) + beta\n        combined = torch.cat([left_feat_modulated, right_feat_modulated], dim=1)\n        green = self.softplus(self.head_green(combined))\n        clover = self.softplus(self.head_clover(combined))\n        dead = self.softplus(self.head_dead(combined))\n        return torch.cat([green, clover, dead], dim=1)\n\ndef predict_folds(dataloaders, models_dir, model_name):\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    print(f'Inference using {torch.cuda.device_count()} GPUs')\n    \n    all_preds = []\n    for model_file in Path(models_dir).glob('*.pth'):\n        model = CSIROModelRegressor(model_name, pretrained=False, num_classes=3)\n        model.load_state_dict(torch.load(model_file, map_location=device))\n        \n        # Dual GPU Support\n        if torch.cuda.device_count() > 1:\n            model = nn.DataParallel(model)\n            \n        model.to(device)\n        model.eval()\n        loader_preds = []\n        for loader in dataloaders:\n            batch_preds = []\n            with torch.no_grad():\n                for l, r, _ in loader:\n                    out = model(l.to(device), r.to(device))\n                    batch_preds.append(out.cpu())\n            loader_preds.append(torch.cat(batch_preds).numpy())\n        all_preds.append(np.mean(loader_preds, axis=0))\n    return np.mean(all_preds, axis=0)\n\nCFG_PATH = \"/kaggle/input/csiro-biomass/\"\nTEST_PATH = CFG_PATH + \"test.csv\"\nMODEL_DIR = '/kaggle/input/modelv3/pytorch/default/1/models_retrained'\nMODEL_NAME = \"vit_large_patch16_dinov3_qkvb\"\n\ntest_df = pd.read_csv(TEST_PATH)\ntest_df[['sample_id_prefix', 'suffix']] = test_df.sample_id.str.split('__', expand=True)\ntest_data = test_df.drop_duplicates(subset=['image_path']).reset_index(drop=True)\ntest_data['image'] = test_data.image_path.apply(lambda p: Image.open(CFG_PATH + p).convert('RGB'))\n\n# Batch size 32 is safe for T4x2\nloaders = get_test_dataloaders(test_data, (512,512), 32)\npreds = predict_folds(loaders, MODEL_DIR, MODEL_NAME)\n\ncols = ['Dry_Green_g', 'Dry_Clover_g', 'Dry_Dead_g']\ntest_data[cols] = preds\ntest_data['GDM_g'] = test_data['Dry_Green_g'] + test_data['Dry_Clover_g']\ntest_data['Dry_Total_g'] = test_data['GDM_g'] + test_data['Dry_Dead_g']\n\nlong_df = test_data.melt(id_vars=['image_path'], value_vars=cols + ['GDM_g', 'Dry_Total_g'], var_name='target_name', value_name='target')\nlong_df['sample_id'] = long_df.apply(lambda r: r['image_path'].split('/')[-1].replace('.jpg','') + '__' + r['target_name'], axis=1)\nlong_df[['sample_id', 'target']].to_csv('submission_dinov2026.csv', index=False)\n\"\"\"\n\nwith open(\"csiro_infer.py\", \"w\") as f:\n    f.write(inference_script)\n\nprint(\"Running DINO Inference...\")\nos.system(\"python csiro_infer.py\")\nprint(\"✓ DINO submission generated\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================================================\n# 7. STREAM C: INDEPENDENT KNN RETRIEVAL\n# =========================================================================================\ndef generate_independent_knn(train_emb, test_emb, train_targets, k=5):\n    print(f\"Running Hybrid KNN Retrieval (k={k})...\")\n    \n    # Normalize\n    train_norm = train_emb / np.linalg.norm(train_emb, axis=1, keepdims=True)\n    test_norm = test_emb / np.linalg.norm(test_emb, axis=1, keepdims=True)\n    \n    # Similarity\n    sim_test = np.dot(test_norm, train_norm.T)\n    \n    # KNN Predictions\n    knn_preds = np.zeros((len(test_emb), train_targets.shape[1]))\n    \n    for i in range(len(test_emb)):\n        scores = sim_test[i]\n        indices = np.argsort(scores)[-k:]\n        \n        # Weights\n        final_scores = scores[indices]\n        weights = np.exp(final_scores * 15)\n        weights /= weights.sum()\n        \n        knn_preds[i] = np.average(train_targets[indices], axis=0, weights=weights)\n        \n    return knn_preds\n\ny_train_arr = train_df[cfg.TARGET_NAMES].values\nknn_values = generate_independent_knn(train_embeddings, test_embeddings, y_train_arr, k=cfg.KNN_K)\n\ndf_knn = pd.DataFrame(knn_values, columns=cfg.TARGET_NAMES)\ndf_knn['image_path'] = test_df['image_path']\ndf_knn = post_process_biomass(df_knn)\nsub_knn = melt_table(df_knn)\nsub_knn.to_csv(\"submission_knn.csv\", index=False)\nprint(\"✓ KNN submission generated\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# =========================================================================================\n# 8. FINAL TRI-STREAM ENSEMBLE\n# =========================================================================================\nprint(\"Ensembling...\")\nsub_siglip = pd.read_csv(\"submission_siglip.csv\").sort_values(\"sample_id\").reset_index(drop=True)\nsub_dino = pd.read_csv(\"submission_dinov2026.csv\").sort_values(\"sample_id\").reset_index(drop=True)\nsub_knn = pd.read_csv(\"submission_knn.csv\").sort_values(\"sample_id\").reset_index(drop=True)\n\n# 0.60 DINO + 0.30 SigLIP + 0.10 KNN\nfinal_target = (sub_dino['target'] * 0.60) + (sub_siglip['target'] * 0.30) + (sub_knn['target'] * 0.10)\n\nsubmission = pd.DataFrame({\n    'sample_id': sub_siglip['sample_id'],\n    'target': final_target\n})\n\n# Final Clip\nsubmission['target'] = submission['target'].clip(lower=0.0)\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"✓ Final submission.csv saved!\")\nprint(submission.head())","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}