P4Learned Delivery Routing
1 Codebase

train_nn.py

train_nn.py · 79 lines · Download

1"""Train the numpy MLP, fit the two baselines, evaluate on the held-out test week,2and export the predicted zone-to-zone time matrices by hour.3 4    python train_nn.py5"""6import json7 8import numpy as np9 10from delivery import config as C11from delivery import plots as P12from delivery.baselines import DistanceLinear, HistoricalAverage, NNPredictor13from delivery.data import load_processed14from delivery.eval import metrics, metrics_by_band15from delivery.nn import TrainConfig, train16from delivery.travel_time import TimeModel, build_all_weekdays17 18 19def main():20    trips, zones, dist = load_processed()21    Z = len(zones)22    tr, va, te = (trips[trips.split == s] for s in ("train", "val", "test"))23    print(f"train {len(tr):,}  val {len(va):,}  test {len(te):,}  zones {Z}")24 25    print("training NN (numpy MLP, Adam, early stopping)")26    cfg = TrainConfig(seed=C.SEED)27    model = train(tr, va, Z, dist, cfg)28    C.MODELS.mkdir(exist_ok=True)29    model.save(C.MODELS / "mlp.npz")30 31    hist = HistoricalAverage(Z).fit(tr)32    lin = DistanceLinear(dist).fit(tr)33    nn = NNPredictor(model)34    args = [te[c].values for c in ("pu", "do", "hour", "weekday")]35    preds = {"NN (MLP)": nn.predict(*args), "hist-avg (pair,hour)": hist.predict(*args),36             "distance-linear": lin.predict(*args)}37    preds_all = dict(preds, **{"global mean": np.full(len(te), tr.duration_min.mean())})38    y = te.duration_min.values39    res = {"test": {k: metrics(y, p) for k, p in preds_all.items()},40           "by_band": {k: metrics_by_band(te, p) for k, p in preds.items()},41           "train_config": cfg.__dict__, "epochs_run": len(model.history),42           "best_val_mae": float(min(h[2] for h in model.history)),43           "distance_linear_coef": {"intercept": float(lin.intercept_), "slope": float(lin.slope_)}}44    # rare night cells: where hist-avg had to back off45    rare = hist.counts[te.pu.values, te["do"].values, te.hour.values] < hist.min_count46    res["rare_cells"] = {"test_trips_in_rare_cells": int(rare.sum()),47                         **{k: metrics(y[rare], p[rare]) for k, p in preds.items()}} if rare.any() else {}48    print("\n test-week results (minutes)")49    print(f"  {'model':<24}{'MAE':>8}{'RMSE':>8}{'bias':>8}")50    for k, m in res["test"].items():51        print(f"  {k:<24}{m['MAE']:8.3f}{m['RMSE']:8.3f}{m['bias']:8.3f}")52 53    C.OUTPUTS.mkdir(exist_ok=True)54    (C.OUTPUTS / "nn_results.json").write_text(json.dumps(res, indent=2))55    (C.ROOT / "report" / "results").mkdir(parents=True, exist_ok=True)56    (C.ROOT / "report" / "results" / "nn_results.json").write_text(json.dumps(res, indent=2))57 58    # time matrices (taxi minutes; the planner multiplies by VAN_FACTOR)59    mats = {name: build_all_weekdays(p, Z) for name, p in60            (("nn", nn), ("hist", hist), ("dist", lin))}61    np.savez_compressed(C.MODELS / "time_matrices.npz", **mats)62    print("FIFO check (|M[h+1]-M[h]| < 60):", {k: TimeModel(m[1]).is_fifo() for k, m in mats.items()})63    with open(C.OUTPUTS / "time_matrix_nn_tuesday.csv", "w") as f:64        f.write("hour,pu_idx,do_idx,pu_zone,do_zone,minutes\n")65        names = zones.sort_values("idx").zone.tolist()66        for h in range(24):67            for i in range(Z):68                for j in range(Z):69                    f.write(f"{h},{i},{j},\"{names[i]}\",\"{names[j]}\",{mats['nn'][1, h, i, j]:.2f}\n")70 71    P.learning_curve(model.history, C.OUTPUTS / "nn_learning_curve.png")72    P.mae_by_hour(te, preds, C.OUTPUTS / "nn_mae_by_hour.png")73    P.pred_vs_actual(y, preds["NN (MLP)"], C.OUTPUTS / "nn_pred_vs_actual.png")74    P.matrix_heatmaps(mats["nn"][1], zones, C.OUTPUTS / "time_matrix_nn.png")75    print("saved models/mlp.npz, models/time_matrices.npz and figures in outputs/")76 77 78if __name__ == "__main__":79    main()