{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"isSourceIdPinned":false,"sourceType":"competition"},{"sourceId":290775987,"sourceType":"kernelVersion"},{"sourceId":290777472,"sourceType":"kernelVersion"}],"dockerImageVersionId":31234,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# 🧱 STEP 1 — Load Everything","metadata":{"_uuid":"fdd6981e-d15a-4362-9d99-4ee2dfe8e26a","_cell_guid":"816ec213-eb26-4ca5-9e73-7f2c6c4b1c2f","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"'''import numpy as np\nimport pandas as pd\nimport random\nimport joblib\n\nfrom sklearn.decomposition import PCA\nfrom sklearn.model_selection import KFold\nfrom sklearn.preprocessing import StandardScaler\nfrom sklearn.metrics import mean_squared_error\n\nfrom lightgbm import LGBMRegressor'''","metadata":{"_uuid":"c8871b12-4343-42a7-b39c-22a4f14a959c","_cell_guid":"78be8322-b09f-477a-a3e2-bec4998ac284","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:45.161465Z","iopub.execute_input":"2026-01-14T12:54:45.161706Z","iopub.status.idle":"2026-01-14T12:54:45.173477Z","shell.execute_reply.started":"2026-01-14T12:54:45.161680Z","shell.execute_reply":"2026-01-14T12:54:45.172260Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''SEED = 42\nnp.random.seed(SEED)\nrandom.seed(SEED)'''","metadata":{"_uuid":"5d22029e-076b-4eea-b648-c256f1bc54a6","_cell_guid":"198457a3-bdd9-434d-b539-5e3838e9ef69","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:45.175794Z","iopub.execute_input":"2026-01-14T12:54:45.176489Z","iopub.status.idle":"2026-01-14T12:54:45.192538Z","shell.execute_reply.started":"2026-01-14T12:54:45.176434Z","shell.execute_reply":"2026-01-14T12:54:45.191467Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# embeddings\n'''siglip = np.load(\"/kaggle/input/csiro-embbedings-2-0/train_siglip.npy\")\ndino   = np.load(\"/kaggle/input/csiro-embbedings-2-0/train_dino.npy\")\n\nmvp_bio   = np.load(\"/kaggle/input/3mvp-models/train_mvp_biomass.npy\")\nmvp_ratio = np.load(\"/kaggle/input/3mvp-models/train_mvp_ratio.npy\")\nmvp_rank  = np.load(\"/kaggle/input/3mvp-models/train_mvp_rank.npy\")\n\n# metadata\nids = np.load(\"/kaggle/input/csiro-embbedings-2-0/train_image_ids.npy\")\ntrain = pd.read_csv(\"/kaggle/input/csiro-biomass/train.csv\")'''","metadata":{"_uuid":"8743efab-deaa-4ce6-b879-51aadf98de93","_cell_guid":"14b920d0-9fb0-4db5-a007-5159864ebefb","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:45.193794Z","iopub.execute_input":"2026-01-14T12:54:45.194209Z","iopub.status.idle":"2026-01-14T12:54:45.210631Z","shell.execute_reply.started":"2026-01-14T12:54:45.194170Z","shell.execute_reply":"2026-01-14T12:54:45.209659Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''assert siglip.shape[0] == dino.shape[0] == len(ids), \\\n    \"❌ Embeddings and image_ids length mismatch\"'''","metadata":{"_uuid":"824002ed-521e-45b3-91b2-3fe614b16f69","_cell_guid":"6585b428-9442-4378-8217-54c9a0c71117","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:45.211890Z","iopub.execute_input":"2026-01-14T12:54:45.212689Z","iopub.status.idle":"2026-01-14T12:54:45.226400Z","shell.execute_reply.started":"2026-01-14T12:54:45.212648Z","shell.execute_reply":"2026-01-14T12:54:45.225251Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🧩 STEP 2 — Build image-level target table","metadata":{"_uuid":"cdf76164-b26d-426c-b1eb-b66351312769","_cell_guid":"9f5aa042-fb15-4d1a-84b8-8935a7b92d8a","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"'''# # 🧩 STEP 2 — Sample-level targets (FIX)\n\ntrain[\"image_id\"] = train[\"sample_id\"].str.split(\"__\").str[0]\n\n# ✅ CHANGED: Added high-weight targets (GDM=0.2, Total=0.5)\nTARGETS = [\n    \"Dry_Green_g\", \n    \"Dry_Dead_g\", \n    \"Dry_Clover_g\",\n    \"GDM_g\",         # Weight 0.2\n    \"Dry_Total_g\"    # Weight 0.5 (CRITICAL)\n]\n\ny_sample = train[\"target\"].values\ntarget_names = train[\"target_name\"].values'''","metadata":{"_uuid":"71ac041b-2c1d-4245-9564-37dc0d5ef1c5","_cell_guid":"d12f116e-f191-431b-a034-e8cb37bf8a0e","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:45.227664Z","iopub.execute_input":"2026-01-14T12:54:45.228074Z","iopub.status.idle":"2026-01-14T12:54:45.244826Z","shell.execute_reply.started":"2026-01-14T12:54:45.228043Z","shell.execute_reply":"2026-01-14T12:54:45.243855Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🧠 STEP 3 — PCA (FIXED dimensions)","metadata":{"_uuid":"9eddeb05-877d-4cdb-8766-67b3c082c7d3","_cell_guid":"6dae8601-08c5-42a0-80b0-9bd2e031e93e","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"'''pca_siglip = PCA(n_components=128, random_state=SEED)\npca_dino   = PCA(n_components=128, random_state=SEED)\npca_mvp    = PCA(n_components=64,  random_state=SEED)\n\nX_sig = pca_siglip.fit_transform(siglip)\nX_din = pca_dino.fit_transform(dino)\n\nX_mvp = pca_mvp.fit_transform(\n    np.concatenate([mvp_bio, mvp_ratio, mvp_rank], axis=1)\n)'''","metadata":{"_uuid":"500be799-4e26-41d4-a5a1-e61e49a3f2ba","_cell_guid":"4d8b09b5-22d2-4f91-955f-d82483051c28","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:45.246072Z","iopub.execute_input":"2026-01-14T12:54:45.246631Z","iopub.status.idle":"2026-01-14T12:54:45.261350Z","shell.execute_reply.started":"2026-01-14T12:54:45.246600Z","shell.execute_reply":"2026-01-14T12:54:45.260254Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🧬 STEP 4 — Final Feature Matrix","metadata":{"_uuid":"0ff60071-faf0-46fd-b6d3-508a224703c9","_cell_guid":"21e2028b-18b6-4dcf-8777-572b95803673","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"'''X = np.concatenate([X_sig, X_din, X_mvp], axis=1)\n\nprint(\"Final feature shape:\", X.shape)'''","metadata":{"_uuid":"a2a50935-ca16-47b4-a78c-b3422afa620a","_cell_guid":"91f771e3-8ad6-421c-a6d8-115bff199500","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:45.264183Z","iopub.execute_input":"2026-01-14T12:54:45.264573Z","iopub.status.idle":"2026-01-14T12:54:45.282388Z","shell.execute_reply.started":"2026-01-14T12:54:45.264544Z","shell.execute_reply":"2026-01-14T12:54:45.281317Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''# # 🧬 STEP 4.5 — Expand image features to sample level (FIX)\n\nimage_index = {img_id: i for i, img_id in enumerate(ids)}\n\nX_sample = np.stack([\n    X[image_index[row.image_id]]\n    for _, row in train.iterrows()\n])\n\nprint(\"Sample-level feature shape:\", X_sample.shape)'''","metadata":{"_uuid":"87385d5f-51ee-4965-b210-68f93836c55e","_cell_guid":"b98e8958-b77c-41ec-a671-af044e951308","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:45.283659Z","iopub.execute_input":"2026-01-14T12:54:45.284001Z","iopub.status.idle":"2026-01-14T12:54:45.298690Z","shell.execute_reply.started":"2026-01-14T12:54:45.283962Z","shell.execute_reply":"2026-01-14T12:54:45.297682Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"'''assert X_sample.shape[0] == len(train), \"X_sample rows mismatch\"\nassert X_sample.shape[1] == X.shape[1], \"Feature dimension mismatch\"'''","metadata":{"_uuid":"28c26c38-48e6-4925-8098-a4ce555f0899","_cell_guid":"19715c35-bce2-4b3b-a834-700d818c12ec","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:45.300060Z","iopub.execute_input":"2026-01-14T12:54:45.300378Z","iopub.status.idle":"2026-01-14T12:54:45.320807Z","shell.execute_reply.started":"2026-01-14T12:54:45.300351Z","shell.execute_reply":"2026-01-14T12:54:45.319804Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ==========================================\n# 🧱 STEP 1-4 REPLACEMENT: NO MVP FEATURES\n# ==========================================\n\nimport numpy as np\nimport pandas as pd\nimport random\nimport joblib\nfrom sklearn.decomposition import PCA\nfrom sklearn.model_selection import KFold\nfrom lightgbm import LGBMRegressor\n\nSEED = 42\nnp.random.seed(SEED)\nrandom.seed(SEED)\n\n# 1. Load ONLY the robust embeddings\nsiglip = np.load(\"/kaggle/input/csiro-embbedings-2-0/train_siglip.npy\")\ndino   = np.load(\"/kaggle/input/csiro-embbedings-2-0/train_dino.npy\")\nids    = np.load(\"/kaggle/input/csiro-embbedings-2-0/train_image_ids.npy\")\ntrain  = pd.read_csv(\"/kaggle/input/csiro-biomass/train.csv\")\n\n# 2. Setup Targets (Keep the 5-target fix!)\ntrain[\"image_id\"] = train[\"sample_id\"].str.split(\"__\").str[0]\nTARGETS = [\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"GDM_g\", \"Dry_Total_g\"]\ny_sample = train[\"target\"].values\ntarget_names = train[\"target_name\"].values\n\n# 3. PCA on Backbone Only\n# We keep 128 components each to capture maximum variance\npca_siglip = PCA(n_components=128, random_state=SEED)\npca_dino   = PCA(n_components=128, random_state=SEED)\n\nX_sig = pca_siglip.fit_transform(siglip)\nX_din = pca_dino.fit_transform(dino)\n\n# 4. Final Concatenation (NO MVP)\nX = np.concatenate([X_sig, X_din], axis=1)\nprint(\"Final feature shape:\", X.shape) # Should be (N, 256)\n\n# 4.5 Expand to Sample Level\nimage_index = {img_id: i for i, img_id in enumerate(ids)}\nX_sample = np.stack([X[image_index[row.image_id]] for _, row in train.iterrows()])\n\n# 5. Save the PCAs immediately so we don't forget\njoblib.dump(pca_siglip, \"pca_siglip.pkl\")\njoblib.dump(pca_dino,   \"pca_dino.pkl\")\n# Note: We do NOT save pca_mvp anymore","metadata":{"_uuid":"c9812c37-46ce-46e8-ba23-881d56a8a859","_cell_guid":"1a6f42d1-4837-4505-88bc-bbad45b75b88","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:45.322181Z","iopub.execute_input":"2026-01-14T12:54:45.322550Z","iopub.status.idle":"2026-01-14T12:54:59.594022Z","shell.execute_reply.started":"2026-01-14T12:54:45.322514Z","shell.execute_reply":"2026-01-14T12:54:59.592818Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🔁 STEP 5 — KFold CV (NO TUNING)","metadata":{"_uuid":"11027b03-94c9-4278-b328-ba812dd34fed","_cell_guid":"5e8219b9-ed8f-44b4-bbc2-0f7f5fbda1b6","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"kf = KFold(n_splits=5, shuffle=True, random_state=SEED)\n\nmodels = {t: [] for t in TARGETS}\noof = np.zeros(len(train))","metadata":{"_uuid":"41a0a22c-2d29-46ff-96a2-e6b692c524ed","_cell_guid":"face8719-c0c6-4a75-b343-e6ad8282e097","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:59.596372Z","iopub.execute_input":"2026-01-14T12:54:59.596761Z","iopub.status.idle":"2026-01-14T12:54:59.602164Z","shell.execute_reply.started":"2026-01-14T12:54:59.596732Z","shell.execute_reply":"2026-01-14T12:54:59.601302Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🌲 STEP 6 — Train LightGBM (PER TARGET)","metadata":{"_uuid":"ac972e79-eb84-4fd3-b696-37d7636529b4","_cell_guid":"b4848fd2-982d-4ca4-80a2-de5e044eb227","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# ==============================\n# 🌲 STEP 6: Linear Training (Tuned High-Precision)\n# ==============================\n\n# Slower learning rate for better convergence\nparams = dict(\n    n_estimators=5000,      # Increased from 2000\n    learning_rate=0.01,     # Lowered from 0.03 (Slower = Better R2)\n    num_leaves=64,          # High capacity\n    min_data_in_leaf=20,\n    subsample=0.8,\n    colsample_bytree=0.8,\n    random_state=SEED,\n    n_jobs=-1\n)\n\nfor tgt in TARGETS:\n    print(f\"\\nTraining target: {tgt}\")\n    \n    rows = target_names == tgt\n    \n    # ✅ REVERT: Use Raw Targets (No Log)\n    y_raw = y_sample[rows]\n    \n    for fold, (tr, va) in enumerate(kf.split(X_sample[rows])):\n        model = LGBMRegressor(**params)\n        \n        # Train on RAW values to maximize R2\n        model.fit(\n            X_sample[rows][tr],\n            y_raw[tr],\n            eval_set=[(\n                X_sample[rows][va],\n                y_raw[va]\n            )],\n            eval_metric=\"rmse\",\n            callbacks=[\n                pd.plotting.info_gbdt_callback() if False else lambda x: None \n            ]\n        )\n        \n        # Predict validation set (Linear scale)\n        pred = model.predict(X_sample[rows][va])\n        oof[np.where(rows)[0][va]] = pred\n        \n        models[tgt].append(model)","metadata":{"_uuid":"441a8df4-d18d-4390-b792-3a4178a01d50","_cell_guid":"4cd5ce9c-2746-4b59-8dc0-7cc152ef7d73","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:54:59.603304Z","iopub.execute_input":"2026-01-14T12:54:59.603652Z","iopub.status.idle":"2026-01-14T12:59:07.574605Z","shell.execute_reply.started":"2026-01-14T12:54:59.603617Z","shell.execute_reply":"2026-01-14T12:59:07.573186Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"_uuid":"59764901-08f2-40cc-bd91-2ba6d5a2238c","_cell_guid":"ab8e4673-8a4e-4ca2-a65c-7bb224f5bab2","trusted":true,"collapsed":true,"jupyter":{"outputs_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 🔹 STEP 6.5 — TRAIN VARIANCE MODELS (Log-Adapted)\n# ============================================================\n\nprint(\"\\nTraining variance models...\")\n\n# Image-level feature matrix\nX_image = X  # shape: (num_images, feature_dim)\nimage_index = {img_id: i for i, img_id in enumerate(ids)}\n\n# ------------------------------------------------------------\n# 1️⃣ Compute IMAGE-LEVEL MEAN predictions\n# ------------------------------------------------------------\nimage_means = {}\n\nfor tgt in TARGETS:\n    preds = np.zeros(len(ids))\n    for model in models[tgt]:\n        # ✅ CRITICAL: Invert the Log prediction to get Grams\n        log_pred = model.predict(X_image)\n        preds += np.expm1(log_pred)\n        \n    preds /= len(models[tgt])\n    image_means[tgt] = preds\n\nprint(\"✔ Image-level mean predictions computed\")\n\n# ------------------------------------------------------------\n# 2️⃣ Compute SAMPLE-LEVEL residuals\n# ------------------------------------------------------------\nresiduals = np.zeros(len(train))\n\nfor i, row in train.iterrows():\n    if row.target_name not in TARGETS:\n        continue\n    \n    img_idx = image_index[row.image_id]\n    mean_pred = image_means[row.target_name][img_idx]\n    residuals[i] = row.target - mean_pred\n\nprint(\"✔ Sample-level residuals computed\")\n\n# ------------------------------------------------------------\n# 3️⃣ Train VARIANCE models (log-variance)\n# ------------------------------------------------------------\nvar_models = {}\n\nfor tgt in TARGETS:\n    rows = target_names == tgt\n    \n    # log(residual^2 + epsilon)\n    y_var = np.log(residuals[rows] ** 2 + 1e-6)\n    \n    var_model = LGBMRegressor(\n        n_estimators=800,\n        learning_rate=0.05,\n        num_leaves=32,\n        min_data_in_leaf=20,\n        subsample=0.9,\n        colsample_bytree=0.9,\n        random_state=SEED,\n        n_jobs=-1\n    )\n    \n    var_model.fit(X_sample[rows], y_var)\n    var_models[tgt] = var_model\n    \n    print(f\"✔ Variance model trained for {tgt}\")\n\nprint(\"✔ ALL variance models trained\\n\")","metadata":{"_uuid":"4f8d4b80-f28f-4c14-ac62-7e575718c0eb","_cell_guid":"60c509c1-908e-418f-b8b4-babe5b6a5443","trusted":true,"execution":{"iopub.status.busy":"2026-01-14T12:59:07.575596Z","iopub.execute_input":"2026-01-14T12:59:07.575919Z","iopub.status.idle":"2026-01-14T12:59:22.180171Z","shell.execute_reply.started":"2026-01-14T12:59:07.575890Z","shell.execute_reply":"2026-01-14T12:59:22.179378Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🧪 STEP 7 — CV Sanity Check","metadata":{"_uuid":"a72a9e10-8b67-44db-a069-42028d660258","_cell_guid":"d2a75233-6777-4c9c-be02-2dc1593999a3","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"# ============================================================\n# 🧪 SANITY CHECK BLOCK — SAMPLE-LEVEL TRAINING (MANDATORY)\n# ============================================================\n\nprint(\"\\n================ SANITY CHECKS ================\\n\")\n\n# ------------------------------------------------------------\n# 1️⃣ BASIC SHAPE CHECKS\n# ------------------------------------------------------------\nprint(\"Image-level feature matrix X:\", X.shape)\nprint(\"Sample-level feature matrix X_sample:\", X_sample.shape)\nprint(\"Train rows:\", len(train))\nprint(\"Targets rows:\", len(y_sample))\n\nassert X.shape[0] == len(ids), \"❌ X must be image-level\"\nassert X_sample.shape[0] == len(train), \"❌ X_sample must match train.csv rows\"\nassert len(y_sample) == len(train), \"❌ y_sample length mismatch\"\n\nprint(\"✔ Shape checks passed\\n\")\n\n# ------------------------------------------------------------\n# 2️⃣ IMAGE → SAMPLE EXPANSION CHECK\n# ------------------------------------------------------------\n# Pick random sample rows and verify they map to same image embedding\nrng = np.random.choice(len(train), size=5, replace=False)\n\nfor idx in rng:\n    img_id = train.iloc[idx].image_id\n    img_idx = np.where(ids == img_id)[0][0]\n    \n    assert np.allclose(\n        X_sample[idx],\n        X[img_idx]\n    ), f\"❌ Feature mismatch for image_id={img_id}\"\n\nprint(\"✔ Image → sample feature expansion correct\\n\")\n\n# ------------------------------------------------------------\n# 3️⃣ TARGET CONSISTENCY CHECK\n# ------------------------------------------------------------\nprint(\"Unique target names:\", np.unique(target_names))\n\nfor t in TARGETS:\n    cnt = np.sum(target_names == t)\n    print(f\"{t}: {cnt} samples\")\n    assert cnt > 0, f\"❌ No samples found for {t}\"\n\nprint(\"✔ Target distribution valid\\n\")\n\n# ------------------------------------------------------------\n# 4️⃣ MODEL COUNT CHECK (KFold × targets)\n# ------------------------------------------------------------\nfor t in TARGETS:\n    print(f\"Models for {t}: {len(models[t])}\")\n    assert len(models[t]) == 5, \"❌ Expected 5 CV models per target\"\n\nprint(\"✔ Model count correct\\n\")\n\n# ------------------------------------------------------------\n# 5️⃣ OOF PREDICTION COVERAGE CHECK\n# ------------------------------------------------------------\ncovered = np.sum(oof != 0)\nprint(f\"OOF non-zero predictions: {covered} / {len(oof)}\")\n\n# ------------------------------------------------------------\n# 5️⃣ OOF PREDICTION COVERAGE CHECK (FIXED)\n# ------------------------------------------------------------\n\nexpected_covered = np.sum(np.isin(target_names, TARGETS))\nprint(f\"OOF non-zero predictions: {covered} / {expected_covered} expected\")\n\nassert covered == expected_covered, \\\n    \"❌ OOF coverage does not match trained target rows\"\n\nprint(\"✔ OOF coverage exactly matches trained targets\\n\")\n\n\nprint(\"✔ OOF coverage looks good\\n\")\n\n# ------------------------------------------------------------\n# 6️⃣ VALUE RANGE CHECK (VERY IMPORTANT)\n# ------------------------------------------------------------\nprint(\"Target value range:\")\nprint(\"Min:\", y_sample.min(), \"Max:\", y_sample.max())\n\nprint(\"OOF prediction range:\")\nprint(\"Min:\", oof.min(), \"Max:\", oof.max())\n\nassert not np.isnan(oof).any(), \"❌ NaNs found in OOF\"\nassert not np.isinf(oof).any(), \"❌ Infs found in OOF\"\n\nprint(\"✔ Value range and NaN checks passed\\n\")\n\n# ------------------------------------------------------------\n# 7️⃣ PER-TARGET LEAKAGE SMELL TEST\n# ------------------------------------------------------------\nfor t in TARGETS:\n    rows = target_names == t\n    corr = np.corrcoef(y_sample[rows], oof[rows])[0, 1]\n    print(f\"{t} OOF vs target correlation:\", corr)\n    assert corr < 0.99\n\nprint(\"✔ No obvious leakage detected\\n\")\n\n\n# ------------------------------------------------------------\n# 8️⃣ FINAL VERDICT\n# ------------------------------------------------------------\nprint(\"✅ ALL SANITY CHECKS PASSED — NOTEBOOK 3 IS SAFE TO USE\\n\")","metadata":{"_uuid":"fa7c72db-7cff-4205-8dd5-2a66b40f35d3","_cell_guid":"0acfd88b-7d34-470b-99bc-8b37a4c5a2b9","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:59:22.182119Z","iopub.execute_input":"2026-01-14T12:59:22.182447Z","iopub.status.idle":"2026-01-14T12:59:22.212215Z","shell.execute_reply.started":"2026-01-14T12:59:22.182417Z","shell.execute_reply":"2026-01-14T12:59:22.210908Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# 🧪 VARIANCE MODEL SANITY CHECK\n# ============================================================\n\nprint(\"\\n===== VARIANCE MODEL CHECK =====\")\n\nfor tgt in TARGETS:\n    rows = target_names == tgt\n    log_var_preds = var_models[tgt].predict(X_sample[rows])\n    \n    print(\n        f\"{tgt} log-var range:\",\n        log_var_preds.min(),\n        log_var_preds.mean(),\n        log_var_preds.max()\n    )\n    \n    assert not np.isnan(log_var_preds).any()\n    assert not np.isinf(log_var_preds).any()\n\nprint(\"✔ Variance models look stable\\n\")","metadata":{"_uuid":"adc34332-6c2d-48b3-91e8-06f9e05d4782","_cell_guid":"a58b53b0-c67d-4037-b41f-dca0cac9fd08","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:59:22.213722Z","iopub.execute_input":"2026-01-14T12:59:22.214063Z","iopub.status.idle":"2026-01-14T12:59:22.306651Z","shell.execute_reply.started":"2026-01-14T12:59:22.214021Z","shell.execute_reply":"2026-01-14T12:59:22.305990Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 💾 STEP 8 — SAVE EVERYTHING (CRITICAL)","metadata":{"_uuid":"d35237da-929f-4e7f-8440-36d52a23e5c9","_cell_guid":"dcbcc5b8-3da5-4e80-a428-4885eecbba84","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"joblib.dump(pca_siglip, \"pca_siglip.pkl\")\njoblib.dump(pca_dino,   \"pca_dino.pkl\")\n\n#joblib.dump(pca_mvp,    \"pca_mvp.pkl\")\n\njoblib.dump(models, \"lgbm_models.pkl\")\njoblib.dump(var_models, \"lgbm_variance_models.pkl\")","metadata":{"_uuid":"75aef2d7-a865-467c-ab8e-ca016d03b4d0","_cell_guid":"15e442bd-fcff-4ddf-8712-8d138ffc4467","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:59:22.307464Z","iopub.execute_input":"2026-01-14T12:59:22.307722Z","iopub.status.idle":"2026-01-14T12:59:31.373189Z","shell.execute_reply.started":"2026-01-14T12:59:22.307695Z","shell.execute_reply":"2026-01-14T12:59:31.372468Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 🟢 SANITY PRINT (MUST SEE)","metadata":{"_uuid":"06e83bf7-7964-4eed-83ef-db07042f7dc9","_cell_guid":"18869120-4dd5-400c-95dd-0f91be886e8b","trusted":true,"collapsed":false,"jupyter":{"outputs_hidden":false}}},{"cell_type":"code","source":"print(\"Saved artifacts:\")\nprint(\"- pca_siglip.pkl\")\nprint(\"- pca_dino.pkl\")\nprint(\"- pca_mvp.pkl\")\nprint(\"- lgbm_models.pkl\")","metadata":{"_uuid":"6ca14e07-9e0e-42f4-9695-efd6f995d9f1","_cell_guid":"95cbfc9c-1a83-4001-a6e6-d71f331059bb","trusted":true,"collapsed":false,"execution":{"iopub.status.busy":"2026-01-14T12:59:31.374982Z","iopub.execute_input":"2026-01-14T12:59:31.375332Z","iopub.status.idle":"2026-01-14T12:59:31.380158Z","shell.execute_reply.started":"2026-01-14T12:59:31.375296Z","shell.execute_reply":"2026-01-14T12:59:31.379293Z"},"jupyter":{"outputs_hidden":false}},"outputs":[],"execution_count":null}]}