| """Phase G.0a β Build 8-way hazard category labels for the v3 cache. |
| |
| Heuristic mapping from (source, category) β hazard index, using the taxonomy |
| from `lkalert/models/adaptive_window.py:49-58`: |
| 0 = HAZARD_PEDESTRIAN |
| 1 = HAZARD_VRURIDER |
| 2 = HAZARD_VEHICLE_CROSS |
| 3 = HAZARD_VEHICLE_ONCOMING |
| 4 = HAZARD_VEHICLE_LEAD |
| 5 = HAZARD_WEATHER |
| 6 = HAZARD_INFRASTRUCTURE |
| 7 = HAZARD_NONE |
| |
| This is an auxiliary-loss label set β it doesn't need to be ground truth. |
| The AdaptiveWindow uses hazard logits to bias window choice; even a noisy |
| 3-way effective mapping (non_ego β cross, ego_positive β lead, safe β none) |
| gives the model a meaningful inductive bias for window selection. |
| |
| Output: data/policy_labels/hazard_categories_{train_9k,multisrc_val}.json |
| """ |
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| from collections import Counter |
| from pathlib import Path |
|
|
| import torch |
|
|
| ROOT = Path(__file__).resolve().parents[1] |
|
|
|
|
| |
| |
| def map_to_hazard(source: str, category: str) -> int: |
| src = (source or "").lower() |
| cat = (category or "").lower() |
|
|
| |
| if cat == "safe_neg" or cat.endswith("silent"): |
| return 7 |
|
|
| |
| if "non_ego" in cat or "cross" in cat: |
| return 2 |
|
|
| |
| if "ego" in cat or cat in ("ego_alert", "ego_observe"): |
| if src in ("dota",): |
| return 4 |
| if src in ("dada",): |
| return 3 |
| if src in ("nexar",): |
| return 4 |
| return 4 |
|
|
| |
| if "positive" in cat: |
| return 4 |
|
|
| |
| if src == "dota": |
| return 4 |
| if src == "dada": |
| return 3 |
| if src == "nexar": |
| return 4 |
| if src == "dad": |
| return 4 |
| return 4 |
|
|
|
|
| def build_for_cache(cache_path: Path, out_path: Path): |
| cache = torch.load(cache_path, weights_only=False, map_location="cpu") |
| ids = cache["ids"] |
| sources = cache["source"] |
| cats = cache["category"] |
| n = len(ids) |
| print(f"[load] {cache_path}: N={n}") |
|
|
| hazard_idx = [] |
| for i in range(n): |
| h = map_to_hazard(sources[i], cats[i]) |
| hazard_idx.append(h) |
|
|
| dist = Counter(hazard_idx) |
| print(f" hazard dist: {dict(sorted(dist.items()))}") |
| src_dist = Counter(sources) |
| cat_dist = Counter(cats) |
| print(f" source dist: {dict(src_dist.most_common(8))}") |
| print(f" category dist: {dict(cat_dist.most_common(8))}") |
|
|
| out = { |
| "schema": "v3_hazard_labels_v1", |
| "cache_path": str(cache_path), |
| "n_samples": n, |
| "taxonomy": { |
| 0: "PEDESTRIAN", 1: "VRURIDER", 2: "VEHICLE_CROSS", |
| 3: "VEHICLE_ONCOMING", 4: "VEHICLE_LEAD", 5: "WEATHER", |
| 6: "INFRASTRUCTURE", 7: "NONE", |
| }, |
| "rule_source": "heuristic (source Γ category) β auxiliary supervision", |
| "labels": hazard_idx, |
| "ids": ids, |
| "dist": dict(dist), |
| } |
| out_path.parent.mkdir(parents=True, exist_ok=True) |
| out_path.write_text(json.dumps(out, indent=None)) |
| print(f"[save] {out_path}") |
|
|
|
|
| def main(): |
| ap = argparse.ArgumentParser(description=__doc__) |
| ap.add_argument("--train_cache", type=Path, |
| default=ROOT / "data/belief_cache_v3/sft_x_v3__train_9k.pt") |
| ap.add_argument("--val_cache", type=Path, |
| default=ROOT / "data/belief_cache_v3/sft_x_v3__multisrc_val.pt") |
| ap.add_argument("--out_dir", type=Path, |
| default=ROOT / "data/policy_labels") |
| args = ap.parse_args() |
|
|
| build_for_cache( |
| args.train_cache, args.out_dir / "hazard_categories_train_9k.json") |
| build_for_cache( |
| args.val_cache, args.out_dir / "hazard_categories_multisrc_val.json") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|