{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.12.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceType":"competition","sourceId":112509,"databundleVersionId":14254895,"isSourceIdPinned":false}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport gc\nimport time\nimport warnings\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nimport cv2\n\nfrom sklearn.model_selection import KFold\nfrom sklearn.metrics import r2_score\nfrom sklearn.multioutput import MultiOutputRegressor\nfrom scipy.optimize import minimize\n\nimport xgboost as xgb\nimport lightgbm as lgb\nfrom catboost import CatBoostRegressor\n\nwarnings.filterwarnings('ignore')\n\n# --- 1. Configuration ---\nCFG = {\n    'n_folds': 5,\n    'seed': 42,\n    'targets':[\"Dry_Green_g\", \"Dry_Dead_g\", \"Dry_Clover_g\", \"GDM_g\", \"Dry_Total_g\"],\n    # Official Competition Metric Weights\n    'target_weights':[0.1, 0.1, 0.1, 0.2, 0.5] \n}\n\nprint(\"--- 1. Load and Pivot Data ---\")\ndef find_first_file(filename: str):\n    matches = list(Path(\"/kaggle/input\").rglob(filename))\n    return matches[0] if matches else None\n\ntrain_csv_path = find_first_file(\"train.csv\")\ntest_csv_path = find_first_file(\"test.csv\")\ninput_dir = train_csv_path.parent\n\ndef make_wide_df(df, is_train=True):\n    val_col = 'target' if is_train else 'sample_id'\n    return df.pivot_table(index='image_path', columns='target_name', values=val_col, aggfunc='first').reset_index()\n\ntrain_wide = make_wide_df(pd.read_csv(train_csv_path), is_train=True)\ntest_wide = make_wide_df(pd.read_csv(test_csv_path), is_train=False)\n\n# Fail-Safe missing targets\nfor t in CFG['targets']:\n    if t not in train_wide.columns: train_wide[t] = 0.0\n\n# Apply LOG1P Transform (Target is evaluated on log-scale per competition rules)\nfor t in CFG['targets']:\n    train_wide[t] = np.log1p(train_wide[t].fillna(0).clip(lower=0))\n\n\nprint(\"\\n--- 2. Advanced Agricultural Image Feature Extraction ---\")\n# We now rely 100% on Image features to prevent hidden-test-set covariate shift\ndef extract_agri_stats(df, img_dir):\n    stats = []\n    for img_path in df['image_path']:\n        full_path = os.path.join(img_dir, img_path)\n        try:\n            img_bgr = cv2.imread(full_path)\n            if img_bgr is None: raise ValueError\n            \n            img_rgb = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)\n            img_hsv = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2HSV)\n            \n            # Basic Color Stats\n            mean_rgb = img_rgb.mean(axis=(0,1))\n            std_rgb = img_rgb.std(axis=(0,1))\n            mean_hsv = img_hsv.mean(axis=(0,1))\n            \n            # Agricultural Indices\n            img_norm = img_rgb.astype(np.float32) / 255.0\n            r, g, b = img_norm[:,:,0], img_norm[:,:,1], img_norm[:,:,2]\n            \n            # ExG (Excess Green) - Strong proxy for live biomass\n            exg = 2*g - r - b\n            # CIVE (Color Index of Vegetation)\n            cive = 0.441*r - 0.811*g + 0.385*b\n            \n            # Texture (Laplacian Variance) - Strong proxy for CANOPY HEIGHT\n            gray = cv2.cvtColor(img_bgr, cv2.COLOR_BGR2GRAY)\n            texture = cv2.Laplacian(gray, cv2.CV_64F).var()\n            \n            # Green Fraction\n            green_mask = cv2.inRange(img_hsv, (35, 40, 40), (85, 255, 255))\n            green_frac = np.sum(green_mask > 0) / (img_rgb.shape[0] * img_rgb.shape[1])\n            \n            row_stats = np.concatenate([\n                mean_rgb, std_rgb, mean_hsv,[exg.mean(), exg.std(), cive.mean(), texture, green_frac]\n            ])\n            stats.append(row_stats)\n        except Exception:\n            stats.append(np.zeros(14)) # 14 features\n    return np.array(stats)\n\nimg_feature_names =[\n    'R_mean', 'G_mean', 'B_mean', 'R_std', 'G_std', 'B_std', \n    'H_mean', 'S_mean', 'V_mean', \n    'ExG_mean', 'ExG_std', 'CIVE_mean', 'Texture_Var', 'Green_Frac'\n]\n\nprint(\"Extracting Train Image Stats...\")\nX = pd.DataFrame(extract_agri_stats(train_wide, input_dir), columns=img_feature_names)\ny = train_wide[CFG['targets']]\n\nprint(\"Extracting Test Image Stats...\")\nX_test = pd.DataFrame(extract_agri_stats(test_wide, input_dir), columns=img_feature_names)\n\n\nprint(\"\\n--- 3. Setting Up 5-Fold Stratified CV ---\")\nkf = KFold(n_splits=CFG['n_folds'], shuffle=True, random_state=CFG['seed'])\n\noof_lgb = np.zeros((len(X), len(CFG['targets'])))\noof_xgb = np.zeros((len(X), len(CFG['targets'])))\noof_cat = np.zeros((len(X), len(CFG['targets'])))\n\ntest_preds_lgb = np.zeros((len(X_test), len(CFG['targets'])))\ntest_preds_xgb = np.zeros((len(X_test), len(CFG['targets'])))\ntest_preds_cat = np.zeros((len(X_test), len(CFG['targets'])))\n\n\nprint(\"\\n--- 4. Training Pure-Vision Ensembles ---\")\noverall_start = time.time()\n\nfor fold, (train_idx, valid_idx) in enumerate(kf.split(X)):\n    print(f\"\\n[ Fold {fold+1} / {CFG['n_folds']} ]\")\n    fold_start = time.time()\n    \n    X_train, y_train = X.iloc[train_idx], y.iloc[train_idx]\n    X_valid, y_valid = X.iloc[valid_idx], y.iloc[valid_idx]\n\n    # --- LightGBM ---\n    lgb_base = lgb.LGBMRegressor(n_estimators=300, learning_rate=0.03, max_depth=4, num_leaves=15, colsample_bytree=0.8, subsample=0.8, random_state=42, n_jobs=-1, verbose=-1)\n    lgb_model = MultiOutputRegressor(lgb_base).fit(X_train, y_train)\n    oof_lgb[valid_idx] = lgb_model.predict(X_valid)\n    test_preds_lgb += lgb_model.predict(X_test) / CFG['n_folds']\n\n    # --- XGBoost ---\n    xgb_base = xgb.XGBRegressor(n_estimators=300, learning_rate=0.03, max_depth=4, subsample=0.8, colsample_bytree=0.8, random_state=42, tree_method='hist')\n    xgb_model = MultiOutputRegressor(xgb_base).fit(X_train, y_train)\n    oof_xgb[valid_idx] = xgb_model.predict(X_valid)\n    test_preds_xgb += xgb_model.predict(X_test) / CFG['n_folds']\n\n    # --- CatBoost ---\n    cat_base = CatBoostRegressor(iterations=300, learning_rate=0.03, depth=4, random_state=42, verbose=False)\n    cat_model = MultiOutputRegressor(cat_base).fit(X_train, y_train)\n    oof_cat[valid_idx] = cat_model.predict(X_valid)\n    test_preds_cat += cat_model.predict(X_test) / CFG['n_folds']\n    \n    print(f\"  => Fold Time: {time.time() - fold_start:.2f}s\")\n\nprint(f\"\\nAll Folds Training Time: {time.time() - overall_start:.2f}s\")\n\n\nprint(\"\\n--- 5. Dynamic Weight Optimization (Aligned to Official Metric) ---\")\ndef competition_metric(y_true, y_pred):\n    # Kaggle evaluates R2 on the LOG scaled values, weighted by target importance\n    return r2_score(y_true, y_pred, multioutput=CFG['target_weights'])\n\ndef objective(w):\n    w = np.clip(w, 0, 1)\n    if np.sum(w) == 0: return 1.0\n    w = w / np.sum(w)\n    blend_log = (w[0] * oof_lgb) + (w[1] * oof_xgb) + (w[2] * oof_cat)\n    return -competition_metric(y, blend_log) # Optimize exact metric\n\nres = minimize(objective,[0.33, 0.33, 0.33], method='Nelder-Mead')\nopt_weights = np.clip(res.x, 0, 1) / np.sum(np.clip(res.x, 0, 1))\n\nprint(f\"Optimal Weights Discovered -> LGB: {opt_weights[0]:.3f} | XGB: {opt_weights[1]:.3f} | CAT: {opt_weights[2]:.3f}\")\n\noof_blend_log = (opt_weights[0] * oof_lgb) + (opt_weights[1] * oof_xgb) + (opt_weights[2] * oof_cat)\nprint(f\"🔥 FINAL OFFICIAL METRIC CV SCORE: {competition_metric(y, oof_blend_log):.5f}\")\n\n\nprint(\"\\n--- 6. De-Log & Save Submission ---\")\n# Revert log predictions back to raw grams for submission\ntest_preds_log = (opt_weights[0] * test_preds_lgb) + (opt_weights[1] * test_preds_xgb) + (opt_weights[2] * test_preds_cat)\ntest_preds_raw = np.expm1(test_preds_log)\n\nfor i, target in enumerate(CFG['targets']):\n    test_wide[f'pred_{target}'] = np.maximum(0, test_preds_raw[:, i])\n\nsubmission_rows =[]\nfor _, row in test_wide.iterrows():\n    for target in CFG['targets']:\n        if target in test_wide.columns:\n            sample_id = row[target]\n            if pd.notna(sample_id):\n                submission_rows.append({\n                    'sample_id': sample_id,\n                    'target': row[f'pred_{target}']\n                })\n\nsubmission = pd.DataFrame(submission_rows)\nsubmission.to_csv('/kaggle/working/submission.csv', index=False)\nprint(f\"✅ Successfully saved 'submission.csv'! Shape: {submission.shape}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-04-17T10:08:53.001220Z","iopub.execute_input":"2026-04-17T10:08:53.002028Z","iopub.status.idle":"2026-04-17T10:10:52.466661Z","shell.execute_reply.started":"2026-04-17T10:08:53.001995Z","shell.execute_reply":"2026-04-17T10:10:52.465782Z"}},"outputs":[],"execution_count":null}]}