{"metadata":{"kernelspec":{"name":"python3","display_name":"Python 3","language":"python"},"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":13900620,"sourceType":"datasetVersion","datasetId":8856212},{"sourceId":14018229,"sourceType":"datasetVersion","datasetId":8929818},{"sourceId":288467413,"sourceType":"kernelVersion"},{"sourceId":4535,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":3327,"modelId":986},{"sourceId":4537,"sourceType":"modelInstanceVersion","modelInstanceId":3329,"modelId":986},{"sourceId":268942,"sourceType":"modelInstanceVersion","modelInstanceId":230141,"modelId":251887},{"sourceId":663314,"sourceType":"modelInstanceVersion","modelInstanceId":471723,"modelId":487624},{"sourceId":672761,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":509765,"modelId":524407}],"dockerImageVersionId":31236,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"c1bd9ce1","cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"id":"ee30c11a","cell_type":"code","source":"import os\nimport gc\nimport math\nimport json\nimport random\nimport argparse\nimport shutil\nfrom copy import deepcopy\nfrom dataclasses import dataclass, field\nfrom typing import Optional, List, Tuple, Dict\nfrom pathlib import Path\nfrom glob import glob\n\nimport cv2\nimport timm\nimport numpy as np\nimport pandas as pd\nimport albumentations as A\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nfrom torch.utils.data import Dataset, DataLoader\nfrom torch.optim import AdamW\nfrom torch.optim.lr_scheduler import CosineAnnealingLR\nfrom torch.cuda.amp import autocast, GradScaler\nfrom albumentations.pytorch import ToTensorV2\nfrom PIL import Image\nfrom tqdm.auto import tqdm\n\nfrom sklearn.model_selection import KFold\nfrom sklearn.linear_model import LinearRegression, Ridge, Lasso, BayesianRidge, ElasticNet\nfrom sklearn.svm import SVR, LinearSVR\nfrom sklearn.ensemble import GradientBoostingRegressor, HistGradientBoostingRegressor, ExtraTreesRegressor\nfrom sklearn.dummy import DummyRegressor\nfrom sklearn.decomposition import PCA\nfrom sklearn.cross_decomposition import PLSRegression\nfrom sklearn.mixture import GaussianMixture\nfrom sklearn import preprocessing\nfrom sklearn.metrics.pairwise import cosine_similarity\nfrom sklearn.base import BaseEstimator, TransformerMixin\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.cluster import MiniBatchKMeans\n\nfrom xgboost import XGBRegressor\nfrom lightgbm import LGBMRegressor\nfrom catboost import CatBoostRegressor\n\nfrom transformers import AutoProcessor, AutoImageProcessor, AutoModel, AutoTokenizer\ntry:\n    from google.protobuf import message_factory as _pb_message_factory\n    from google.protobuf.internal import message_factory as _pb_internal_factory\n    def _ensure_getprototype(mf):\n        if not hasattr(mf, 'GetPrototype'):\n            def _getprototype(self, descriptor):\n                return self.GetMessageClass(descriptor)\n            mf.GetPrototype = _getprototype\n    _ensure_getprototype(_pb_message_factory.MessageFactory)\n    _ensure_getprototype(_pb_internal_factory.MessageFactory)\nexcept Exception:\n    pass","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.396925Z","iopub.execute_input":"2026-01-08T13:15:07.397150Z","iopub.status.idle":"2026-01-08T13:15:07.406115Z","shell.execute_reply.started":"2026-01-08T13:15:07.397126Z","shell.execute_reply":"2026-01-08T13:15:07.405463Z"}},"outputs":[],"execution_count":null},{"id":"ac3d2059","cell_type":"code","source":"def 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.cuda.manual_seed_all(SEED)\n        torch.backends.cudnn.deterministic = True\n        torch.backends.cudnn.benchmark = False\n\ndef flush():\n    gc.collect()\n    if torch.cuda.is_available():\n        torch.cuda.empty_cache()\n        torch.cuda.reset_peak_memory_stats()\n\ndef _fast_mode_flag() -> bool:\n    try:\n        return bool(int(os.environ.get(\"FAST_MODE\", \"0\")))\n    except ValueError:\n        return False\n\n\n@dataclass\nclass Config:\n    DATA_PATH: Path = Path(\"/kaggle/input/csiro-biomass/\")\n    TRAIN_DATA_PATH: Path = DATA_PATH/'train'\n    TEST_DATA_PATH: Path = DATA_PATH/'test'\n    device = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n    seed = 42\n    stack_kfolds: int = 10\n    legacy_kfolds: int = 8\n    dino_kfolds: int = 10\n    siglip_hybrid_weight: float = 0.5\n    # Execution controls / staging\n    run_siglip_stack: bool = True\n    run_siglip_legacy: bool = True\n    run_dino1: bool = True\n    run_mvp: bool = True\n    run_dinov2: bool = True\n    # Fast-mode / performance controls\n    fast_mode: bool = field(default_factory=_fast_mode_flag)\n    use_semantic_features: bool = True\n    siglip_cluster_sizes: Tuple[int, ...] = (12, 24, 36, 48, 64)\n    dino_cluster_sizes: Tuple[int, ...] = (16, 32, 48, 64, 80)\n    siglip_fast_models: Tuple[str, ...] = field(default_factory=tuple)\n    dino_fast_models: Tuple[str, ...] = field(default_factory=tuple)\n    enable_legacy_siglip: bool = True\n\n    def __post_init__(self):\n        if self.fast_mode:\n            self.stack_kfolds = min(self.stack_kfolds, 5)\n            self.legacy_kfolds = min(self.legacy_kfolds, 4)\n            self.dino_kfolds = min(self.dino_kfolds, 5)\n            self.siglip_cluster_sizes = (16, 32)\n            self.dino_cluster_sizes = (24, 48)\n            self.use_semantic_features = False\n            self.enable_legacy_siglip = False\n            self.run_siglip_legacy = False\n            # Keep MVP for final blend; drop Dinov2 to shorten runtime\n            self.run_dinov2 = False\n            # SigLIP stack dominates in fast mode\n            self.siglip_fast_models = (\n                \"cat_seed0\",\n                \"svr_seed0\",\n                \"xgb_seed0\",\n                \"bayes_ridge\",\n                \"elastic_net\",\n            )\n            self.dino_fast_models = (\"dinov2_cat\", \"dinov2_xgb\")\n\ncfg = Config()\nseeding(cfg.seed)\n\n\ndef unwrap_model(model):\n    return model.module if isinstance(model, torch.nn.DataParallel) else model\n\ndef maybe_parallelize(model, device=None, min_batch=2):\n    if device is not None and hasattr(model, 'to'):\n        model.to(device)\n    use_multi = (\n        torch.cuda.is_available()\n        and torch.cuda.device_count() > 1\n        and (min_batch is None or min_batch >= torch.cuda.device_count())\n    )\n    if use_multi:\n        model = torch.nn.DataParallel(model)\n    return model\n\n\ndef assign_folds(df: pd.DataFrame, n_splits: int, seed: int):\n    fold_assign = df[['image_path']].drop_duplicates().reset_index(drop=True)\n    fold_assign['fold'] = -1\n    kf = KFold(n_splits=n_splits, shuffle=True, random_state=seed)\n    for fold, (_, val_idx) in enumerate(kf.split(fold_assign)):\n        fold_assign.loc[val_idx, 'fold'] = fold\n    df = df.drop(columns=['fold'], errors='ignore').merge(fold_assign, on='image_path', how='left')\n    df['fold'] = df['fold'].astype(int)\n    return df\n\n\ndef ensure_abs_path(path_str: str) -> str:\n    path_str = str(path_str)\n    if path_str.startswith('/') or path_str.startswith('\\\\'):\n        return path_str\n    return str(cfg.DATA_PATH / path_str)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.407406Z","iopub.execute_input":"2026-01-08T13:15:07.407836Z","iopub.status.idle":"2026-01-08T13:15:07.440855Z","shell.execute_reply.started":"2026-01-08T13:15:07.407812Z","shell.execute_reply":"2026-01-08T13:15:07.440188Z"}},"outputs":[],"execution_count":null},{"id":"fff6e6ec","cell_type":"code","source":"TARGET_NAMES = ['Dry_Clover_g', 'Dry_Dead_g', 'Dry_Green_g', 'Dry_Total_g', 'GDM_g']\nweights = {\n    'Dry_Green_g': 0.1,\n    'Dry_Dead_g': 0.1,\n    'Dry_Clover_g': 0.1,\n    'GDM_g': 0.2,\n    'Dry_Total_g': 0.5,\n}\nTARGET_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\ndef competition_metric(y_true, y_pred) -> float:\n    y_weighted = 0\n    for l, label in enumerate(TARGET_NAMES):\n        y_weighted = y_weighted + y_true[:, l].mean() * weights[label]\n    ss_res = 0\n    ss_tot = 0\n    for l, label in enumerate(TARGET_NAMES):\n        ss_res = ss_res + ((y_true[:, l] - y_pred[:, l])**2).mean() * weights[label]\n        ss_tot = ss_tot + ((y_true[:, l] - y_weighted)**2).mean() * weights[label]\n    return 1 - ss_res / ss_tot\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=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', 'image_path', 'target_name', '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    Y = df_preds[ordered_cols].values.T\n    C = np.array([[1, 1, 0, -1,  0], [0, 0, 1,  1, -1]])\n    C_T = C.T\n    inv_CCt = np.linalg.inv(C @ C_T)\n    P = np.eye(5) - C_T @ inv_CCt @ C\n    Y_reconciled = P @ Y\n    Y_reconciled = Y_reconciled.T.clip(min=0)\n    df_out = df_preds.copy()\n    df_out[ordered_cols] = Y_reconciled\n    return df_out\n\ndef split_image(image, patch_size=520, overlap=16):\n    h, w, c = image.shape\n    stride = patch_size - overlap\n    patches, coords = [], []\n    for y in range(0, h, stride):\n        for x in range(0, w, stride):\n            y1, x1, y2, x2 = y, x, y + patch_size, x + patch_size\n            patch = image[y1:y2, x1:x2, :]\n            if patch.shape[0] < patch_size or patch.shape[1] < patch_size:\n                pad_h = patch_size - patch.shape[0]\n                pad_w = patch_size - patch.shape[1]\n                patch = np.pad(patch, ((0,pad_h), (0,pad_w), (0,0)), mode='reflect')\n            patches.append(patch)\n            coords.append((y1, x1, y2, x2))\n    return patches, coords\n\ndef get_model(model_path: str, device: str = 'cpu'):\n    model_lower = model_path.lower()\n    if 'clip' in model_lower:\n        from transformers import CLIPModel, CLIPImageProcessor\n        model = CLIPModel.from_pretrained(model_path, local_files_only=True)\n        try:\n            processor = CLIPImageProcessor.from_pretrained(model_path, local_files_only=True)\n        except OSError:\n            processor = CLIPImageProcessor.from_dict({\n                'do_resize': True,\n                'size': {'shortest_edge': 336},\n                'resample': 3,\n                'do_center_crop': True,\n                'crop_size': {'height': 336, 'width': 336},\n                'do_normalize': True,\n                'image_mean': [0.48145466, 0.4578275, 0.40821073],\n                'image_std': [0.26862954, 0.26130258, 0.27577711],\n            })\n    else:\n        model = AutoModel.from_pretrained(model_path, local_files_only=True)\n        processor = AutoImageProcessor.from_pretrained(\n            model_path, use_fast=True, local_files_only=True\n        )\n    return model.eval().to(device), processor\n\ndef compute_embeddings(model_path, df, patch_size=520):\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n    model, processor = get_model(model_path=model_path, device=device)\n    IMAGE_PATHS, EMBEDDINGS = [], []\n    for i, row in tqdm(df.iterrows(), total=len(df)):\n        img_path = row['image_path']\n        img = cv2.imread(img_path, cv2.IMREAD_COLOR)\n        patches, coords = split_image(img, patch_size=patch_size)\n        images = [Image.fromarray(p).convert(\"RGB\") for p in patches]\n        inputs = processor(images=images, return_tensors=\"pt\").to(model.device)\n        with torch.no_grad():\n            model_lower = model_path.lower()\n            if 'siglip' in model_lower or 'clip' in model_lower:\n                if hasattr(model, 'get_image_features'):\n                    features = model.get_image_features(**inputs, interpolate_pos_encoding=True)\n                else:\n                    outputs = model(**inputs)\n                    if hasattr(outputs, 'last_hidden_state'):\n                        features = outputs.last_hidden_state.mean(dim=1)\n                    else:\n                        features = outputs[0].mean(dim=1)\n            elif 'dino' in model_lower:\n                outputs = model(**inputs)\n                if hasattr(outputs, 'pooler_output'):\n                    features = outputs.pooler_output\n                else:\n                    features = outputs[0]\n            else:\n                outputs = model(**inputs)\n                if hasattr(outputs, 'pooler_output'):\n                    features = outputs.pooler_output\n                elif hasattr(outputs, 'last_hidden_state'):\n                    features = outputs.last_hidden_state.mean(dim=1)\n                else:\n                    features = outputs[0]\n\n        embeds = features.mean(dim=0).detach().cpu().numpy()\n        EMBEDDINGS.append(embeds)\n        IMAGE_PATHS.append(img_path)\n    embeddings = np.stack(EMBEDDINGS, axis=0)\n    n_features = embeddings.shape[1]\n    emb_columns = [f\"emb{i+1}\" for i in range(n_features)]\n    emb_df = pd.DataFrame(embeddings, columns=emb_columns)\n    emb_df['image_path'] = IMAGE_PATHS\n    df_final = df.merge(emb_df, on='image_path', how='left')\n    flush()\n    return df_final\n\ndef generate_semantic_features(image_embeddings, model_path):\n    device = 'cuda' if torch.cuda.is_available() else 'cpu'\n    try:\n        model = AutoModel.from_pretrained(\n            model_path, local_files_only=True\n        ).to(device)\n        tokenizer = AutoTokenizer.from_pretrained(\n            model_path, use_fast=True, local_files_only=True\n        )\n    except Exception as e:\n        return None\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        \"weeds\": [\"broadleaf weeds\", \"thistles\", \"non-pasture vegetation\"]\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(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    if isinstance(image_embeddings, np.ndarray):\n        img_tensor = torch.tensor(image_embeddings, dtype=torch.float32).to(device)\n    else:\n        img_tensor = image_embeddings.to(device)\n    img_tensor = img_tensor / img_tensor.norm(p=2, dim=-1, keepdim=True)\n    scores = {}\n    for name, vec in concept_vectors.items():\n        scores[name] = torch.matmul(img_tensor, vec.T).cpu().numpy().flatten()\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    df_scores['max_density'] = df_scores[['bare', 'sparse', 'medium', 'dense']].max(axis=1)\n    return df_scores.values\n\n\ndef append_cluster_features(train_df, test_df, embed_prefix='emb', cluster_sizes=(12, 24, 36, 48, 64), random_state=42):\n    embed_cols = [c for c in train_df.columns if c.startswith(embed_prefix)]\n    if not embed_cols:\n        return train_df, test_df\n    train_emb = train_df[embed_cols].values.astype(np.float32, copy=False)\n    test_emb = test_df[embed_cols].values.astype(np.float32, copy=False)\n    stacked = np.vstack([train_emb, test_emb]).astype(np.float32, copy=False)\n    if len(stacked) == 0:\n        return train_df, test_df\n    batch_size = min(4096, max(256, stacked.shape[0]))\n    for n_clusters in cluster_sizes:\n        if not isinstance(n_clusters, int) or n_clusters <= 0:\n            continue\n        km = MiniBatchKMeans(\n            n_clusters=n_clusters,\n            batch_size=batch_size,\n            random_state=random_state,\n            n_init=10,\n        )\n        km.fit(stacked)\n        train_df[f\"meta_cluster_{n_clusters}\"] = km.predict(train_emb)\n        test_df[f\"meta_cluster_{n_clusters}\"] = km.predict(test_emb)\n    return train_df, test_df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.499656Z","iopub.execute_input":"2026-01-08T13:15:07.500141Z","iopub.status.idle":"2026-01-08T13:15:07.528368Z","shell.execute_reply.started":"2026-01-08T13:15:07.500119Z","shell.execute_reply":"2026-01-08T13:15:07.527758Z"}},"outputs":[],"execution_count":null},{"id":"a79572d1","cell_type":"code","source":"class SupervisedEmbeddingEngine(BaseEstimator, TransformerMixin):\n    def __init__(self, n_pca=0.98, n_pls=8, n_gmm=5, random_state=42):\n        self.n_pca = n_pca\n        self.n_pls = n_pls\n        self.n_gmm = n_gmm\n        self.random_state = random_state\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        self.gmm.fit(X_scaled)\n        if y is not None:\n            y_clean = y.values if hasattr(y, 'values') else y\n            y_array = np.asarray(y_clean)\n            if y_array.ndim == 1:\n                y_array = y_array.reshape(-1, 1)\n            rank_bound = min(max(1, X_scaled.shape[0] - 1), X_scaled.shape[1], y_array.shape[1])\n            n_comp = min(self.n_pls, rank_bound)\n            if n_comp >= 1:\n                if getattr(self.pls, 'n_components', None) != n_comp:\n                    self.pls = PLSRegression(n_components=n_comp, scale=False)\n                self.pls.fit(X_scaled, y_array)\n                self.pls_fitted_ = True\n            else:\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        return self._generate_features(X_scaled, X_semantic)\n\n    def _generate_features(self, X_scaled, X_semantic=None):\n        features = []\n        f_pca = self.pca.transform(X_scaled)\n        features.append(f_pca)\n        if self.pls_fitted_:\n            f_pls = self.pls.transform(X_scaled)\n            features.append(f_pls)\n        f_gmm = self.gmm.predict_proba(X_scaled)\n        features.append(f_gmm)\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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.529560Z","iopub.execute_input":"2026-01-08T13:15:07.530092Z","iopub.status.idle":"2026-01-08T13:15:07.552097Z","shell.execute_reply.started":"2026-01-08T13:15:07.530050Z","shell.execute_reply":"2026-01-08T13:15:07.551382Z"}},"outputs":[],"execution_count":null},{"id":"761d771e","cell_type":"code","source":"def compare_results(oof, train_data, return_scores=False):\n    y_oof_df = pd.DataFrame(oof, columns=TARGET_NAMES)\n    raw_score = competition_metric(train_data[TARGET_NAMES].values, y_oof_df.values)\n    print(f\"Raw CV Score: {raw_score:.6f}\")\n    y_oof_proc = post_process_biomass(y_oof_df)\n    proc_score = competition_metric(train_data[TARGET_NAMES].values, y_oof_proc.values)\n    print(f\"Processed CV Score: {proc_score:.6f}\")\n    print(f\"Weight for blend (processed): {proc_score:.6f}\")\n    print(f\"Improvement: {raw_score - proc_score:.6f}\")\n    if return_scores:\n        return raw_score, proc_score\n\ndef cross_validate(model, train_data, test_data, feature_engine, semantic_train=None, semantic_test=None, target_transform='max', seed=42, n_splits_override=None):\n    n_splits = n_splits_override or train_data['fold'].nunique()\n    target_max_arr = np.array([TARGET_MAX[t] for t in TARGET_NAMES], dtype=float)\n    y_true = train_data[TARGET_NAMES]\n    y_pred = pd.DataFrame(0.0, index=train_data.index, columns=TARGET_NAMES)\n    y_pred_test = np.zeros([len(test_data), len(TARGET_NAMES)], dtype=float)\n\n    # Feature space: raw embeddings + cluster assignments\n    emb_columns = [col for col in train_data.columns if col.startswith('emb')]\n    cluster_columns = [col for col in train_data.columns if col.startswith('meta_cluster_')]\n    COLUMNS = emb_columns + cluster_columns\n\n    for fold in range(n_splits):\n        seeding(seed*(seed//2 + fold))\n        train_mask = train_data['fold'] != fold\n        valid_mask = train_data['fold'] == fold\n        val_idx = train_data[valid_mask].index\n        X_train_raw = train_data[train_mask][COLUMNS].values\n        X_valid_raw = train_data[valid_mask][COLUMNS].values\n        X_test_raw = test_data[COLUMNS].values\n        sem_train_fold = semantic_train[train_mask] if semantic_train is not None else None\n        sem_valid_fold = semantic_train[valid_mask] if semantic_train is not None else None\n        y_train = train_data[train_mask][TARGET_NAMES].values\n        y_valid = train_data[valid_mask][TARGET_NAMES].values\n        if target_transform == 'log':\n            y_train_proc = np.log1p(y_train)\n        elif target_transform == 'max':\n            y_train_proc = y_train / target_max_arr\n        else:\n            y_train_proc = y_train\n        engine = deepcopy(feature_engine)\n        engine.fit(X_train_raw, y=y_train_proc, X_semantic=sem_train_fold)\n        x_train_eng = engine.transform(X_train_raw, X_semantic=sem_train_fold)\n        x_valid_eng = engine.transform(X_valid_raw, X_semantic=sem_valid_fold)\n        x_test_eng = engine.transform(X_test_raw, X_semantic=semantic_test)\n        fold_valid_pred = np.zeros_like(y_valid)\n        fold_test_pred = np.zeros([len(test_data), len(TARGET_NAMES)])\n        for k in range(len(TARGET_NAMES)):\n            regr = deepcopy(model)\n            regr.fit(x_train_eng, y_train_proc[:, k])\n            pred_valid_raw = regr.predict(x_valid_eng)\n            pred_test_raw = regr.predict(x_test_eng)\n            if target_transform == 'log':\n                pred_valid_inv = np.expm1(pred_valid_raw)\n                pred_test_inv = np.expm1(pred_test_raw)\n            elif target_transform == 'max':\n                pred_valid_inv = (pred_valid_raw * target_max_arr[k])\n                pred_test_inv = (pred_test_raw * target_max_arr[k])\n            else:\n                pred_valid_inv = pred_valid_raw\n                pred_test_inv = pred_test_raw\n            fold_valid_pred[:, k] = pred_valid_inv\n            fold_test_pred[:, k] = pred_test_inv\n        y_pred.loc[val_idx] = fold_valid_pred\n        y_pred_test += fold_test_pred / n_splits\n    full_cv = competition_metric(y_true.values, y_pred.values)\n    print(f\"Full CV Score: {full_cv:.6f}\")\n    return y_pred.values, y_pred_test\n\nclass FeedForward(nn.Module):\n    def __init__(self, dim, mlp_ratio=4.0, dropout=0.0):\n        super().__init__()\n        hid = int(dim * mlp_ratio)\n        self.net = nn.Sequential(\n            nn.Linear(dim, hid), nn.GELU(), nn.Dropout(dropout),\n            nn.Linear(hid, dim), nn.Dropout(dropout))\n\n    def forward(self, x): return self.net(x)\n\nclass AttentionBlock(nn.Module):\n    def __init__(self, dim, heads=8, dropout=0.0, mlp_ratio=4.0):\n        super().__init__()\n        self.norm1 = nn.LayerNorm(dim)\n        self.attn = nn.MultiheadAttention(dim, heads, dropout=dropout, batch_first=True)\n        self.norm2 = nn.LayerNorm(dim)\n        self.ff = FeedForward(dim, mlp_ratio=mlp_ratio, dropout=dropout)\n\n    def forward(self, x):\n        h = self.norm1(x)\n        attn_out, _ = self.attn(h, h, h, need_weights=False)\n        x = x + attn_out\n        x = x + self.ff(self.norm2(x))\n        return x\n\nclass MobileViTBlock(nn.Module):\n    def __init__(self, dim, heads=4, depth=2, patch=(2, 2), dropout=0.0):\n        super().__init__()\n        self.local = nn.Sequential(\n            nn.Conv2d(dim, dim, 3, padding=1, groups=dim),\n            nn.Conv2d(dim, dim, 1), nn.GELU())\n        self.patch = patch\n        self.transformer = nn.ModuleList([AttentionBlock(dim, heads=heads, dropout=dropout, mlp_ratio=2.0) for _ in range(depth)])\n        self.fuse = nn.Conv2d(dim * 2, dim, kernel_size=1)\n\n    def forward(self, x: torch.Tensor):\n        local_feat = self.local(x)\n        B, C, H, W = local_feat.shape\n        ph, pw = self.patch\n        new_h = math.ceil(H / ph) * ph\n        new_w = math.ceil(W / pw) * pw\n        if new_h != H or new_w != W:\n            local_feat = F.interpolate(local_feat, size=(new_h, new_w), mode=\"bilinear\", align_corners=False)\n            H, W = new_h, new_w\n        tokens = local_feat.unfold(2, ph, ph).unfold(3, pw, pw)\n        tokens = tokens.contiguous().view(B, C, -1, ph, pw)\n        tokens = tokens.permute(0, 2, 3, 4, 1).reshape(B, -1, C)\n        for blk in self.transformer: tokens = blk(tokens)\n        feat = tokens.view(B, -1, ph * pw, C).permute(0, 3, 1, 2)\n        nh = H // ph\n        nw = W // pw\n        feat = feat.view(B, C, nh, nw, ph, pw).permute(0, 1, 2, 4, 3, 5)\n        feat = feat.reshape(B, C, H, W)\n        if feat.shape[-2:] != x.shape[-2:]:\n            feat = F.interpolate(feat, size=x.shape[-2:], mode=\"bilinear\", align_corners=False)\n        return self.fuse(torch.cat([x, feat], dim=1))\n\nclass SpatialReductionAttention(nn.Module):\n    def __init__(self, dim, heads=8, sr_ratio=2, dropout=0.0):\n        super().__init__()\n        self.heads = heads\n        self.scale = (dim // heads) ** -0.5\n        self.q = nn.Linear(dim, dim)\n        self.kv = nn.Linear(dim, dim * 2)\n        self.sr_ratio = sr_ratio\n        if sr_ratio > 1:\n            self.sr = nn.Conv2d(dim, dim, kernel_size=sr_ratio, stride=sr_ratio)\n            self.norm = nn.LayerNorm(dim)\n        else: self.sr = None\n        self.proj = nn.Linear(dim, dim)\n        self.drop = nn.Dropout(dropout)\n\n    def forward(self, x, hw: Tuple[int, int]):\n        B, N, C = x.shape\n        q = self.q(x).reshape(B, N, self.heads, C // self.heads).permute(0, 2, 1, 3)\n        if self.sr is not None:\n            H, W = hw\n            feat = x.transpose(1, 2).reshape(B, C, H, W)\n            feat = self.sr(feat)\n            feat = feat.reshape(B, C, -1).transpose(1, 2)\n            feat = self.norm(feat)\n        else: feat = x\n        kv = self.kv(feat)\n        k, v = kv.chunk(2, dim=-1)\n        k = k.reshape(B, -1, self.heads, C // self.heads).permute(0, 2, 3, 1)\n        v = v.reshape(B, -1, self.heads, C // self.heads).permute(0, 2, 1, 3)\n        attn = torch.matmul(q, k) * self.scale\n        attn = attn.softmax(dim=-1)\n        attn = self.drop(attn)\n        out = torch.matmul(attn, v).permute(0, 2, 1, 3).reshape(B, N, C)\n        return self.proj(out)\n\nclass PVTBlock(nn.Module):\n    def __init__(self, dim, heads=8, sr_ratio=2, dropout=0.0, mlp_ratio=4.0):\n        super().__init__()\n        self.norm1 = nn.LayerNorm(dim)\n        self.sra = SpatialReductionAttention(dim, heads=heads, sr_ratio=sr_ratio, dropout=dropout)\n        self.norm2 = nn.LayerNorm(dim)\n        self.ff = FeedForward(dim, mlp_ratio=mlp_ratio, dropout=dropout)\n\n    def forward(self, x, hw: Tuple[int, int]):\n        x = x + self.sra(self.norm1(x), hw)\n        x = x + self.ff(self.norm2(x))\n        return x\n\nclass LocalMambaBlock(nn.Module):\n    def __init__(self, dim, kernel_size=5, dropout=0.0):\n        super().__init__()\n        self.norm = nn.LayerNorm(dim)\n        self.dwconv = nn.Conv1d(dim, dim, kernel_size=kernel_size, padding=kernel_size//2, groups=dim)\n        self.gate = nn.Linear(dim, dim)\n        self.proj = nn.Linear(dim, dim)\n        self.drop = nn.Dropout(dropout)\n\n    def forward(self, x):\n        shortcut = x\n        x = self.norm(x)\n        g = torch.sigmoid(self.gate(x))\n        x = (x * g).transpose(1, 2)\n        x = self.dwconv(x).transpose(1, 2)\n        x = self.proj(x)\n        x = self.drop(x)\n        return shortcut + x\n\nclass T2TRetokenizer(nn.Module):\n    def __init__(self, dim, depth=2, heads=4, dropout=0.0):\n        super().__init__()\n        self.blocks = nn.ModuleList([AttentionBlock(dim, heads=heads, dropout=dropout, mlp_ratio=2.0) for _ in range(depth)])\n\n    def forward(self, tokens: torch.Tensor, grid_hw: Tuple[int, int]):\n        B, T, C = tokens.shape\n        H, W = grid_hw\n        feat_map = tokens.transpose(1, 2).reshape(B, C, H, W)\n        seq = feat_map.flatten(2).transpose(1, 2)\n        for blk in self.blocks: seq = blk(seq)\n        seq_map = seq.transpose(1, 2).reshape(B, C, H, W)\n        pooled = F.adaptive_avg_pool2d(seq_map, (2, 2))\n        retokens = pooled.flatten(2).transpose(1, 2)\n        return retokens, seq_map\n\nclass CrossScaleFusion(nn.Module):\n    def __init__(self, dim, heads=6, dropout=0.0, layers=2):\n        super().__init__()\n        self.layers_s = nn.ModuleList([AttentionBlock(dim, heads=heads, dropout=dropout, mlp_ratio=2.0) for _ in range(layers)])\n        self.layers_b = nn.ModuleList([AttentionBlock(dim, heads=heads, dropout=dropout, mlp_ratio=2.0) for _ in range(layers)])\n        self.cross_s = nn.ModuleList([nn.MultiheadAttention(dim, heads, dropout=dropout, batch_first=True, kdim=dim, vdim=dim) for _ in range(layers)])\n        self.cross_b = nn.ModuleList([nn.MultiheadAttention(dim, heads, dropout=dropout, batch_first=True, kdim=dim, vdim=dim) for _ in range(layers)])\n        self.norm_s = nn.LayerNorm(dim)\n        self.norm_b = nn.LayerNorm(dim)\n\n    def forward(self, tok_s: torch.Tensor, tok_b: torch.Tensor):\n        B, Ts, C = tok_s.shape\n        Tb = tok_b.shape[1]\n        cls_s = tok_s.new_zeros(B, 1, C)\n        cls_b = tok_b.new_zeros(B, 1, C)\n        tok_s = torch.cat([cls_s, tok_s], dim=1)\n        tok_b = torch.cat([cls_b, tok_b], dim=1)\n        for ls, lb, cs, cb in zip(self.layers_s, self.layers_b, self.cross_s, self.cross_b):\n            tok_s = ls(tok_s)\n            tok_b = lb(tok_b)\n            q_s = self.norm_s(tok_s[:, :1])\n            q_b = self.norm_b(tok_b[:, :1])\n            cls_s_upd, _ = cs(q_s, torch.cat([tok_b, q_b], dim=1), torch.cat([tok_b, q_b], dim=1), need_weights=False)\n            cls_b_upd, _ = cb(q_b, torch.cat([tok_s, q_s], dim=1), torch.cat([tok_s, q_s], dim=1), need_weights=False)\n            tok_s = torch.cat([tok_s[:, :1] + cls_s_upd, tok_s[:, 1:]], dim=1)\n            tok_b = torch.cat([tok_b[:, :1] + cls_b_upd, tok_b[:, 1:]], dim=1)\n        tokens = torch.cat([tok_s[:, :1], tok_b[:, :1], tok_s[:, 1:], tok_b[:, 1:]], dim=1)\n        return tokens\n\nclass TileEncoder(nn.Module):\n    def __init__(self, backbone: nn.Module, input_res: int):\n        super().__init__()\n        self.backbone = backbone\n        self.input_res = input_res\n\n    def forward(self, x: torch.Tensor, grid: Tuple[int, int]):\n        B, C, H, W = x.shape\n        r, c = grid\n        hs = torch.linspace(0, H, steps=r + 1, device=x.device).round().long()\n        ws = torch.linspace(0, W, steps=c + 1, device=x.device).round().long()\n        tiles = []\n        for i in range(r):\n            for j in range(c):\n                rs, re = hs[i].item(), hs[i + 1].item()\n                cs, ce = ws[j].item(), ws[j + 1].item()\n                xt = x[:, :, rs:re, cs:ce]\n                if xt.shape[-2:] != (self.input_res, self.input_res):\n                    xt = F.interpolate(xt, size=(self.input_res, self.input_res), mode=\"bilinear\", align_corners=False)\n                tiles.append(xt)\n        tiles = torch.stack(tiles, dim=1)\n        flat = tiles.view(-1, C, self.input_res, self.input_res)\n        feats = self.backbone(flat)\n        return feats.view(B, -1, feats.shape[-1])\n\nclass PyramidMixer(nn.Module):\n    def __init__(self, dim_in: int, dims: Tuple[int, int, int], mobilevit_heads=4, mobilevit_depth=2, sra_heads=6, sra_ratio=2, mamba_depth=3, mamba_kernel=5, dropout=0.0):\n        super().__init__()\n        c1, c2, c3 = dims\n        self.proj1 = nn.Linear(dim_in, c1)\n        self.mobilevit = MobileViTBlock(c1, heads=mobilevit_heads, depth=mobilevit_depth, dropout=dropout)\n        self.proj2 = nn.Linear(c1, c2)\n        self.pvt = PVTBlock(c2, heads=sra_heads, sr_ratio=sra_ratio, dropout=dropout, mlp_ratio=3.0)\n        self.mamba_local = LocalMambaBlock(c2, kernel_size=mamba_kernel, dropout=dropout)\n        self.proj3 = nn.Linear(c2, c3)\n        self.mamba_global = nn.ModuleList([LocalMambaBlock(c3, kernel_size=mamba_kernel, dropout=dropout) for _ in range(mamba_depth)])\n        self.final_attn = AttentionBlock(c3, heads=min(8, c3//64+1), dropout=dropout, mlp_ratio=2.0)\n\n    def _tokens_to_map(self, tokens: torch.Tensor, target_hw: Tuple[int, int]):\n        B, N, C = tokens.shape\n        H, W = target_hw\n        need = H * W\n        if N < need:\n            pad = tokens.new_zeros(B, need-N, C)\n            tokens = torch.cat([tokens, pad], dim=1)\n        tokens = tokens[:, :need, :]\n        return tokens.transpose(1, 2).reshape(B, C, H, W)\n\n    @staticmethod\n    def _fit_hw(n_tokens: int) -> Tuple[int, int]:\n        h = int(math.sqrt(n_tokens))\n        w = h\n        while h * w < n_tokens:\n            w += 1\n            if h * w < n_tokens: h += 1\n        return h, w\n\n    def forward(self, tokens: torch.Tensor):\n        B, N, C = tokens.shape\n        map_hw = (3, 4)\n        feat_map = self._tokens_to_map(tokens, map_hw)\n        t1 = self.proj1(tokens)\n        m1 = self._tokens_to_map(t1, map_hw)\n        m1 = self.mobilevit(m1)\n        t1_out = m1.flatten(2).transpose(1, 2)[:, :N]\n        t2 = self.proj2(t1_out)\n        new_len = max(4, N//2)\n        t2 = t2[:, :new_len] + F.adaptive_avg_pool1d(t2.transpose(1, 2), new_len).transpose(1, 2)\n        hw2 = self._fit_hw(t2.size(1))\n        if t2.size(1) < hw2[0] * hw2[1]:\n            pad = t2.new_zeros(B, hw2[0]*hw2[1]-t2.size(1), t2.size(2))\n            t2 = torch.cat([t2, pad], dim=1)\n        t2 = self.pvt(t2, hw2)\n        t2 = self.mamba_local(t2)\n        t3 = self.proj3(t2)\n        pooled = torch.stack([t3.mean(dim=1), t3.max(dim=1).values], dim=1)\n        t3 = pooled\n        for blk in self.mamba_global: t3 = blk(t3)\n        t3 = self.final_attn(t3)\n        return t3.mean(dim=1), {\"stage1_map\": m1.detach(), \"stage2_tokens\": t2.detach(), \"stage3_tokens\": t3.detach()}\n\n@dataclass\nclass TrainCFG:\n    dropout: float = 0.1\n    hidden_ratio: float = 0.35\n    dino_candidates: Tuple[str, ...] = (\"vit_base_patch14_dinov2\", \"vit_base_patch14_reg4_dinov2\", \"vit_small_patch14_dinov2\")\n    small_grid: Tuple[int, int] = (4, 4)\n    big_grid: Tuple[int, int] = (2, 2)\n    t2t_depth: int = 2\n    cross_layers: int = 2\n    cross_heads: int = 6\n    pyramid_dims: Tuple[int, int, int] = (384, 512, 640)\n    mobilevit_heads: int = 4\n    mobilevit_depth: int = 2\n    sra_heads: int = 8\n    sra_ratio: int = 2\n    mamba_depth: int = 3\n    mamba_kernel: int = 5\n    aux_head: bool = True\n    aux_loss_weight: float = 0.4\n    ALL_TARGET_COLS: Tuple[str, ...] = (\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"GDM_g\", \"Dry_Total_g\")\n\nCFG = TrainCFG()\n\ndef update_cfg_from_checkpoint(cfg_dict: dict):\n    global CFG\n    if not cfg_dict: return\n    for k, v in cfg_dict.items():\n        if hasattr(CFG, k): setattr(CFG, k, v)\n\nclass CrossPVT_T2T_MambaDINO(nn.Module):\n    def __init__(self, dropout: float = 0.1, hidden_ratio: float = 0.35):\n        super().__init__()\n        self.backbone, self.feat_dim, self.backbone_name, self.input_res = self._build_dino_backbone()\n        self.tile_encoder = TileEncoder(self.backbone, self.input_res)\n        self.t2t = T2TRetokenizer(self.feat_dim, depth=CFG.t2t_depth, heads=CFG.cross_heads, dropout=dropout)\n        self.cross = CrossScaleFusion(self.feat_dim, heads=CFG.cross_heads, dropout=dropout, layers=CFG.cross_layers)\n        self.pyramid = PyramidMixer(dim_in=self.feat_dim, dims=CFG.pyramid_dims, mobilevit_heads=CFG.mobilevit_heads, mobilevit_depth=CFG.mobilevit_depth, sra_heads=CFG.sra_heads, sra_ratio=CFG.sra_ratio, mamba_depth=CFG.mamba_depth, mamba_kernel=CFG.mamba_kernel, dropout=dropout)\n        self.combined_dim = CFG.pyramid_dims[-1] * 2\n        hidden = max(32, int(self.combined_dim * hidden_ratio))\n        def head(): return nn.Sequential(nn.Linear(self.combined_dim, hidden), nn.GELU(), nn.Dropout(dropout), nn.Linear(hidden, 1))\n        self.head_green = head()\n        self.head_clover = head()\n        self.head_dead = head()\n        self.score_head = nn.Sequential(nn.LayerNorm(self.combined_dim), nn.Linear(self.combined_dim, 1))\n        self.aux_head = nn.Sequential(nn.LayerNorm(CFG.pyramid_dims[1]), nn.Linear(CFG.pyramid_dims[1], 5)) if CFG.aux_head else None\n        self.softplus = nn.Softplus(beta=1.0)\n        self.cross_gate_left = nn.Linear(CFG.pyramid_dims[-1], CFG.pyramid_dims[-1])\n        self.cross_gate_right = nn.Linear(CFG.pyramid_dims[-1], CFG.pyramid_dims[-1])\n\n    def _build_dino_backbone(self):\n        last_err = None\n        for name in CFG.dino_candidates:\n            for gp in [\"token\", \"avg\", \"__default__\"]:\n                try:\n                    if gp == \"__default__\":\n                        m = timm.create_model(name, pretrained=False, num_classes=0)\n                        gp_str = \"default\"\n                    else:\n                        m = timm.create_model(name, pretrained=False, num_classes=0, global_pool=gp)\n                        gp_str = gp\n                    feat = m.num_features\n                    input_res = self._infer_input_res(m)\n                    if hasattr(m, \"set_grad_checkpointing\"):\n                        m.set_grad_checkpointing(True)\n                    return m, feat, name, int(input_res)\n                except Exception as e: last_err = e; continue\n        raise RuntimeError(f\"无法创建任何 DINO 主干。最后错误: {last_err}\")\n\n    @staticmethod\n    def _infer_input_res(m) -> int:\n        if hasattr(m, \"patch_embed\") and hasattr(m.patch_embed, \"img_size\"):\n            isz = m.patch_embed.img_size\n            return int(isz if isinstance(isz, (int, float)) else isz[0])\n        if hasattr(m, \"img_size\"):\n            isz = m.img_size\n            return int(isz if isinstance(isz, (int, float)) else isz[0])\n        dc = getattr(m, \"default_cfg\", {}) or {}\n        ins = dc.get(\"input_size\", None)\n        if ins:\n            if isinstance(ins, (tuple, list)) and len(ins) >= 2:\n                return int(ins[1])\n            return int(ins if isinstance(ins, (int, float)) else 224)\n        return 518\n\n    def _half_forward(self, x_half: torch.Tensor):\n        tiles_small = self.tile_encoder(x_half, CFG.small_grid)\n        tiles_big = self.tile_encoder(x_half, CFG.big_grid)\n        t2, stage1_map = self.t2t(tiles_small, CFG.small_grid)\n        fused = self.cross(t2, tiles_big)\n        feat, feat_maps = self.pyramid(fused)\n        feat_maps[\"stage1_map\"] = stage1_map\n        return feat, feat_maps\n\n    def _merge_heads(self, f_l: torch.Tensor, f_r: torch.Tensor):\n        g_l = torch.sigmoid(self.cross_gate_left(f_r))\n        g_r = torch.sigmoid(self.cross_gate_right(f_l))\n        f_l = f_l * g_l\n        f_r = f_r * g_r\n        f = torch.cat([f_l, f_r], dim=1)\n        green_pos = self.softplus(self.head_green(f))\n        clover_pos = self.softplus(self.head_clover(f))\n        dead_pos = self.softplus(self.head_dead(f))\n        gdm = green_pos + clover_pos\n        total = gdm + dead_pos\n        return total, gdm, green_pos, f\n\n    def forward(self, *inputs, x_left=None, x_right=None, return_features: bool = False):\n        if inputs:\n            if len(inputs) == 1:\n                first = inputs[0]\n                if isinstance(first, (tuple, list)):\n                    if len(first) >= 1: x_left = first[0]\n                    if len(first) >= 2: x_right = first[1]\n                else: x_left = first\n            else: x_left = inputs[0]; x_right = inputs[1]\n        if x_left is None or (isinstance(x_left, torch.Tensor) and x_left.shape[0] == 0):\n            device = next(self.parameters()).device\n            dtype = next(self.parameters()).dtype\n            zero = torch.zeros(0, 1, device=device, dtype=dtype)\n            out = {\"total\": zero, \"gdm\": zero, \"green\": zero, \"score_feat\": torch.zeros(0, self.combined_dim, device=device, dtype=dtype)}\n            if self.aux_head is not None:\n                out[\"aux\"] = torch.zeros(0, len(CFG.ALL_TARGET_COLS), device=device, dtype=dtype)\n            if return_features: out[\"feature_maps\"] = {}\n            return out\n        if x_right is None:\n            if isinstance(x_left, torch.Tensor) and x_left.shape[1] % 2 == 0:\n                x_left, x_right = torch.chunk(x_left, 2, dim=1)\n            else: raise ValueError(\"缺少 x_right 输入。\")\n        feat_l, feats_l = self._half_forward(x_left)\n        feat_r, feats_r = self._half_forward(x_right)\n        total, gdm, green, f_concat = self._merge_heads(feat_l, feat_r)\n        out = {\"total\": total, \"gdm\": gdm, \"green\": green, \"score_feat\": f_concat}\n        if self.aux_head is not None:\n            aux_tokens = torch.cat([feats_l[\"stage2_tokens\"], feats_r[\"stage2_tokens\"]], dim=1)\n            aux_pred = self.softplus(self.aux_head(aux_tokens.mean(dim=1)))\n            out[\"aux\"] = aux_pred\n        if return_features:\n            out[\"feature_maps\"] = {\"stage1_left\": feats_l.get(\"stage1_map\"), \"stage1_right\": feats_r.get(\"stage1_map\"), \"stage3_left\": feats_l.get(\"stage3_tokens\"), \"stage3_right\": feats_r.get(\"stage3_tokens\")}\n        return out","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.596681Z","iopub.execute_input":"2026-01-08T13:15:07.596951Z","iopub.status.idle":"2026-01-08T13:15:07.666105Z","shell.execute_reply.started":"2026-01-08T13:15:07.596926Z","shell.execute_reply":"2026-01-08T13:15:07.665564Z"}},"outputs":[],"execution_count":null},{"id":"8340006a","cell_type":"code","source":"from transformers import CLIPModel\n\n@dataclass\nclass EVA_FINE_CFG:\n    model_path: str = \"/kaggle/input/eva02-large-patch14-clip-336-merged2b/pytorch/default/1\"\n    image_size: int = 224\n    train_batch_size: int = 4\n    valid_batch_size: int = 8\n    epochs: int = 2\n    lr: float = 2e-4\n    weight_decay: float = 1e-3\n    head_dropout: float = 0.2\n    num_workers: int = 2\n    use_amp: bool = True\n    freeze_backbone: bool = True\n    unfreeze_blocks: int = 2\n    train_folds: List[int] = None\n    output_dir: Path = Path(\"./eva_supervised_ckpts\")\n    force_retrain: bool = False\n    max_grad_norm: float = 1.0\n    smooth_l1_beta: float = 0.5\n\n    def __post_init__(self):\n        if self.train_folds is None:\n            self.train_folds = list(range(5))\nEVA_FINE_CFG = EVA_FINE_CFG()\n\n\ndef build_eva_transforms(train: bool = True, size: Optional[int] = None):\n    size = size or EVA_FINE_CFG.image_size\n    normalize = A.Normalize(\n        mean=[0.48145466, 0.4578275, 0.40821073],\n        std=[0.26862954, 0.26130258, 0.27577711],\n        max_pixel_value=255.0,\n    )\n    if train:\n        return A.Compose([\n            A.RandomResizedCrop(size=(size, size), scale=(0.85, 1.0), ratio=(0.9, 1.1), p=0.7),\n            A.HorizontalFlip(p=0.5),\n            A.VerticalFlip(p=0.2),\n            A.RandomBrightnessContrast(0.2, 0.2, p=0.3),\n            A.HueSaturationValue(10, 15, 10, p=0.2),\n            normalize,\n            ToTensorV2(),\n        ])\n    return A.Compose([\n        A.Resize(height=size, width=size),\n        normalize,\n        ToTensorV2(),\n    ])\n\n\nclass EVAImageDataset(Dataset):\n    def __init__(self, df: pd.DataFrame, transforms, has_targets: bool = True):\n        self.df = df.reset_index(drop=True)\n        self.transforms = transforms\n        self.has_targets = has_targets and all(col in self.df.columns for col in TARGET_NAMES)\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx: int):\n        row = self.df.iloc[idx]\n        img_path = row['image_path']\n        img = cv2.imread(img_path, cv2.IMREAD_COLOR)\n        if img is None:\n            img = np.zeros((EVA_FINE_CFG.image_size, EVA_FINE_CFG.image_size, 3), dtype=np.uint8)\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        if self.transforms is not None:\n            transformed = self.transforms(image=img)\n            img_tensor = transformed['image']\n        else:\n            img_resized = cv2.resize(img, (EVA_FINE_CFG.image_size, EVA_FINE_CFG.image_size), interpolation=cv2.INTER_AREA)\n            img_tensor = torch.from_numpy(img_resized.transpose(2, 0, 1)).float() / 255.0\n        if not torch.is_tensor(img_tensor):\n            img_tensor = torch.tensor(img_tensor)\n        img = img_tensor.contiguous().clone()\n        item = {'pixel_values': img}\n        if self.has_targets:\n            targets = row[TARGET_NAMES].values.astype(np.float32)\n            item['targets'] = torch.tensor(targets, dtype=torch.float32)\n        return item\n\n\nclass EVARegressor(nn.Module):\n    def __init__(self, cfg: EVA_FINE_CFG):\n        super().__init__()\n        self.cfg = cfg\n        self.backbone = CLIPModel.from_pretrained(cfg.model_path, local_files_only=True)\n        vision_dim = self.backbone.config.vision_config.hidden_size\n        self.head = nn.Sequential(\n            nn.LayerNorm(vision_dim),\n            nn.Dropout(cfg.head_dropout),\n            nn.Linear(vision_dim, len(TARGET_NAMES)),\n        )\n        self._configure_trainable_layers()\n\n    def _configure_trainable_layers(self):\n        if not self.cfg.freeze_backbone:\n            return\n        for param in self.backbone.parameters():\n            param.requires_grad = False\n        if self.cfg.unfreeze_blocks > 0:\n            layers = self.backbone.vision_model.encoder.layers\n            for block in layers[-self.cfg.unfreeze_blocks:]:\n                for param in block.parameters():\n                    param.requires_grad = True\n        for param in self.backbone.visual_projection.parameters():\n            param.requires_grad = True\n\n    def forward(self, pixel_values):\n        vision_outputs = self.backbone.vision_model(pixel_values=pixel_values, output_attentions=False, output_hidden_states=False)\n        if hasattr(vision_outputs, 'pooler_output') and vision_outputs.pooler_output is not None:\n            feats = vision_outputs.pooler_output\n        else:\n            feats = vision_outputs[1]\n        return self.head(feats)\n\n\ndef create_eva_loader(df: pd.DataFrame, train: bool, has_targets: bool):\n    transforms = build_eva_transforms(train=train)\n    dataset = EVAImageDataset(df, transforms, has_targets=has_targets)\n    batch_size = EVA_FINE_CFG.train_batch_size if train else EVA_FINE_CFG.valid_batch_size\n    return DataLoader(\n        dataset,\n        batch_size=batch_size,\n        shuffle=train and has_targets,\n        num_workers=EVA_FINE_CFG.num_workers,\n        pin_memory=True,\n        drop_last=False,\n    )\n\n\ndef evaluate_eva(model, loader, device, criterion=None):\n    model.eval()\n    preds, loss_sum, count = [], 0.0, 0\n    with torch.no_grad():\n        for batch in loader:\n            images = batch['pixel_values'].to(device)\n            outputs = model(images)\n            preds.append(outputs.detach().cpu().numpy())\n            targets = batch.get('targets')\n            if criterion is not None and targets is not None:\n                targets = targets.to(device)\n                batch_loss = criterion(outputs, targets).item()\n                loss_sum += batch_loss * images.size(0)\n                count += images.size(0)\n    stacked = np.concatenate(preds, axis=0)\n    loss = loss_sum / max(1, count) if count else 0.0\n    return loss, stacked\n\n\ndef train_eva_models(train_df: pd.DataFrame):\n    cfg = EVA_FINE_CFG\n    cfg.output_dir.mkdir(parents=True, exist_ok=True)\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    train_df = train_df.reset_index(drop=True)\n    oof = np.zeros((len(train_df), len(TARGET_NAMES)), dtype=np.float32)\n    fold_scores = {}\n    criterion = nn.SmoothL1Loss(beta=cfg.smooth_l1_beta)\n    for fold in cfg.train_folds:\n        valid_mask = train_df['fold'] == fold\n        if valid_mask.sum() == 0:\n            continue\n        train_split = train_df[~valid_mask].reset_index(drop=True)\n        valid_split = train_df[valid_mask].reset_index(drop=True)\n        val_indices = np.where(valid_mask.values)[0]\n        ckpt_path = cfg.output_dir / f\"eva_fold_{fold}.pt\"\n        need_train = cfg.force_retrain or not ckpt_path.exists()\n        valid_loader = create_eva_loader(valid_split, train=False, has_targets=True)\n        model = EVARegressor(cfg).to(device)\n        if need_train:\n            optimizer = AdamW(filter(lambda p: p.requires_grad, model.parameters()), lr=cfg.lr, weight_decay=cfg.weight_decay)\n            scheduler = CosineAnnealingLR(optimizer, T_max=max(1, cfg.epochs))\n            scaler = GradScaler(enabled=cfg.use_amp and device.type == 'cuda')\n            best_metric = -np.inf\n            best_state = None\n            best_preds = None\n            train_loader = create_eva_loader(train_split, train=True, has_targets=True)\n            for epoch in range(cfg.epochs):\n                model.train()\n                for batch in train_loader:\n                    optimizer.zero_grad(set_to_none=True)\n                    images = batch['pixel_values'].to(device)\n                    targets = batch['targets'].to(device)\n                    with autocast(enabled=cfg.use_amp and device.type == 'cuda'):\n                        outputs = model(images)\n                        loss = criterion(outputs, targets)\n                    scaler.scale(loss).backward()\n                    if cfg.max_grad_norm is not None:\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                scheduler.step()\n                val_loss, val_preds = evaluate_eva(model, valid_loader, device, criterion)\n                metric = competition_metric(valid_split[TARGET_NAMES].values, val_preds)\n                if metric > best_metric:\n                    best_metric = metric\n                    best_state = {k: v.detach().cpu() for k, v in model.state_dict().items()}\n                    best_preds = val_preds.copy()\n            torch.save({'model_state': best_state, 'metric': best_metric}, ckpt_path)\n            fold_scores[fold] = best_metric\n            oof[val_indices] = best_preds\n            print(f\"[EVA-FT] Fold {fold}: CV {best_metric:.6f}\")\n        else:\n            state = torch.load(ckpt_path, map_location=device)\n            model.load_state_dict(state['model_state'], strict=False)\n            _, val_preds = evaluate_eva(model, valid_loader, device, criterion)\n            metric = competition_metric(valid_split[TARGET_NAMES].values, val_preds)\n            fold_scores[fold] = metric\n            oof[val_indices] = val_preds\n            print(f\"[EVA-FT] Fold {fold}: CV {metric:.6f} (loaded)\")\n        del model\n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n    overall = competition_metric(train_df[TARGET_NAMES].values, oof)\n    print(f\"[EVA-FT] Overall CV: {overall:.6f}\")\n    return oof, overall\n\n\ndef predict_eva_test(test_df: pd.DataFrame):\n    cfg = EVA_FINE_CFG\n    device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\n    test_df = test_df.reset_index(drop=True)\n    loader = create_eva_loader(test_df, train=False, has_targets=False)\n    preds = []\n    for fold in cfg.train_folds:\n        ckpt_path = cfg.output_dir / f\"eva_fold_{fold}.pt\"\n        if not ckpt_path.exists():\n            continue\n        model = EVARegressor(cfg).to(device)\n        state = torch.load(ckpt_path, map_location=device)\n        model.load_state_dict(state['model_state'], strict=False)\n        _, fold_pred = evaluate_eva(model, loader, device, criterion=None)\n        preds.append(fold_pred)\n        del model\n        if torch.cuda.is_available():\n            torch.cuda.empty_cache()\n    if not preds:\n        raise RuntimeError(\"No EVA checkpoints were found. Run training before inference.\")\n    return np.mean(preds, axis=0)\n\n\ndef run_eva_supervised_pipeline(train_df: pd.DataFrame, test_df: pd.DataFrame):\n    oof, overall = train_eva_models(train_df)\n    preds = predict_eva_test(test_df)\n    return preds, overall","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.667330Z","iopub.execute_input":"2026-01-08T13:15:07.667573Z","iopub.status.idle":"2026-01-08T13:15:07.699212Z","shell.execute_reply.started":"2026-01-08T13:15:07.667553Z","shell.execute_reply":"2026-01-08T13:15:07.698630Z"}},"outputs":[],"execution_count":null},{"id":"b5a724b9","cell_type":"code","source":"class INF_CFG:\n    BASE_PATH = \"/kaggle/input/csiro-biomass\"\n    TEST_CSV = os.path.join(BASE_PATH, \"test.csv\")\n    TEST_IMAGE_DIR = os.path.join(BASE_PATH, \"test\")\n    EXPERIMENT_DIR = \"/kaggle/input/csiro/pytorch/default/12\"\n    CKPT_PATTERN_FOLD_X = os.path.join(EXPERIMENT_DIR, \"fold_{fold}\", \"checkpoints\", \"best_wr2.pt\")\n    CKPT_PATTERN_FOLDX = os.path.join(EXPERIMENT_DIR, \"fold{fold}\", \"checkpoints\", \"best_wr2.pt\")\n    N_FOLDS = 5\n    SUBMISSION_FILE = \"submission3.csv\"\n    DEVICE = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    BATCH_SIZE = 1\n    NUM_WORKERS = 0\n    MIXED_PRECISION = True\n    USE_TTA = True\n    TTA_TRANSFORMS = [\"original\", \"hflip\", \"vflip\"]\n    TTA_STACK = True\n    TTA_STACK_WEIGHT = 0.5\n    CKPT_SEARCH_PATTERNS = (\"best*.pt\", \"model*.pt\", \"*.pth\")\n    EXTRA_CKPT_DIRS: List[str] = []\n    MAX_MODELS = 8\n    PER_FOLD_FILENAMES = (\"best_wr2.pt\", \"best_loss.pt\", \"last.pt\")\n    ALL_TARGET_COLS = [\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"GDM_g\", \"Dry_Total_g\"]\n\n\nclass TestBiomassDataset(Dataset):\n    def __init__(self, df: pd.DataFrame, transform, image_dir: str):\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n        self.image_dir = image_dir\n        self.paths = self.df[\"image_path\"].values\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        filename = os.path.basename(self.paths[idx])\n        full_path = os.path.join(self.image_dir, filename)\n        img = cv2.imread(full_path)\n        if img is None:\n            img = np.zeros((1000, 2000, 3), dtype=np.uint8)\n        else:\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        h, w, _ = img.shape\n        mid = w // 2\n        left = img[:, :mid]\n        right = img[:, mid:]\n        left_t = self.transform(image=left)[\"image\"]\n        right_t = self.transform(image=right)[\"image\"]\n        return left_t, right_t\n\n\ndef strip_module_prefix(state_dict: dict) -> dict:\n    if not state_dict:\n        return state_dict\n    keys = list(state_dict.keys())\n    if all(k.startswith(\"module.\") for k in keys):\n        return {k[len(\"module.\"):]: v for k, v in state_dict.items()}\n    return state_dict\n\n\ndef load_checkpoint(path: str) -> dict:\n    if not os.path.exists(path):\n        raise FileNotFoundError(f\"Checkpoint not found: {path}\")\n    try:\n        state = torch.load(path, map_location=\"cpu\", weights_only=False)\n    except TypeError:\n        state = torch.load(path, map_location=\"cpu\")\n    return state\n\n\ndef load_model_from_checkpoint(ckpt_path: str) -> nn.Module:\n    state = load_checkpoint(ckpt_path)\n    cfg_dict = state.get(\"cfg\", {})\n    update_cfg_from_checkpoint(cfg_dict)\n    dropout = cfg_dict.get(\"dropout\", CFG.dropout)\n    hidden_ratio = cfg_dict.get(\"hidden_ratio\", CFG.hidden_ratio)\n    model = CrossPVT_T2T_MambaDINO(dropout=dropout, hidden_ratio=hidden_ratio)\n    model_state = state.get(\"model_state\", state)\n    model_state = strip_module_prefix(model_state)\n    model.load_state_dict(model_state, strict=False)\n    model = maybe_parallelize(model, INF_CFG.DEVICE, min_batch=INF_CFG.BATCH_SIZE)\n    model.eval()\n    return model\n\n\ndef pack5_targets(total: torch.Tensor, gdm: torch.Tensor, green: torch.Tensor) -> torch.Tensor:\n    clover = gdm - green\n    dead = total - gdm\n    return torch.cat([green, dead, clover, gdm, total], dim=1)\n\n\n@torch.no_grad()\ndef predict_one_view(models: List[nn.Module], loader: DataLoader) -> np.ndarray:\n    preds_list = []\n    amp_dtype = \"cuda\" if INF_CFG.DEVICE.type == \"cuda\" else \"cpu\"\n    for xl, xr in tqdm(loader, desc=\"  Predicting\", leave=False):\n        xl = xl.to(INF_CFG.DEVICE, non_blocking=True)\n        xr = xr.to(INF_CFG.DEVICE, non_blocking=True)\n        x_cat = torch.cat([xl, xr], dim=1)\n        per_model_preds = []\n        with torch.amp.autocast(amp_dtype, enabled=INF_CFG.MIXED_PRECISION):\n            for model in models:\n                out = model(x_cat, return_features=False)\n                total = out[\"total\"]\n                gdm = out[\"gdm\"]\n                green = out[\"green\"]\n                five = torch.clamp(pack5_targets(total, gdm, green), min=0.0)\n                per_model_preds.append(five.float().cpu())\n        stacked = torch.mean(torch.stack(per_model_preds, dim=0), dim=0)\n        preds_list.append(stacked.numpy())\n    return np.concatenate(preds_list, axis=0)\n\n\ndef _gather_configured_ckpts():\n    ckpts = []\n    for fold in range(INF_CFG.N_FOLDS):\n        candidate_paths = []\n        for pattern in (INF_CFG.CKPT_PATTERN_FOLD_X, INF_CFG.CKPT_PATTERN_FOLDX):\n            base = pattern.format(fold=fold)\n            candidate_paths.append(base)\n            base_dir = os.path.dirname(base)\n            if os.path.isdir(base_dir):\n                for fname in INF_CFG.PER_FOLD_FILENAMES:\n                    candidate_paths.append(os.path.join(base_dir, fname))\n        for path in candidate_paths:\n            if os.path.exists(path):\n                ckpts.append(path)\n                break\n    return ckpts\n\n\ndef _discover_additional_ckpts():\n    search_dirs = [d for d in [INF_CFG.EXPERIMENT_DIR, *(INF_CFG.EXTRA_CKPT_DIRS or [])] if d and os.path.isdir(d)]\n    if not search_dirs:\n        default_root = \"/kaggle/input\"\n        if os.path.isdir(default_root):\n            search_dirs.append(default_root)\n    found = []\n    for base in search_dirs:\n        for pattern in INF_CFG.CKPT_SEARCH_PATTERNS:\n            glob_pattern = os.path.join(base, \"**\", pattern)\n            for path in glob(glob_pattern, recursive=True):\n                if os.path.isfile(path):\n                    found.append(path)\n    unique = []\n    seen = set()\n    for path in sorted(found):\n        if path not in seen:\n            unique.append(path)\n            seen.add(path)\n    return unique\n\n\ndef collect_checkpoint_paths():\n    ckpts = _gather_configured_ckpts()\n    if ckpts:\n        return ckpts\n    return _discover_additional_ckpts()\n\n\ndef run_inference(test_df: pd.DataFrame, image_dir: str) -> np.ndarray:\n    models = []\n    input_res = None\n    checkpoint_paths = collect_checkpoint_paths()\n    if not checkpoint_paths:\n        raise RuntimeError(\"No checkpoints were found for the configured folds.\")\n    usable = checkpoint_paths[:INF_CFG.MAX_MODELS]\n    print(f\"[DINO] Using {len(usable)} checkpoint(s) from: {usable}\")\n    for ckpt_path in usable:\n        model = load_model_from_checkpoint(ckpt_path)\n        if model is None:\n            continue\n        models.append(model)\n        if input_res is None:\n            input_res = getattr(unwrap_model(model), \"input_res\", 518)\n    if not models:\n        raise RuntimeError(\"Unable to load any DINO checkpoints.\")\n    transforms = get_tta_transforms(input_res)\n    if INF_CFG.USE_TTA and len(transforms) > 1:\n        final_pred = apply_tta_with_stacking(models, test_df, image_dir, transforms)\n    else:\n        single = transforms[0]\n        ds = TestBiomassDataset(test_df, single, image_dir)\n        dl = DataLoader(ds, batch_size=INF_CFG.BATCH_SIZE, shuffle=False, num_workers=INF_CFG.NUM_WORKERS, pin_memory=True)\n        final_pred = predict_one_view(models, dl)\n    return final_pred\n\n\ndef create_submission(final_pred: np.ndarray, test_long: pd.DataFrame, test_unique: pd.DataFrame) -> pd.DataFrame:\n    def clean(x):\n        x = np.nan_to_num(x, nan=0.0, posinf=0.0, neginf=0.0)\n        return np.maximum(0, x)\n    green, dead, clover, gdm, total = map(clean, [final_pred[:, 0], final_pred[:, 1], final_pred[:, 2], final_pred[:, 3], final_pred[:, 4]])\n    wide = pd.DataFrame({\n        \"image_path\": test_unique[\"image_path\"],\n        \"Dry_Green_g\": green,\n        \"Dry_Dead_g\": dead,\n        \"Dry_Clover_g\": clover,\n        \"GDM_g\": gdm,\n        \"Dry_Total_g\": total,\n    })\n    wide = post_process_biomass(wide)\n    long_preds = wide.melt(id_vars=[\"image_path\"], value_vars=INF_CFG.ALL_TARGET_COLS, var_name=\"target_name\", value_name=\"target\")\n    sub = pd.merge(\n        test_long[[\"sample_id\", \"image_path\", \"target_name\"]],\n        long_preds,\n        on=[\"image_path\", \"target_name\"],\n        how=\"left\",\n    )[[\"sample_id\", \"target\"]]\n    sub[\"target\"] = np.nan_to_num(sub[\"target\"], nan=0.0, posinf=0.0, neginf=0.0)\n    sub.to_csv(INF_CFG.SUBMISSION_FILE, index=False)\n    return sub\n\n\ndef parse_args():\n    parser = argparse.ArgumentParser(description=\"CSIRO CrossPVT DINO inference\")\n    parser.add_argument(\"--test-csv\", type=str, default=None)\n    parser.add_argument(\"--test-image-dir\", type=str, default=None)\n    parser.add_argument(\"--experiment-dir\", type=str, default=None)\n    parser.add_argument(\"--output\", type=str, default=None)\n    parser.add_argument(\"--batch-size\", type=int, default=None)\n    parser.add_argument(\"--no-tta\", action=\"store_true\")\n    parser.add_argument(\"--ckpt-root\", action=\"append\", default=None)\n    args, _ = parser.parse_known_args()\n    return args\n\n\ndef run_dino_inference():\n    args = parse_args()\n    if args.test_csv:\n        INF_CFG.TEST_CSV = args.test_csv\n    if args.test_image_dir:\n        INF_CFG.TEST_IMAGE_DIR = args.test_image_dir\n    if args.experiment_dir:\n        INF_CFG.EXPERIMENT_DIR = args.experiment_dir\n        INF_CFG.CKPT_PATTERN_FOLD_X = os.path.join(INF_CFG.EXPERIMENT_DIR, \"fold_{fold}\", \"checkpoints\", \"best_wr2.pt\")\n        INF_CFG.CKPT_PATTERN_FOLDX = os.path.join(INF_CFG.EXPERIMENT_DIR, \"fold{fold}\", \"checkpoints\", \"best_wr2.pt\")\n    if args.output:\n        INF_CFG.SUBMISSION_FILE = args.output\n    if args.batch_size:\n        INF_CFG.BATCH_SIZE = args.batch_size\n    if args.no_tta:\n        INF_CFG.USE_TTA = False\n    if args.ckpt_root:\n        INF_CFG.EXTRA_CKPT_DIRS = [p for p in args.ckpt_root if p]\n    test_long = pd.read_csv(INF_CFG.TEST_CSV)\n    test_unique = test_long.drop_duplicates(subset=[\"image_path\"]).reset_index(drop=True)\n    final_pred = run_inference(test_unique, INF_CFG.TEST_IMAGE_DIR)\n    submission = create_submission(final_pred, test_long, test_unique)\n    print(f\"[DINO] Saved {len(submission)} rows to {INF_CFG.SUBMISSION_FILE}\")\n    gc.collect()\n    if torch.cuda.is_available():\n        torch.cuda.empty_cache()\n    return submission\n\n\ndef get_input_size(model):\n    if hasattr(model, \"patch_embed\") and hasattr(model.patch_embed, \"img_size\"):\n        size = model.patch_embed.img_size\n        return int(size if isinstance(size, (int, float)) else size[0])\n    if hasattr(model, \"img_size\"):\n        size = model.img_size\n        return int(size if isinstance(size, (int, float)) else size[0])\n    cfg = getattr(model, \"default_cfg\", {}) or {}\n    ins = cfg.get(\"input_size\", None)\n    if ins:\n        if isinstance(ins, (tuple, list)) and len(ins) >= 2:\n            return int(ins[1])\n        return int(ins if isinstance(ins, (int, float)) else 224)\n    arch = cfg.get(\"architecture\", \"\") or str(type(model))\n    return 518 if \"dinov2\" in arch.lower() or \"dinov3\" in arch.lower() else 224\n\n\ndef build_backbone(name):\n    model = timm.create_model(name, pretrained=False, num_classes=0)\n    features = model.num_features\n    input_size = get_input_size(model)\n    return model, features, input_size\n\n\nclass BaseDINO(nn.Module):\n    def __init__(self, backbone_name):\n        super().__init__()\n        self.backbone, feat_dim, input_size = build_backbone(backbone_name)\n        self.input_size = int(input_size)\n        self.feat_dim = feat_dim\n        self.combined_dim = feat_dim * 2\n        hidden_size = max(8, int(self.combined_dim * 0.25))\n        def make_head():\n            return nn.Sequential(\n                nn.Linear(self.combined_dim, hidden_size),\n                nn.ReLU(inplace=True),\n                nn.Dropout(0.3),\n                nn.Linear(hidden_size, 1),\n            )\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\n    def merge_features(self, left_feat, right_feat):\n        combined = torch.cat([left_feat, right_feat], 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        gdm = green + clover\n        total = gdm + dead\n        return total, gdm, green\n\n\nclass TiledFiLMDINO(BaseDINO):\n    def __init__(self, backbone_name):\n        super().__init__(backbone_name)\n        self.grid = (2, 2)\n\n        class FiLM(nn.Module):\n            def __init__(self, feat_dim):\n                super().__init__()\n                hidden = max(32, feat_dim // 2)\n                self.mlp = nn.Sequential(\n                    nn.Linear(feat_dim, hidden),\n                    nn.ReLU(inplace=True),\n                    nn.Linear(hidden, feat_dim * 2),\n                )\n\n            def forward(self, context):\n                gamma_beta = self.mlp(context)\n                return torch.chunk(gamma_beta, 2, dim=1)\n\n        self.film_left = FiLM(self.feat_dim)\n        self.film_right = FiLM(self.feat_dim)\n\n    def extract_tile_features(self, x):\n        B, C, H, W = x.shape\n        rows, cols = self.grid\n\n        def split_dimension(length, parts):\n            step = length // parts\n            segments = []\n            start = 0\n            for _ in range(parts - 1):\n                segments.append((start, start + step))\n                start += step\n            segments.append((start, length))\n            return segments\n\n        row_segments = split_dimension(H, rows)\n        col_segments = split_dimension(W, cols)\n        features = []\n        for (rs, re) in row_segments:\n            for (cs, ce) in col_segments:\n                tile = x[:, :, rs:re, cs:ce]\n                if tile.shape[-2:] != (self.input_size, self.input_size):\n                    tile = F.interpolate(tile, size=(self.input_size, self.input_size), mode=\"bilinear\", align_corners=False)\n                feat = self.backbone(tile)\n                features.append(feat)\n        return torch.stack(features, dim=0).permute(1, 0, 2)\n\n    def process_stream(self, x, film_layer):\n        tiles = self.extract_tile_features(x)\n        context = tiles.mean(dim=1)\n        gamma, beta = film_layer(context)\n        modulated = tiles * (1 + gamma.unsqueeze(1)) + beta.unsqueeze(1)\n        return modulated.mean(dim=1)\n\n    def forward(self, left_img, right_img):\n        left_feat = self.process_stream(left_img, self.film_left)\n        right_feat = self.process_stream(right_img, self.film_right)\n        return self.merge_features(left_feat, right_feat)\n\n\ndef clean_state_dict(state_dict):\n    if not state_dict:\n        return state_dict\n    cleaned_dict = {}\n    for k, v in state_dict.items():\n        if k.startswith(\"module.\"):\n            k = k[7:]\n        if k.startswith(\"student.\"):\n            k = k[8:]\n        skip_prefixes = (\"txt_enc.\", \"img_proj.\", \"txt_film\", \"teacher.\", \"momentum_teacher.\")\n        if any(k.startswith(prefix) for prefix in skip_prefixes):\n            continue\n        cleaned_dict[k] = v\n    return cleaned_dict\n\n\ndef load_model(checkpoint_path):\n    if not os.path.exists(checkpoint_path):\n        raise FileNotFoundError(f\"Checkpoint not found: {checkpoint_path}\")\n    try:\n        raw_state = torch.load(checkpoint_path, map_location=INF_CFG.DEVICE, weights_only=False)\n    except Exception:\n        return None\n    if isinstance(raw_state, dict):\n        if \"state_dict\" in raw_state:\n            state_dict = raw_state[\"state_dict\"]\n        elif \"model\" in raw_state:\n            state_dict = raw_state[\"model\"]\n        else:\n            state_dict = raw_state\n    else:\n        state_dict = raw_state\n    state_dict = clean_state_dict(state_dict)\n    if not state_dict:\n        return None\n    backbones = [\"vit_base_patch14_reg4_dinov2\", \"vit_base_patch14_reg4_dinov3\", \"vit_base_patch14_dinov3\"]\n    for backbone in backbones:\n        try:\n            model = TiledFiLMDINO(backbone)\n            result = model.load_state_dict(state_dict, strict=False)\n            missing = [k for k in result.missing_keys if not k.startswith(\"backbone.pos_embed\")]\n            if len(missing) == 0:\n                model = maybe_parallelize(model, INF_CFG.DEVICE, min_batch=4)\n                model.eval()\n                return model\n        except Exception:\n            continue\n    return None\n\n\ndef get_tta_transforms_mvp(img_size):\n    norm = [A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2()]\n    return [\n        A.Compose([A.Resize(img_size, img_size), *norm]),\n        A.Compose([A.HorizontalFlip(p=1.0), A.Resize(img_size, img_size), *norm]),\n        A.Compose([A.VerticalFlip(p=1.0), A.Resize(img_size, img_size), *norm]),\n        A.Compose([A.RandomRotate90(p=1.0), A.Resize(img_size, img_size), *norm]),\n    ]\n\n\nclass BiomassDataset(Dataset):\n    def __init__(self, df, transform, img_dir):\n        self.df = df.reset_index(drop=True)\n        self.transform = transform\n        self.img_dir = img_dir\n        self.paths = self.df[\"image_path\"].values\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        filename = os.path.basename(self.paths[idx])\n        full_path = os.path.join(self.img_dir, filename)\n        img = cv2.imread(full_path)\n        if img is None:\n            img = np.zeros((1000, 2000, 3), dtype=np.uint8)\n        else:\n            img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        h, w, _ = img.shape\n        mid = w // 2\n        left_img = img[:, :mid]\n        right_img = img[:, mid:]\n        left_tensor = self.transform(image=left_img)[\"image\"]\n        right_tensor = self.transform(image=right_img)[\"image\"]\n        return left_tensor, right_tensor\n\n\n@torch.no_grad()\ndef predict_one_view_mvp(models, loader):\n    preds = []\n    use_amp = INF_CFG.DEVICE.type == \"cuda\"\n    for left_imgs, right_imgs in tqdm(loader, desc=\"Infer\", leave=False):\n        left_imgs = left_imgs.to(INF_CFG.DEVICE, non_blocking=True)\n        right_imgs = right_imgs.to(INF_CFG.DEVICE, non_blocking=True)\n        batch_preds = []\n        with torch.amp.autocast(\"cuda\", enabled=use_amp):\n            for model in models:\n                total, gdm, green = model(left_imgs, right_imgs)\n                dead = torch.clamp(total - gdm, min=0.0)\n                clover = torch.clamp(gdm - green, min=0.0)\n                pred = torch.cat([green, dead, clover, gdm, total], dim=1)\n                batch_preds.append(pred.clamp(0.05, 400.0).cpu())\n        preds.append(torch.stack(batch_preds).mean(dim=0).numpy())\n    return np.concatenate(preds)\n\n\ndef run_inference_for_ckpts(checkpoint_paths, df, img_dir):\n    models = []\n    for ckpt_path in checkpoint_paths:\n        model = load_model(ckpt_path)\n        if model is not None:\n            models.append(model)\n    if not models:\n        raise ValueError(f\"No models loaded from {checkpoint_paths}\")\n    print(f\"[MVP] Loaded {len(models)} checkpoint(s) from {checkpoint_paths}\")\n    input_size = getattr(unwrap_model(models[0]), \"input_size\", 518)\n    tta_preds = []\n    for transform in get_tta_transforms_mvp(input_size):\n        ds = BiomassDataset(df, transform, img_dir)\n        dl = DataLoader(ds, batch_size=4, shuffle=False, num_workers=0, pin_memory=True)\n        tta_preds.append(predict_one_view_mvp(models, dl))\n    return np.mean(tta_preds, axis=0)\n\n\ndef create_submission_mvp(final_preds, test_df, unique_df):\n    cols = [\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"GDM_g\", \"Dry_Total_g\"]\n    wide = pd.DataFrame({\"image_path\": unique_df[\"image_path\"]})\n    for i, col in enumerate(cols):\n        wide[col] = np.clip(final_preds[:, i], 0.05, 400.0)\n    wide[\"GDM_g\"] = wide[\"Dry_Green_g\"] + wide[\"Dry_Clover_g\"]\n    wide[\"Dry_Total_g\"] = wide[\"GDM_g\"] + wide[\"Dry_Dead_g\"]\n    wide = post_process_biomass(wide)\n    long_df = wide.melt(id_vars=\"image_path\", value_vars=cols, var_name=\"target_name\", value_name=\"target\")\n    sub = test_df[[\"sample_id\", \"image_path\", \"target_name\"]].merge(long_df, on=[\"image_path\", \"target_name\"], how=\"left\")[[\"sample_id\", \"target\"]]\n    sub.to_csv(\"submission2.csv\", index=False)\n    return sub","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.700570Z","iopub.execute_input":"2026-01-08T13:15:07.700784Z","iopub.status.idle":"2026-01-08T13:15:07.757839Z","shell.execute_reply.started":"2026-01-08T13:15:07.700763Z","shell.execute_reply":"2026-01-08T13:15:07.757298Z"}},"outputs":[],"execution_count":null},{"id":"98756ab0","cell_type":"code","source":"def _build_single_tta(name: str, img_size: int, base_ops):\n    name = (name or \"identity\").strip().lower()\n    ops = []\n    if name in (\"identity\", \"original\", \"base\"):\n        pass\n    elif name in (\"hflip\", \"horizontal_flip\"):\n        ops.append(A.HorizontalFlip(p=1.0))\n    elif name in (\"vflip\", \"vertical_flip\"):\n        ops.append(A.VerticalFlip(p=1.0))\n    elif name == \"hvflip\":\n        ops.extend([A.HorizontalFlip(p=1.0), A.VerticalFlip(p=1.0)])\n    elif name == \"transpose\":\n        ops.append(A.Transpose(p=1.0))\n    elif name in (\"rot90\", \"rotate90\"):\n        ops.append(A.Rotate(limit=(90, 90), border_mode=cv2.BORDER_REFLECT101, p=1.0))\n    elif name in (\"rot180\", \"rotate180\"):\n        ops.append(A.Rotate(limit=(180, 180), border_mode=cv2.BORDER_REFLECT101, p=1.0))\n    elif name in (\"rot270\", \"rotate270\"):\n        ops.append(A.Rotate(limit=(270, 270), border_mode=cv2.BORDER_REFLECT101, p=1.0))\n    elif name == \"brightness\":\n        ops.append(A.RandomBrightnessContrast(brightness_limit=0.15, contrast_limit=0.1, p=1.0))\n    elif name == \"blur\":\n        ops.append(A.GaussianBlur(blur_limit=(3, 5), p=1.0))\n    else:\n        return None\n    ops.append(A.Resize(img_size, img_size, interpolation=cv2.INTER_AREA))\n    ops.extend(base_ops)\n    return A.Compose(ops)\n\ndef get_tta_transforms(img_size: int, variant_names=None):\n    base_ops = [A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]), ToTensorV2()]\n    names = variant_names or INF_CFG.TTA_TRANSFORMS\n    transforms = []\n    for name in names:\n        aug = _build_single_tta(name, img_size, base_ops)\n        if aug is not None:\n            transforms.append(aug)\n    if not transforms:\n        transforms.append(A.Compose([A.Resize(img_size, img_size, interpolation=cv2.INTER_AREA), *base_ops]))\n    return transforms","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.758598Z","iopub.execute_input":"2026-01-08T13:15:07.758835Z","iopub.status.idle":"2026-01-08T13:15:07.779265Z","shell.execute_reply.started":"2026-01-08T13:15:07.758807Z","shell.execute_reply":"2026-01-08T13:15:07.778570Z"}},"outputs":[],"execution_count":null},{"id":"d8c242f7","cell_type":"code","source":"def apply_tta_with_stacking(models, base_df, image_dir, transforms):\n    preds_per_aug = []\n    for transform in transforms:\n        ds = TestBiomassDataset(base_df, transform, image_dir)\n        dl = DataLoader(ds, batch_size=INF_CFG.BATCH_SIZE, shuffle=False, num_workers=INF_CFG.NUM_WORKERS, pin_memory=True)\n        preds_per_aug.append(predict_one_view(models, dl))\n    stacked = np.stack(preds_per_aug, axis=0)  # (T, N, C)\n    mean_pred = stacked.mean(axis=0)\n    if INF_CFG.TTA_STACK and stacked.shape[0] > 1:\n        from sklearn.linear_model import Ridge\n        n_transforms, n_samples, n_targets = stacked.shape\n        aug_features = stacked.transpose(1, 0, 2).reshape(n_samples, n_transforms * n_targets)\n        meta = Ridge(alpha=1e-3)\n        meta.fit(aug_features, mean_pred)\n        meta_pred = meta.predict(aug_features).reshape(n_samples, n_targets)\n        return INF_CFG.TTA_STACK_WEIGHT * meta_pred + (1 - INF_CFG.TTA_STACK_WEIGHT) * mean_pred\n    return mean_pred","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.780847Z","iopub.execute_input":"2026-01-08T13:15:07.781065Z","iopub.status.idle":"2026-01-08T13:15:07.800102Z","shell.execute_reply.started":"2026-01-08T13:15:07.781045Z","shell.execute_reply":"2026-01-08T13:15:07.799600Z"}},"outputs":[],"execution_count":null},{"id":"7734c39a","cell_type":"code","source":"def get_tta_transforms_mvp(img_size, variant_names=None):\n    return get_tta_transforms(img_size, variant_names)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.800897Z","iopub.execute_input":"2026-01-08T13:15:07.801382Z","iopub.status.idle":"2026-01-08T13:15:07.822994Z","shell.execute_reply.started":"2026-01-08T13:15:07.801348Z","shell.execute_reply":"2026-01-08T13:15:07.822516Z"}},"outputs":[],"execution_count":null},{"id":"2c10302d","cell_type":"code","source":"def run_mvp_inference():\n    global device\n    device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n    test_csv = \"/kaggle/input/csiro-biomass/test.csv\"\n    test_img_dir = \"/kaggle/input/csiro-biomass/test\"\n    model_dir = \"/kaggle/input/csiro-mvp-models\"\n    model_paths = [os.path.join(model_dir, f\"model{i}.pth\") for i in range(1, 11)]\n    existing_models = [path for path in model_paths if os.path.exists(path)]\n    CKPTS_A = existing_models[:5]; CKPTS_B = existing_models[5:]\n    W_A, W_B = 0.95, 0.075\n    test_df = pd.read_csv(test_csv)\n    unique_df = test_df.drop_duplicates(\"image_path\").reset_index(drop=True)\n    pred_a = run_inference_for_ckpts(CKPTS_A, unique_df, test_img_dir)\n    pred_b = run_inference_for_ckpts(CKPTS_B, unique_df, test_img_dir)\n    final_preds = W_A * pred_a + W_B * pred_b\n    submission = create_submission_mvp(final_preds, test_df, unique_df)\n    print(f\"[MVP] Saved {len(submission)} rows to submission2.csv\")\n    return submission","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.823680Z","iopub.execute_input":"2026-01-08T13:15:07.823943Z","iopub.status.idle":"2026-01-08T13:15:07.841171Z","shell.execute_reply.started":"2026-01-08T13:15:07.823912Z","shell.execute_reply":"2026-01-08T13:15:07.840543Z"}},"outputs":[],"execution_count":null},{"id":"3345998a","cell_type":"code","source":"def strip_embedding_columns(df, prefixes=(\"emb\", \"dino_emb\")):\n    cols = [c for c in df.columns if any(c.startswith(p) for p in prefixes)]\n    if not cols:\n        return df\n    return df.drop(columns=cols, errors='ignore')\n\n\ndef rename_embedding_columns(df, base_prefix=\"dino_emb\"):\n    emb_cols = [c for c in df.columns if c.startswith(base_prefix) or c.startswith('emb')]\n    if not emb_cols:\n        return df, []\n    rename_map = {}\n    for col in emb_cols:\n        cleaned = col.replace('_x','').replace('_y','')\n        cleaned = cleaned.replace('emb', base_prefix, 1) if cleaned.startswith('emb') else cleaned\n        rename_map[col] = cleaned\n    df = df.rename(columns=rename_map)\n    final_cols = [c for c in df.columns if c.startswith(base_prefix)]\n    return df, final_cols\n\n\ndef preview_embedding_columns(df, label=\"DF\"):\n    emb_cols = [c for c in df.columns if c.startswith('emb') or c.startswith('dino_emb')]\n    print(f\"[{label}] embedding columns: {len(emb_cols)}\")\n    if emb_cols:\n        print(emb_cols[:20])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.841991Z","iopub.execute_input":"2026-01-08T13:15:07.842269Z","iopub.status.idle":"2026-01-08T13:15:07.860894Z","shell.execute_reply.started":"2026-01-08T13:15:07.842240Z","shell.execute_reply":"2026-01-08T13:15:07.860380Z"}},"outputs":[],"execution_count":null},{"id":"6ad10547","cell_type":"code","source":"# Run this cell after assigning EMBED_DEBUG_DF to any dataframe to preview its embedding columns.\nEMBED_DEBUG_DF = globals().get('EMBED_DEBUG_DF')\nif EMBED_DEBUG_DF is not None:\n    preview_embedding_columns(EMBED_DEBUG_DF, 'EMBED_DEBUG_DF')\nelse:\n    print('[Embedding Debug] Set EMBED_DEBUG_DF to a dataframe (e.g., dino_train_df) and re-run this cell.')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.861531Z","iopub.execute_input":"2026-01-08T13:15:07.861764Z","iopub.status.idle":"2026-01-08T13:15:07.879820Z","shell.execute_reply.started":"2026-01-08T13:15:07.861740Z","shell.execute_reply":"2026-01-08T13:15:07.879149Z"}},"outputs":[],"execution_count":null},{"id":"40d256b3","cell_type":"code","source":"def main():\n    seeding(42)\n\n    print(\"SigLIP/Ensemble Model ===\")\n    test_df = pd.read_csv(cfg.DATA_PATH/'test.csv')\n    test_df = pivot_table(df=test_df)\n    test_df['image_path'] = test_df['image_path'].apply(lambda p: str(cfg.DATA_PATH / p))\n    train_df = pd.read_csv(\"/kaggle/input/csiro-datasplit/csiro_data_split.csv\")\n\n    base_train_df = train_df.copy()\n    siglip_pred = None\n    pillar_paths = {}\n\n    sem_train_full = None\n    sem_test_full = None\n\n    if cfg.run_siglip_stack:\n        train_siglip_df = assign_folds(base_train_df.copy(), cfg.stack_kfolds, cfg.seed)\n        train_siglip_df_legacy = assign_folds(base_train_df.copy(), cfg.legacy_kfolds, cfg.seed)\n        siglip_path = \"/kaggle/input/google-siglip-so400m-patch14-384/transformers/default/1\"\n        base_test_siglip_df = compute_embeddings(model_path=siglip_path, df=test_df, patch_size=520)\n\n        train_siglip_df, test_siglip_df = append_cluster_features(\n            train_siglip_df,\n            base_test_siglip_df.copy(),\n            embed_prefix='emb',\n            cluster_sizes=cfg.siglip_cluster_sizes,\n            random_state=cfg.seed,\n        )\n        train_siglip_df_legacy, test_siglip_df_legacy = append_cluster_features(\n            train_siglip_df_legacy,\n            base_test_siglip_df.copy(),\n            embed_prefix='emb',\n            cluster_sizes=cfg.siglip_cluster_sizes,\n            random_state=cfg.seed,\n        )\n\n        flush()\n\n        sem_train_full = None\n        sem_test_full = None\n        if cfg.use_semantic_features:\n            X_all_emb = np.vstack([train_siglip_df.filter(like=\"emb\").values, test_siglip_df.filter(like=\"emb\").values])\n            try:\n                all_semantic_scores = generate_semantic_features(X_all_emb, model_path=siglip_path)\n                n_train = len(train_siglip_df)\n                sem_train_full = all_semantic_scores[:n_train]\n                sem_test_full = all_semantic_scores[n_train:]\n            except Exception as e:\n                sem_train_full = None\n                sem_test_full = None\n        else:\n            print(\"[FastMode] Skipping semantic feature generation for SigLIP pillar.\")\n\n        if cfg.fast_mode:\n            feat_engine = SupervisedEmbeddingEngine(n_pca=0.95, n_pls=5, n_gmm=3)\n        else:\n            feat_engine = SupervisedEmbeddingEngine(n_pca=0.88, n_pls=10, n_gmm=5)\n\n        ensemble_preds = []\n        ensemble_weights = []\n        stack_train_features = []\n        stack_test_features = []\n        model_tags = []\n\n        def register_model(name, oof_preds, test_preds, processed_cv):\n            weight = max(processed_cv, 1e-6)\n            ensemble_preds.append(test_preds)\n            ensemble_weights.append(weight)\n            stack_train_features.append(oof_preds)\n            stack_test_features.append(test_preds)\n            model_tags.append(name)\n            print(f\"[Stack] {name}: processed CV {processed_cv:.6f}\")\n        def run_siglip_model(name, estimator):\n            if cfg.fast_mode and cfg.siglip_fast_models and name not in cfg.siglip_fast_models:\n                print(f\"[FastMode] Skipping SigLIP model {name}\")\n                return\n            oof_preds, pred_test = cross_validate(\n                estimator,\n                train_siglip_df,\n                test_siglip_df,\n                feature_engine=feat_engine,\n                semantic_train=sem_train_full,\n                semantic_test=sem_test_full,\n                n_splits_override=cfg.stack_kfolds,\n            )\n            _, proc_score = compare_results(oof_preds, train_siglip_df, return_scores=True)\n            register_model(name, oof_preds, pred_test, proc_score)\n\n        run_siglip_model(\"cat_seed0\", CatBoostRegressor(verbose=0, random_seed=cfg.seed))\n        run_siglip_model(\"svr_seed0\", SVR(kernel='rbf', C=0.7, epsilon=0.02, tol=1e-4, max_iter=100000, cache_size=2000))\n        run_siglip_model(\"xgb_seed0\", XGBRegressor(n_estimators=700, learning_rate=0.02, max_depth=7, subsample=0.9, colsample_bytree=0.95, reg_lambda=1.0, reg_alpha=0.0, tree_method='hist', random_state=cfg.seed))\n        run_siglip_model(\"bayes_ridge\", BayesianRidge())\n        run_siglip_model(\"elastic_net\", ElasticNet(alpha=0.08, l1_ratio=0.4, max_iter=2000))\n        run_siglip_model(\"extra_trees\", ExtraTreesRegressor(n_estimators=600, max_depth=None, min_samples_split=4, min_samples_leaf=2, random_state=cfg.seed))\n        run_siglip_model(\"xgb_seed1\", XGBRegressor(n_estimators=700, learning_rate=0.02, max_depth=7, subsample=0.9, colsample_bytree=0.95, reg_lambda=1.0, reg_alpha=0.0, tree_method='hist', random_state=cfg.seed + 1881))\n        run_siglip_model(\"svr_seed1\", SVR(kernel='rbf', C=0.7, epsilon=0.02, tol=1e-4, max_iter=100000, cache_size=2000))\n        run_siglip_model(\"cat_seed1\", CatBoostRegressor(verbose=0, random_seed=cfg.seed + 404))\n        run_siglip_model(\"ridge_seed0\", Ridge(alpha=0.5))\n        run_siglip_model(\"ridge_seed1\", Ridge(alpha=0.4))\n        run_siglip_model(\"elastic_net_seed1\", ElasticNet(alpha=0.06, l1_ratio=0.35, max_iter=2500))\n        weights = np.array(ensemble_weights, dtype=np.float64)\n        weights /= weights.sum()\n        pred_stack = np.stack(ensemble_preds, axis=0)\n        weighted_pred = np.tensordot(weights, pred_stack, axes=(0, 0))\n        print(f\"Ensemble weights: {[round(w, 4) for w in weights.tolist()]}\")\n\n        if stack_train_features:\n            stack_train_matrix = np.concatenate(stack_train_features, axis=1)\n            stack_test_matrix = np.concatenate(stack_test_features, axis=1)\n            stacker = Ridge(alpha=0.3)\n            stacker.fit(stack_train_matrix, train_siglip_df[TARGET_NAMES].values)\n            stack_train_pred = stacker.predict(stack_train_matrix)\n            stack_cv = competition_metric(train_siglip_df[TARGET_NAMES].values, stack_train_pred)\n            print(f\"[Stack] Ridge layer CV: {stack_cv:.6f}\")\n            stack_test_pred = stacker.predict(stack_test_matrix)\n            pred_advanced = 0.6 * stack_test_pred + 0.4 * weighted_pred\n        else:\n            pred_advanced = weighted_pred\n\n        mix = cfg.siglip_hybrid_weight\n        if mix < 1.0 and cfg.enable_legacy_siglip:\n            legacy_models = [\n                (\"gb\", GradientBoostingRegressor()),\n                (\"hgb\", HistGradientBoostingRegressor()),\n                (\"cat\", CatBoostRegressor(verbose=0)),\n                (\"lgbm\", LGBMRegressor(verbose=-1)),\n            ]\n            legacy_preds = []\n            for name, model in legacy_models:\n                oof_leg, pred_leg = cross_validate(\n                    model,\n                    train_siglip_df_legacy,\n                    test_siglip_df_legacy,\n                    feature_engine=feat_engine,\n                    semantic_train=sem_train_full,\n                    semantic_test=sem_test_full,\n                    n_splits_override=cfg.legacy_kfolds,\n                )\n                _, proc_leg = compare_results(oof_leg, train_siglip_df_legacy, return_scores=True)\n                print(f\"[Legacy] {name}: processed CV {proc_leg:.6f}\")\n                legacy_preds.append(pred_leg)\n            pred_legacy = np.mean(legacy_preds, axis=0)\n            pred_test = mix * pred_advanced + (1 - mix) * pred_legacy\n            print(f\"[SigLIP] Hybrid advanced/legacy weight: {mix:.2f}/{1 - mix:.2f}\")\n        else:\n            pred_test = pred_advanced\n\n        siglip_pred = pred_test\n        test_df[TARGET_NAMES] = pred_test\n        test_df = post_process_biomass(test_df)\n        sub_df = melt_table(test_df)\n        sub_df[['sample_id', 'target']].to_csv(\"submission1.csv\", index=False)\n        pillar_paths[\"siglip\"] = Path(\"submission1.csv\")\n    else:\n        print(\"[Config] Skipping SigLIP stack.\")\n        pillar_paths[\"siglip\"] = None\n\n\n    dinov2_pred = None\n    if cfg.run_dinov2:\n        print(\"Dinov2-L Pillar ===\")\n        dinov2_path = \"/kaggle/input/dinov2/pytorch/large/1\"\n        dino_train_df = assign_folds(base_train_df.copy(), cfg.dino_kfolds, cfg.seed)\n        preview_embedding_columns(dino_train_df, \"Dinov2 train (pre-strip)\")\n        dino_train_df = strip_embedding_columns(dino_train_df, prefixes=(\"emb\", \"dino_emb\"))\n        preview_embedding_columns(dino_train_df, \"Dinov2 train (post-strip)\")\n        dino_train_df[\"image_path\"] = dino_train_df[\"image_path\"].apply(ensure_abs_path)\n        dino_test_meta = pd.DataFrame({\"image_path\": test_df[\"image_path\"].apply(ensure_abs_path)})\n        dino_test_meta = strip_embedding_columns(dino_test_meta, prefixes=(\"emb\", \"dino_emb\"))\n        dino_train_df = compute_embeddings(dinov2_path, dino_train_df, patch_size=518)\n        dino_test_df = compute_embeddings(dinov2_path, dino_test_meta.copy(), patch_size=518)\n        dino_train_df, dino_emb_cols = rename_embedding_columns(dino_train_df, base_prefix=\"dino_emb\")\n        dino_test_df, _ = rename_embedding_columns(dino_test_df, base_prefix=\"dino_emb\")\n        preview_embedding_columns(dino_train_df, \"Dinov2 train (post-rename)\")\n        preview_embedding_columns(dino_test_df, \"Dinov2 test (post-rename)\")\n        dino_train_df, dino_test_df = append_cluster_features(\n            dino_train_df,\n            dino_test_df,\n            embed_prefix=\"dino_emb\",\n            cluster_sizes=(16, 32, 48, 64, 80),\n            random_state=cfg.seed,\n        )\n        feat_engine_dino = SupervisedEmbeddingEngine(n_pca=0.995, n_pls=10, n_gmm=4)\n\n        dino_preds = []\n        dino_oof = []\n        dino_weights = []\n        dino_tags = []\n\n        def register_dino(name, oof_preds, test_preds, processed_cv):\n            dino_preds.append(test_preds)\n            dino_oof.append(oof_preds)\n            dino_weights.append(max(processed_cv, 1e-6))\n            dino_tags.append(name)\n            print(f\"[Dinov2] {name}: processed CV {processed_cv:.6f}\")\n\n        oof_dino_cat, pred_dino_cat = cross_validate(\n            CatBoostRegressor(verbose=0, random_seed=cfg.seed, iterations=900, depth=7, learning_rate=0.028),\n            dino_train_df,\n            dino_test_df,\n            feature_engine=feat_engine_dino,\n            semantic_train=sem_train_full,\n            semantic_test=sem_test_full,\n            n_splits_override=cfg.dino_kfolds,\n        )\n        _, proc_dino_cat = compare_results(oof_dino_cat, dino_train_df, return_scores=True)\n        register_dino(\"dinov2_cat\", oof_dino_cat, pred_dino_cat, proc_dino_cat)\n\n        oof_dino_xgb, pred_dino_xgb = cross_validate(\n            XGBRegressor(\n                n_estimators=650, learning_rate=0.025, max_depth=7, subsample=0.9, colsample_bytree=0.95,\n                reg_lambda=1.3, reg_alpha=0.1, tree_method='hist', random_state=cfg.seed\n            ),\n            dino_train_df,\n            dino_test_df,\n            feature_engine=feat_engine_dino,\n            semantic_train=sem_train_full,\n            semantic_test=sem_test_full,\n            n_splits_override=cfg.dino_kfolds,\n        )\n        _, proc_dino_xgb = compare_results(oof_dino_xgb, dino_train_df, return_scores=True)\n        register_dino(\"dinov2_xgb\", oof_dino_xgb, pred_dino_xgb, proc_dino_xgb)\n\n        oof_dino_lgbm, pred_dino_lgbm = cross_validate(\n            LGBMRegressor(\n                n_estimators=900, learning_rate=0.03, num_leaves=192, subsample=0.85, colsample_bytree=0.9,\n                reg_lambda=0.9, reg_alpha=0.1, random_state=cfg.seed, verbose=-1\n            ),\n            dino_train_df,\n            dino_test_df,\n            feature_engine=feat_engine_dino,\n            semantic_train=sem_train_full,\n            semantic_test=sem_test_full,\n            n_splits_override=cfg.dino_kfolds,\n        )\n        _, proc_dino_lgbm = compare_results(oof_dino_lgbm, dino_train_df, return_scores=True)\n        register_dino(\"dinov2_lgbm\", oof_dino_lgbm, pred_dino_lgbm, proc_dino_lgbm)\n\n        oof_dino_lasso, pred_dino_lasso = cross_validate(\n            Lasso(alpha=0.0005, max_iter=5000),\n            dino_train_df,\n            dino_test_df,\n            feature_engine=feat_engine_dino,\n            semantic_train=sem_train_full,\n            semantic_test=sem_test_full,\n            n_splits_override=cfg.dino_kfolds,\n        )\n        _, proc_dino_lasso = compare_results(oof_dino_lasso, dino_train_df, return_scores=True)\n        register_dino(\"dinov2_lasso\", oof_dino_lasso, pred_dino_lasso, proc_dino_lasso)\n\n        dino_weights = np.array(dino_weights, dtype=np.float64)\n        dino_weights /= dino_weights.sum()\n        dino_stack = np.stack(dino_preds, axis=0)\n        dino_weighted_pred = np.tensordot(dino_weights, dino_stack, axes=(0, 0))\n        dino_oof_stack = np.concatenate(dino_oof, axis=1)\n        dino_test_stack = np.concatenate(dino_preds, axis=1)\n        dino_meta = Ridge(alpha=0.25)\n        dino_meta.fit(dino_oof_stack, dino_train_df[TARGET_NAMES].values)\n        dino_meta_oof = dino_meta.predict(dino_oof_stack)\n        dino_meta_pred = dino_meta.predict(dino_test_stack)\n        meta_cv = competition_metric(dino_train_df[TARGET_NAMES].values, dino_meta_oof)\n        print(f\"[Dinov2] Ridge meta CV: {meta_cv:.6f}\")\n\n        dino_weighted_oof = np.tensordot(dino_weights, np.stack(dino_oof, axis=0), axes=(0, 0))\n        dinov2_pred = 0.7 * dino_meta_pred + 0.3 * dino_weighted_pred\n        dinov2_cv = competition_metric(dino_train_df[TARGET_NAMES].values, 0.7 * dino_meta_oof + 0.3 * dino_weighted_oof)\n        print(f\"[Dinov2] Blended CV: {dinov2_cv:.6f}\")\n\n        dinov2_out = dino_test_df[['image_path']].copy()\n        dinov2_out[TARGET_NAMES] = dinov2_pred\n        dinov2_out = post_process_biomass(dinov2_out)\n        dinov2_sub = melt_table(dinov2_out)\n        dinov2_sub[[\"sample_id\", \"target\"]].to_csv(\"submission4.csv\", index=False)\n        pillar_paths[\"dinov2\"] = Path(\"submission4.csv\")\n    else:\n        print(\"[Config] Skipping Dinov2 pillar.\")\n        pillar_paths[\"dinov2\"] = None\n\n    if cfg.run_dino1:\n        print(\"DINO Model 1 ===\")\n        run_dino_inference()\n        pillar_paths[\"dino\"] = Path(\"submission3.csv\") if Path(\"submission3.csv\").exists() else None\n    else:\n        print(\"[Config] Skipping DINO Model 1 pillar.\")\n        pillar_paths[\"dino\"] = None\n\n    if cfg.run_mvp:\n        print(\"DINO Model 2 ===\")\n        run_mvp_inference()\n        pillar_paths[\"mvp\"] = Path(\"submission2.csv\") if Path(\"submission2.csv\").exists() else None\n    else:\n        print(\"[Config] Skipping MVP pillar.\")\n        pillar_paths[\"mvp\"] = None\n\n    blend_candidates = []\n    default_weights = {\n        \"siglip\": 0.65,\n        \"dino\": 0.25,\n        \"mvp\": 0.10,\n        \"dinov2\": 0.00,\n    }\n    submission_map = {\n        \"siglip\": Path(\"submission1.csv\"),\n        \"dino\": Path(\"submission3.csv\"),\n        \"mvp\": Path(\"submission2.csv\"),\n        \"dinov2\": Path(\"submission4.csv\"),\n    }\n    for key, path in submission_map.items():\n        if path.exists():\n            df = pd.read_csv(path)\n            blend_candidates.append((default_weights[key], df))\n        else:\n            print(f\"[Blend] Missing {path}, skipping.\")\n\n    if not blend_candidates:\n        raise RuntimeError(\"No pillar submissions available for blending.\")\n\n    merged = None\n    total_w = sum(w for w, _ in blend_candidates)\n    for idx, (w, df) in enumerate(blend_candidates, start=1):\n        col = f\"target_{idx}\"\n        df = df.rename(columns={\"target\": col})\n        if merged is None:\n            merged = df\n        else:\n            merged = merged.merge(df, on=\"sample_id\")\n    weight_cols = [c for c in merged.columns if c.startswith(\"target_\")]\n    weights = np.array([w for w, _ in blend_candidates], dtype=np.float64)\n    weights /= total_w\n    merged[\"target\"] = sum(merged[col] * weights[i] for i, col in enumerate(weight_cols))\n    print(f\"[Blend] Final weights (normalized): {', '.join(f'{w:.2f}' for w in weights.tolist())}\")\n\n    final_submission = merged[['sample_id', 'target']]\n    final_submission.to_csv('submission.csv', index=False)\n\n    pillar_dir = Path(\"pillar_outputs\")\n    pillar_dir.mkdir(exist_ok=True)\n    pillar_sources = {\n        pillar_dir / \"submission_siglip.csv\": submission_map[\"siglip\"],\n        pillar_dir / \"submission_dino.csv\": submission_map[\"dino\"],\n        pillar_dir / \"submission_mvp.csv\": submission_map[\"mvp\"],\n        pillar_dir / \"submission_dinov2.csv\": submission_map[\"dinov2\"],\n    }\n    for dst, src in pillar_sources.items():\n        if src.exists():\n            shutil.copy(src, dst)\n            print(f\"[Pillar Export] Copied {src} -> {dst}\")\n        else:\n            print(f\"[Pillar Export] Missing {src}, skipping export.\")\n\nif __name__ == \"__main__\":\n    main()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-01-08T13:15:07.880845Z","iopub.execute_input":"2026-01-08T13:15:07.881655Z"}},"outputs":[],"execution_count":null}]}