File size: 2,614 Bytes
8bf7463
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Non-neural baselines. A published neural checkpoint has to beat these."""
from __future__ import annotations

import json
from pathlib import Path

import lightgbm as lgb
import numpy as np
import pandas as pd
from sklearn.isotonic import IsotonicRegression
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.preprocessing import StandardScaler

from common import NUMERIC, report, split

df = pd.read_parquet("data/rows.parquet")
df["network_cat"] = df.network.astype("category")
results: dict = {}
Path("runs").mkdir(exist_ok=True)

for h in (7, 14):
    lab = f"paid_{h}d"
    tr, va, te = split(df, h)
    print(f"\n== paid_{h}d  train {len(tr):,}  val {len(va):,}  test {len(te):,} "
          f"(test hosts {te.host.nunique()}, unseen in train)")
    res = {}

    # 1. Recency rule: the fresher the last paid call, the likelier the next one.
    rec = te.days_since_last_call.fillna(99).to_numpy()
    iso = IsotonicRegression(out_of_bounds="clip", increasing=False).fit(
        va.days_since_last_call.fillna(99), va[lab])
    res["recency_rule"] = report(te, iso.predict(rec), h)

    # 2. Logistic regression on numeric features.
    X = lambda d: d[NUMERIC].fillna(-1).to_numpy()
    lr = make_pipeline(StandardScaler(), LogisticRegression(max_iter=2000)).fit(X(tr), tr[lab])
    res["logreg"] = report(te, lr.predict_proba(X(te))[:, 1], h)

    # 3. LightGBM on numeric + network.
    feats = NUMERIC + ["network_cat"]
    gbm = lgb.LGBMClassifier(n_estimators=600, learning_rate=0.03, num_leaves=31,
                             min_child_samples=50, subsample=0.8, subsample_freq=1,
                             colsample_bytree=0.8, verbose=-1)
    gbm.fit(tr[feats], tr[lab], eval_set=[(va[feats], va[lab])],
            callbacks=[lgb.early_stopping(50, verbose=False)])
    p = gbm.predict_proba(te[feats])[:, 1]
    res["lightgbm"] = report(te, p, h)
    imp = sorted(zip(feats, gbm.booster_.feature_importance("gain")), key=lambda x: -x[1])
    res["lightgbm_top_features"] = [(f, round(float(g), 1)) for f, g in imp[:10]]
    if h == 7:
        gbm.booster_.save_model("runs/lightgbm_paid7d.txt")
        np.save("runs/lightgbm_paid7d_test_pred.npy", p)

    for k, v in res.items():
        if k.endswith("features"):
            print(" ", k, v[:6])
            continue
        print(f"  {k:14s}", "  ".join(f"{seg}: auc {m['auc']} ap {m['ap']} ece {m['ece']}"
                                       for seg, m in v.items()))
    results[f"paid_{h}d"] = res

Path("runs/baselines.json").write_text(json.dumps(results, indent=1))