Spaces:
Sleeping
Sleeping
| """Offline experiment: can we lower validation MAE without breaking the pipeline? | |
| Tests objective choices and extra features on the SAME time-split as production, | |
| so the comparison is apples-to-apples. Prints a table; ships nothing. | |
| """ | |
| import warnings | |
| import numpy as np | |
| import pandas as pd | |
| import lightgbm as lgb | |
| from src import config | |
| from src.data_prep import load_clean | |
| from src.features import add_h3 | |
| from src.model import build_panel, FEATURES as BASE_FEATURES | |
| warnings.filterwarnings("ignore") | |
| np.random.seed(config.RANDOM_STATE) | |
| print("Loading + indexing data ...") | |
| df = add_h3(load_clean()) | |
| panel, keep_cells, all_dates = build_panel(df) | |
| # ---- add candidate features on top of the production panel ---- | |
| panel = panel.sort_values(["h3", "date"]).reset_index(drop=True) | |
| g = panel.groupby("h3")["y"] | |
| yshift = g.shift(1) | |
| # extra short/seasonal lags | |
| for L in [2, 3, 21]: | |
| panel[f"lag_{L}"] = g.shift(L) | |
| # rolling std (volatility) + a mid window mean | |
| for W in [7, 14, 28]: | |
| panel[f"roll_std_{W}"] = (yshift.groupby(panel["h3"]).rolling(W, min_periods=2) | |
| .std().reset_index(level=0, drop=True)) | |
| panel["roll_mean_14"] = (yshift.groupby(panel["h3"]).rolling(14, min_periods=1) | |
| .mean().reset_index(level=0, drop=True)) | |
| # EWMA (recency-weighted level) | |
| panel["ewm_7"] = (yshift.groupby(panel["h3"]).ewm(span=7, min_periods=1) | |
| .mean().reset_index(level=0, drop=True)) | |
| # same-weekday mean over the last 4 occurrences (weekly seasonality, no leak) | |
| sd = panel.groupby(["h3", "dow"])["y"].shift(1) | |
| panel["samedow_mean4"] = (sd.groupby([panel["h3"], panel["dow"]]) | |
| .rolling(4, min_periods=1).mean() | |
| .reset_index(level=[0, 1], drop=True)) | |
| # expanding cell mean (shifted -> no leak) | |
| panel["cell_expmean"] = (g.apply(lambda s: s.shift(1).expanding().mean()) | |
| .reset_index(level=0, drop=True)) | |
| panel["roll_std_7"] = panel["roll_std_7"].fillna(0) | |
| panel["roll_std_14"] = panel["roll_std_14"].fillna(0) | |
| panel["roll_std_28"] = panel["roll_std_28"].fillna(0) | |
| EXTRA = ["lag_2", "lag_3", "lag_21", "roll_std_7", "roll_std_14", "roll_std_28", | |
| "roll_mean_14", "ewm_7", "samedow_mean4", "cell_expmean"] | |
| model_df = panel.dropna(subset=[f"lag_{max(config.LAGS)}"]).copy() | |
| cutoff = all_dates.max() - pd.Timedelta(days=config.VALID_DAYS) | |
| train = model_df[model_df["date"] <= cutoff] | |
| valid = model_df[model_df["date"] > cutoff] | |
| yv = valid["y"].values | |
| base_mae = float(np.mean(np.abs(valid["lag_7"].values - yv))) | |
| print(f"rows train/valid: {len(train):,}/{len(valid):,} | " | |
| f"same-weekday baseline MAE: {base_mae:.3f}\n") | |
| def run(feats, objective, label, log=False, extra_params=None): | |
| p = dict(metric="mae", learning_rate=0.05, num_leaves=63, min_data_in_leaf=50, | |
| feature_fraction=0.8, bagging_fraction=0.8, bagging_freq=1, | |
| seed=config.RANDOM_STATE, verbose=-1, objective=objective) | |
| if extra_params: | |
| p.update(extra_params) | |
| ytr = np.log1p(train["y"]) if log else train["y"] | |
| dtr = lgb.Dataset(train[feats], ytr) | |
| dva = lgb.Dataset(valid[feats], (np.log1p(valid["y"]) if log else valid["y"]), | |
| reference=dtr) | |
| m = lgb.train(p, dtr, num_boost_round=1200, valid_sets=[dva], | |
| callbacks=[lgb.early_stopping(80), lgb.log_evaluation(0)]) | |
| pred = m.predict(valid[feats], num_iteration=m.best_iteration) | |
| if log: | |
| pred = np.expm1(pred) | |
| mae = float(np.mean(np.abs(np.clip(pred, 0, None) - yv))) | |
| imp = 100 * (base_mae - mae) / base_mae | |
| print(f" {label:<46} MAE={mae:.3f} ({imp:+.1f}% vs baseline) it={m.best_iteration}") | |
| return mae | |
| print("=== production config (reproduce) ===") | |
| run(BASE_FEATURES, "regression", "L2 objective, base features [CURRENT]") | |
| print("\n=== change the training objective only (base features) ===") | |
| run(BASE_FEATURES, "regression_l1", "L1/MAE objective") | |
| run(BASE_FEATURES, "huber", "Huber objective") | |
| run(BASE_FEATURES, "fair", "Fair objective") | |
| print("\n=== add features ===") | |
| run(BASE_FEATURES + EXTRA, "regression", "L2 + extra features") | |
| run(BASE_FEATURES + EXTRA, "regression_l1", "L1 + extra features") | |
| run(BASE_FEATURES + EXTRA, "huber", "Huber + extra features") | |
| print("\n=== best combo + mild regularization ===") | |
| run(BASE_FEATURES + EXTRA, "regression_l1", "L1 + extra + L2 reg + smaller leaves", | |
| extra_params=dict(lambda_l2=1.0, num_leaves=48, min_data_in_leaf=80)) | |
| run(BASE_FEATURES + EXTRA, "huber", "Huber + extra + reg", | |
| extra_params=dict(lambda_l2=1.0, num_leaves=48, min_data_in_leaf=80)) | |
| print("\n=== log1p target variants ===") | |
| run(BASE_FEATURES + EXTRA, "regression", "log1p + L2 + extra", log=True) | |
| run(BASE_FEATURES + EXTRA, "regression_l1", "log1p + L1 + extra", log=True) | |