Gridlock / MODEL_REPORT.md
parvmittal07's picture
Initial Demo complete
01eb82e
|
Raw
History Blame Contribute Delete
14.7 kB
# Gridlock — Model Report & Implementation Guide
A complete account of everything built for the Astram event dataset: the
predicted variables, the models behind each, every metric on the chronological
hold-out, which implementation is best for each task, and the exact single
commands to reproduce the best models.
- **Dataset:** `astram_events.csv` — 8,173 raw → 8,057 clean Bengaluru traffic
events, 9 Nov 2023 → 8 Apr 2024 (150-day span, 46 raw columns).
- **Two independent implementations:**
1. **Main pipeline** (`src/`, run with `python -m src.train`) — three
operational targets over one shared, leakage-safe feature pipeline.
2. **Standalone hotspot model** (`hotspot_model.py`) — a fourth,
newly-engineered forward-looking target, fully self-contained.
- **Evaluation discipline (all tasks):** chronological train→test split (train on
the past, test on the future), skew-aware metrics, leakage-controlled features,
probability calibration, and decision thresholds chosen on held-out data only.
---
## 0. TL;DR — the four tasks at a glance
| # | Predicted variable | Type (positive rate) | Best implementation | Headline metric | Single command |
|---|--------------------|----------------------|---------------------|-----------------|----------------|
| **T1** | `y_road_closure` — will the event need a closure/diversion? | Binary (7.2%) | Main pipeline | **PR-AUC 0.326** (4.5× base), MCC 0.40 @ balanced | `python -m src.train` |
| **T2** | `y_high_priority` — High vs Low operational priority | Binary (62.2%) | Main pipeline | **PR-AUC 0.984**, MCC 0.90 | `python -m src.train` |
| **T3** | `y_duration_min` — minutes until cleared | Regression (heavy-tailed) | Main pipeline | **log-R² 0.251**, median AE 74 min | `python -m src.train` |
| **T4** | `y_hotspot` — will this ~110 m spot reoffend (≥2 events in 14 days)? | Binary (14.1%) | Standalone hotspot model | **PR-AUC 0.441** (2.8× base), recall 0.88 | `python hotspot_model.py train` |
> **Get every best model in one line:**
> ```bash
> python -m src.train && python hotspot_model.py train
> ```
> The first command trains and deploys the best **T1/T2/T3** models; the second
> trains and deploys the best **T4** model. Details in [§6](#6-how-to-get-the-best-models).
---
## 1. The two implementations
### 1A. Main pipeline — `src/` (`python -m src.train`)
Three **separately tuned models over one shared feature pipeline** (multi-task by
construction, not a single fragile multi-head net). Each model is a **stacked
ensemble**:
```
Optuna-tuned LightGBM ┐
Optuna-tuned XGBoost ├─► logistic meta-learner on out-of-fold preds
Optuna-tuned CatBoost ┘ └─► isotonic calibration └─► decision threshold
```
- **Duration** uses the same ensemble on a `log1p` target for the point estimate,
plus **p10/p50/p90 quantile models** with a conformal correction for an honest
80% prediction interval.
- **Shared features:** temporal + cyclical encodings, spatial bins
(`geo_cluster` KMeans), causal (past-only) hotspot/recurrence counts,
empirical-Bayes causal target-rate encodings, multilingual sentence-transformer
embeddings (per-task PCA width), a bilingual impact lexicon, and missingness
flags.
### 1B. Standalone hotspot model — `hotspot_model.py`
A single self-contained file (no import from `src/`) with its own cleaning,
target, features, training and prediction CLI. One **calibrated LightGBM**
classifier on a strictly causal feature set, with an isotonic calibrator and a
recall-favouring F2 threshold. Artifacts live under `hotspot_artifacts/`.
---
## 2. T1 — Road closure (`y_road_closure`)
**What it predicts:** at report time, will this event require a road closure or
diversion? Drives **barricading and diversion** planning.
**Model:** stacked LightGBM + XGBoost + CatBoost → logistic meta → isotonic
calibration → F2 threshold. Embedding PCA width **96** (closure-specific).
**Test set:** n = 1,611, positive rate **7.2%** (≈116 real closures).
| Metric | Value | Read as |
|--------|-------|---------|
| **Average precision (PR-AUC)** | **0.326** | **4.5× the 7.2% base rate** |
| ROC-AUC | 0.835 | ranks closures well above non-closures |
| Recall | 0.655 | catches ~2/3 of real closures… |
| Precision | 0.277 | …at the recall-favoured deployed threshold (0.10) |
| F2 / F1 | 0.515 / 0.390 | recall-weighted |
| MCC | 0.360 | at deployed threshold (0.40 at balanced point) |
| Balanced accuracy | 0.761 | |
| Brier | 0.057 | well-calibrated probabilities |
| Confusion @ deploy | TP 76 · FP 198 · FN 40 · TN 1297 | 76/116 closures pre-flagged, 40 missed |
**Base learners (OOF AP):** LightGBM 0.380 · XGBoost 0.392 · CatBoost 0.389 →
stack **0.396**.
**The threshold is a policy choice** — the same model, scored at different
operating points (from [reports/metrics.json](reports/metrics.json)):
| Operating point | Threshold | Recall | Precision | F1 | F2 | MCC |
|-----------------|-----------|--------|-----------|----|----|-----|
| **MCC-optimal** (balanced) | 0.213 | 0.509 | 0.399 | 0.447 | 0.482 | **0.402** |
| **F2-optimal** (recall-leaning, deployed) | 0.097 | 0.655 | 0.277 | 0.390 | **0.515** | 0.360 |
| **Recall ≥ 0.8** (never miss) | 0.057 | 0.828 | 0.151 | 0.255 | 0.436 | 0.247 |
**Why it's hard (and honest):** the strongest raw closure predictors (end-point
coordinates / `route_path`) are *consequences* of the closure decision and were
removed as leakage; closure is partly discretionary; and there is real temporal
drift (OOF AP 0.40 → test 0.33). The PR-AUC of 0.326 is genuine signal at 4.5×
the base rate.
---
## 3. T2 — Priority (`y_high_priority`)
**What it predicts:** High vs Low operational priority. Drives the **manpower
tier**.
**Model:** same stacked ensemble; `corridor` deliberately **excluded** (it makes
the label a trivial one-field lookup). Embedding PCA width 32.
**Test set:** n = 1,611, positive rate **62.2%**.
| Metric | Value |
|--------|-------|
| **Average precision (PR-AUC)** | **0.984** |
| ROC-AUC | 0.980 |
| F1 | 0.963 |
| Precision / Recall | 0.951 / 0.975 |
| Balanced accuracy | 0.946 |
| MCC | 0.901 |
| Brier | 0.040 |
| Confusion @ deploy (thr 0.38) | TP 977 · FP 50 · FN 25 · TN 559 |
**Base learners (OOF AP):** LightGBM 0.991 · XGBoost 0.988 · CatBoost 0.986 →
stack **0.989**.
**Note:** even without `corridor`, priority is near-deterministic from *location*
(it encodes "is this on a designated priority corridor?"), which is legitimately
knowable at report time. The high score reflects a **genuinely easy geofencing
task, not leakage** — verified via feature importance and per-junction purity.
This is the "good" task with little headroom left.
---
## 4. T3 — Duration (`y_duration_min`)
**What it predicts:** minutes from start until the event is cleared
(reconstructed by coalescing `resolved → closed → end` minus start, cleaned of
non-positive and automated batch-closure rows). Drives the **manpower estimate +
clearance interval**.
**Model:** stacked ensemble on a `log1p` target (winsorised p99) for the point
estimate, plus p10/p50/p90 quantile models with conformal correction for the
interval. Embedding PCA width 32.
**Test set:** n = 630 valid events (train n = 2,568).
| Metric | Value | Note |
|--------|-------|------|
| **R² (log scale)** | **0.251** | honest fit on the heavy-tailed target |
| Median absolute error | **74 min** | typical incident error |
| MAE (log scale) | 1.44 | central-tendency error |
| MAE / RMSE (raw min) | 2411 / 5581 | inflated by the multi-week construction tail |
| R² (raw min) | 0.092 | misleading — dominated by 140-day outliers |
| MAPE | 17.2 | |
| 80% interval coverage | **0.78** | conformalized → ≈ nominal 80% |
| Median interval width | 119 min | actionable uncertainty band |
**Why two R² numbers:** raw-minute R² (0.09) is dominated by a handful of
days-to-weeks construction events; the **log-scale R² (0.251)** is the honest
measure of central-tendency fit, and the 74-min median error is what a control
room actually experiences on normal incidents.
---
## 5. T4 — Chronic hotspot early warning (`y_hotspot`) — NEW
**What it predicts (engineered from scratch):** at the moment an event is
reported at a location, will that **same ~110 m spot generate ≥ 2 more events
within the next 14 days?** This is a *recurring-hotspot early warning* — instead
of repeatedly firefighting the same junction/pothole/water-logging spot, the
control room gets a flag to send a **root-cause fix** (drainage, resurfacing, a
permanent marshal).
**Model:** single calibrated LightGBM (isotonic + F2 threshold), built entirely
on strictly past-only causal features.
**Test set:** n = 1,383, base rate **15.6%** (overall positive rate 14.1% across
6,914 labelable rows).
| Metric | Value | Read as |
|--------|-------|---------|
| **Average precision (PR-AUC)** | **0.441** | **2.8× the 15.6% base rate** |
| ROC-AUC | 0.790 | |
| Brier | 0.106 | |
| Recall (deployed F2, thr 0.071) | **0.875** | catches 189 of 216 emerging hotspots |
| Precision (deployed) | 0.259 | early-warning favours recall |
| F1 / MCC (deployed) | 0.400 / 0.299 | |
| Confusion @ deploy | TP 189 · FP 541 · FN 27 · TN 626 | only 27 emerging hotspots missed |
Alternative balanced operating point (MCC-optimal, thr 0.138): recall 0.593,
precision 0.414, MCC **0.381**, F2 0.546.
**Leakage safety:** every feature uses only strictly-earlier events; the label
uses only strictly-later events (disjoint time windows); right-censored rows
(without a full 14-day future) are dropped from train/test. **Top features**
(gain): `junc_win30`, `zone_win30`, `ps_win30`, `police_station`, lat/long,
`loc_days_since_first/last`, `area_to_loc_ratio`, `loc_rate_per_day`.
**Face validity:** the highest-risk events are recurring **construction at HAL
Old Airport** (metro work) and **potholes at J.P. Nagar** — exactly the chronic
spots worth a permanent fix. Honest caveat: brand-new locations (no prior
history) almost never turn chronic (6 positives in 383 first-ever sites), and the
model correctly assigns them low risk rather than inventing signal.
---
## 6. How to get the best models
### Best implementation per task
| Task | Best implementation | Command | Why |
|------|---------------------|---------|-----|
| **T1 Road closure** | Main pipeline (`src/`) | `python -m src.train` | Deploys the spw = 1 + PCA-96 config (PR-AUC 0.326), the best validated closure model. |
| **T2 Priority** | Main pipeline (`src/`) | `python -m src.train` | Only and best implementation (PR-AUC 0.984). |
| **T3 Duration** | Main pipeline (`src/`) | `python -m src.train` | Only and best implementation (log-R² 0.251 + calibrated interval). |
| **T4 Chronic hotspot** | Standalone (`hotspot_model.py`) | `python hotspot_model.py train` | Self-contained forward-looking model (PR-AUC 0.441). |
### Single command for the best models
```bash
# from the repo root, with the venv active:
source .venv/bin/activate
# Best T1 + T2 + T3 (Optuna-tuned) AND best T4, one line:
python -m src.train && python hotspot_model.py train
```
- `python -m src.train` → writes the 6 main model artifacts to `models/`,
metrics to [reports/metrics.json](reports/metrics.json), and figures to
`reports/figures/`.
- `python hotspot_model.py train` → writes `hotspot_artifacts/hotspot_bundle.joblib`,
`hotspot_history.parquet`, and `hotspot_metrics.json`.
### Inference (after training)
```bash
# T1/T2/T3 — closure prob, manpower tier, officers, barricading, diversion, duration interval:
python -m src.predict
# T4 — score events and list the top emerging hotspots:
python hotspot_model.py predict
```
### Useful variants
```bash
GRIDLOCK_NO_TUNE=1 python -m src.train # fast smoke run (skips Optuna)
GRIDLOCK_NO_TRANSFORMER=1 python -m src.train # offline TF-IDF text fallback
python -m src.train_best # focused closure model + operating-point table
GRIDLOCK_CLOSURE_SPW=3 python -m src.train_best # trade some PR-AUC for more closure recall
```
---
## 7. Leakage control (applies to all tasks)
- **`LEAKAGE_COLUMNS`** — anything known only after the event unfolds (`status`,
all resolution timestamps, `resolved_at_*`, `comment`) is never a feature. The
biggest trap: **end-point coordinates / `route_path`** are populated only when a
closure/diversion segment is drawn, so `has_end_point` alone "predicts" closure
at AP ≈ 0.98 — these are excluded as leakage.
- **Causal features only** — every history/recurrence/target-rate feature sees
strictly earlier events (`shift(1)` / append-after accumulators).
- **Train-only fitting** — categorical vocabularies, embedding PCA, calibration
and thresholds are learned on training rows; the chronological test set is
untouched until final scoring.
- **`corridor` excluded from T2 only** — it makes priority a trivial lookup.
---
## 8. Artifact map
| Path | Produced by | Contents |
|------|-------------|----------|
| `models/*.joblib` (6) | `python -m src.train` | T1/T2/T3 models, preprocessors, calibrators, thresholds |
| `models/closure_model_best.joblib` | `python -m src.train_best` | focused closure model |
| `models/geo_kmeans.joblib` | `python -m src.train` | spatial bin assignment for inference |
| `data/processed/history.parquet` | `python -m src.train` | causal target-rate history for inference |
| [reports/metrics.json](reports/metrics.json) | `python -m src.train` | full T1/T2/T3 metrics + operating points |
| `reports/figures/*.png` | `python -m src.train` | PR, calibration, SHAP |
| `hotspot_artifacts/hotspot_bundle.joblib` | `python hotspot_model.py train` | T4 model + isotonic + threshold + dtypes |
| `hotspot_artifacts/hotspot_history.parquet` | `python hotspot_model.py train` | event log for causal feature replay |
| `hotspot_artifacts/hotspot_metrics.json` | `python hotspot_model.py train` | full T4 metrics + operating points |
---
## 9. Summary
- **Four predicted variables**, each engineered from a dataset that ships with no
ready-made label, each forecastable strictly from report-time information.
- **T2 (priority)** is the saturated "easy" task (0.984); **T1 (closure)** and
**T3 (duration)** are the genuinely hard operational tasks, improved to PR-AUC
0.326 and log-R² 0.251 through per-task embedding width and causal target-rate
features; **T4 (chronic hotspot)** is a new forward-looking early-warning model
at PR-AUC 0.441 with 0.88 recall.
- **One command per implementation** reproduces every best model:
`python -m src.train && python hotspot_model.py train`.