File size: 4,799 Bytes
c8a8b27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
"""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)