Spaces:
Paused
Paused
Download sensorflow/megaeval/analysis.py from whassan/SensorflowStudio: direct link, hf CLI and curl.
- Browser
- Download file 14.3 kB
-
https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/megaeval/analysis.py
- Command line
-
hf download hf://spaces/whassan/SensorflowStudio/sensorflow/megaeval/analysis.py
-
curl -L -o analysis.py https://huggingface.co/spaces/whassan/SensorflowStudio/resolve/main/sensorflow/megaeval/analysis.py
14.3 kB
| """Cross-run analysis: model-vs-model comparison, distribution shift, the "why" | |
| layer, and hybrid similarity search. | |
| All computations run on cubes and the error index — never on raw records. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| from typing import Dict, List, Optional | |
| import numpy as np | |
| import pandas as pd | |
| from sensorflow.megaeval import cube as cube_mod | |
| from sensorflow.megaeval import population as pop_mod | |
| from sensorflow.megaeval.population import DIMENSIONS, DIM_NAMES | |
| from sensorflow.megaeval.runs import ERROR_TYPES, EvaluationRun, MegaStore, run_dir | |
| # ------------------------------------------------------------------ model vs model | |
| DEFAULT_PROMOTION_POLICY = { | |
| "max_recall_drop": 0.010, | |
| "max_precision_drop": 0.015, | |
| "max_safety_recall_drop": 0.005, | |
| "max_cohort_recall_drop": 0.050, | |
| "min_cohort_support": 200, | |
| "cohort_dims": ["road_type", "lighting", "class"], | |
| } | |
| _HEADLINE_METRICS = ["precision", "recall", "f1", "mean_iou", "safety_recall", "anomaly_rate"] | |
| def compare_runs(store: MegaStore, candidate: EvaluationRun, baseline: EvaluationRun, | |
| policy: Optional[Dict] = None) -> Dict: | |
| policy = {**DEFAULT_PROMOTION_POLICY, **(policy or {})} | |
| cand_art, base_art = store.artifacts(candidate.run_id), store.artifacts(baseline.run_id) | |
| cand_cube, base_cube = cand_art["cube"], base_art["cube"] | |
| headline = [] | |
| for m in _HEADLINE_METRICS: | |
| c, b = candidate.headline.get(m), baseline.headline.get(m) | |
| if c is None or b is None: | |
| continue | |
| headline.append({"metric": m, "baseline": round(b, 4), "candidate": round(c, 4), | |
| "delta": round(c - b, 4)}) | |
| per_class = [] | |
| for cls in DIMENSIONS["class"]: | |
| c, b = candidate.per_class.get(cls), baseline.per_class.get(cls) | |
| if not c or not b: | |
| continue | |
| per_class.append({ | |
| "class": cls, "n": c.get("n"), | |
| "recall_baseline": b.get("recall"), "recall_candidate": c.get("recall"), | |
| "recall_delta": round((c.get("recall") or 0) - (b.get("recall") or 0), 4), | |
| "precision_baseline": b.get("precision"), "precision_candidate": c.get("precision"), | |
| "precision_delta": round((c.get("precision") or 0) - (b.get("precision") or 0), 4), | |
| }) | |
| dims = policy["cohort_dims"] | |
| cand_rows, _ = cube_mod.aggregate(cand_cube, None, dims, ["n", "recall", "precision"], 2000) | |
| base_rows, _ = cube_mod.aggregate(base_cube, None, dims, ["n", "recall", "precision"], 2000) | |
| base_map = {tuple(r[d] for d in dims): r for r in base_rows} | |
| cohorts = [] | |
| for r in cand_rows: | |
| key = tuple(r[d] for d in dims) | |
| b = base_map.get(key) | |
| if not b or r["n"] < policy["min_cohort_support"]: | |
| continue | |
| if r.get("recall") is None or b.get("recall") is None: | |
| continue | |
| cohorts.append({ | |
| "cohort": "/".join(key), **{d: r[d] for d in dims}, "n": r["n"], | |
| "recall_baseline": b["recall"], "recall_candidate": r["recall"], | |
| "recall_delta": round(r["recall"] - b["recall"], 4), | |
| }) | |
| cohorts.sort(key=lambda x: x["recall_delta"]) | |
| regressions = [c for c in cohorts if c["recall_delta"] < -policy["max_cohort_recall_drop"]] | |
| blockers: List[str] = [] | |
| hd = {h["metric"]: h["delta"] for h in headline} | |
| if hd.get("recall", 0) < -policy["max_recall_drop"]: | |
| blockers.append(f"headline recall dropped {hd['recall']:+.4f} " | |
| f"(policy allows -{policy['max_recall_drop']})") | |
| if hd.get("precision", 0) < -policy["max_precision_drop"]: | |
| blockers.append(f"headline precision dropped {hd['precision']:+.4f}") | |
| if hd.get("safety_recall", 0) < -policy["max_safety_recall_drop"]: | |
| blockers.append(f"safety recall dropped {hd['safety_recall']:+.4f} " | |
| f"(policy allows -{policy['max_safety_recall_drop']})") | |
| for c in regressions[:5]: | |
| blockers.append( | |
| f"REGRESSION: {c['cohort']} recall " | |
| f"{c['recall_baseline']*100:.1f}% -> {c['recall_candidate']*100:.1f}% " | |
| f"(Δ {c['recall_delta']*100:+.1f})") | |
| # integrate with the existing regression tracker so the Regression page shows it | |
| try: | |
| from sensorflow.evaluation import regression as legacy_regression | |
| from sensorflow.evaluation.records import get_store as get_legacy_store | |
| legacy_regression.compare_runs( | |
| get_legacy_store(), | |
| current_metrics={"precision": candidate.headline.get("precision"), | |
| "recall": candidate.headline.get("recall"), | |
| "safety_critical_recall": candidate.headline.get("safety_recall"), | |
| "mean_iou_3d": candidate.headline.get("mean_iou")}, | |
| model_version=candidate.model_version, | |
| dataset_version=candidate.population_id, | |
| run_id=candidate.run_id, | |
| baseline_metrics={"precision": baseline.headline.get("precision"), | |
| "recall": baseline.headline.get("recall"), | |
| "safety_critical_recall": baseline.headline.get("safety_recall"), | |
| "mean_iou_3d": baseline.headline.get("mean_iou")}, | |
| baseline_version=baseline.model_version, | |
| affected_classes=[c["class"] for c in per_class if c["recall_delta"] < -0.03], | |
| affected_scenarios=sorted({c.get("road_type", "") for c in regressions[:5] if c}), | |
| ) | |
| except Exception: | |
| pass | |
| return { | |
| "candidate": {"run_id": candidate.run_id, "model_version": candidate.model_version}, | |
| "baseline": {"run_id": baseline.run_id, "model_version": baseline.model_version}, | |
| "headline_deltas": headline, | |
| "per_class": per_class, | |
| "worst_cohorts": cohorts[:15], | |
| "regressions": regressions[:10], | |
| "policy": policy, | |
| "recommendation": "DO_NOT_PROMOTE" if blockers else "PROMOTE", | |
| "blockers": blockers, | |
| } | |
| # ------------------------------------------------------------------ distribution shift | |
| def distribution_shift(store: MegaStore, run: EvaluationRun, | |
| min_eval_count: int = 300, rel_threshold: float = 0.35) -> Dict: | |
| cache_path = os.path.join(run_dir(run.run_id), "shift.json") | |
| if os.path.exists(cache_path): | |
| with open(cache_path) as f: | |
| return json.load(f) | |
| meta = pop_mod.load_meta(run.population_id) | |
| cube_df = store.artifacts(run.run_id)["cube"] | |
| overall_rows, _ = cube_mod.aggregate(cube_df, None, None, ["recall"], 1) | |
| overall_recall = overall_rows[0].get("recall") | |
| shifts = [] | |
| for row in meta.get("train_mix", []): | |
| train, evals = row["train_share"], row["eval_share"] | |
| if train <= 0 or row["eval_count"] < min_eval_count: | |
| continue | |
| rel = (evals - train) / train | |
| if abs(rel) < rel_threshold: | |
| continue | |
| filt = {"class": [row["class"]], "weather": [row["weather"]], | |
| "lighting": [row["lighting"]]} | |
| crows, _ = cube_mod.aggregate(cube_df, filt, None, ["n", "recall", "precision"], 1) | |
| c = crows[0] if crows else {} | |
| shifts.append({ | |
| "cohort": f"{row['class']}/{row['weather']}/{row['lighting']}", | |
| "class": row["class"], "weather": row["weather"], "lighting": row["lighting"], | |
| "train_share": round(train, 5), "eval_share": round(evals, 5), | |
| "relative_change": round(rel, 4), | |
| "eval_count": row["eval_count"], | |
| "cohort_recall": c.get("recall"), | |
| "overall_recall": overall_recall, | |
| "recall_gap": (round(c["recall"] - overall_recall, 4) | |
| if c.get("recall") is not None and overall_recall is not None else None), | |
| }) | |
| shifts.sort(key=lambda s: abs(s["relative_change"]) * (1 + abs(s.get("recall_gap") or 0) * 5), | |
| reverse=True) | |
| # surface the worst shifts as alerts in the existing alerting system (once per run) | |
| try: | |
| from sensorflow.evaluation.records import Alert, get_store as get_legacy_store, new_id | |
| legacy = get_legacy_store() | |
| for s in shifts[:3]: | |
| if s.get("recall_gap") is not None and s["recall_gap"] < -0.03: | |
| legacy.put("alerts", Alert( | |
| alert_id=new_id("alert"), kind="distribution_shift", | |
| severity="critical" if s["recall_gap"] < -0.08 else "warning", | |
| message=(f"Distribution shift {s['cohort']}: train {s['train_share']*100:.1f}% " | |
| f"-> eval {s['eval_share']*100:.1f}% " | |
| f"(rel {s['relative_change']*100:+.0f}%), cohort recall " | |
| f"{(s['cohort_recall'] or 0)*100:.1f}% vs overall " | |
| f"{(s['overall_recall'] or 0)*100:.1f}%"), | |
| evidence_page="command", evidence_id=run.run_id)) | |
| legacy.save() | |
| except Exception: | |
| pass | |
| result = {"run_id": run.run_id, "shifts": shifts[:20], | |
| "method": "train mix vs eval mix over (class, weather, lighting); " | |
| "relative share change with cube recall impact", | |
| "thresholds": {"min_eval_count": min_eval_count, "rel_threshold": rel_threshold}} | |
| with open(cache_path, "w") as f: | |
| json.dump(result, f) | |
| return result | |
| # ------------------------------------------------------------------ the "why" layer | |
| _FACTOR_ORDER = ["occlusion", "low_illumination", "long_range", "sensor_disagreement", "other"] | |
| def why(store: MegaStore, run: EvaluationRun, filters: Optional[Dict] = None, | |
| metric: str = "recall") -> Dict: | |
| """Decompose the failure population of a filtered slice by primary factor.""" | |
| art = store.artifacts(run.run_id) | |
| errors = art.get("errors") | |
| cube_df = art["cube"] | |
| if errors is None or not len(errors): | |
| return {"factors": [], "top_cohorts": [], "failure_count": 0} | |
| mask = np.ones(len(errors), dtype=bool) | |
| fail_types = [ERROR_TYPES.index(t) for t in ("FN", "FP", "LOCALIZATION")] | |
| if metric == "recall": | |
| fail_types = [ERROR_TYPES.index("FN"), ERROR_TYPES.index("LOCALIZATION")] | |
| elif metric == "precision": | |
| fail_types = [ERROR_TYPES.index("FP"), ERROR_TYPES.index("LOCALIZATION")] | |
| mask &= errors["error_type"].isin(fail_types).to_numpy() | |
| for dim, values in (filters or {}).items(): | |
| if dim not in DIM_NAMES: | |
| continue | |
| vocab = DIMENSIONS[dim] | |
| codes = [vocab.index(v) for v in (values if isinstance(values, list) else [values]) | |
| if v in vocab] | |
| mask &= errors[dim].isin(codes).to_numpy() | |
| sub = errors[mask] | |
| total = len(sub) | |
| if total == 0: | |
| return {"factors": [], "top_cohorts": [], "failure_count": 0} | |
| occl = sub["occlusion"].to_numpy() >= 1 | |
| night = sub["lighting"].to_numpy() == 1 | |
| far = sub["distance_band"].to_numpy() >= 2 | |
| sensor = sub["sensor_disagree"].to_numpy().astype(bool) | |
| factor = np.full(total, 4, dtype=np.int8) # other | |
| factor[sensor] = 3 | |
| factor[far] = 2 | |
| factor[night] = 1 | |
| factor[occl] = 0 # priority: occlusion > low light > range > sensor > other | |
| counts = np.bincount(factor, minlength=5) | |
| factors = [{"factor": _FACTOR_ORDER[i], "count": int(counts[i]), | |
| "share": round(float(counts[i]) / total, 4)} | |
| for i in range(5) if counts[i] > 0] | |
| factors.sort(key=lambda x: -x["count"]) | |
| rows, _ = cube_mod.aggregate(cube_df, filters, ["class", "lighting", "weather"], | |
| ["n", metric, "fn", "fp"], 2000) | |
| rows = [r for r in rows if r["n"] >= 150 and r.get(metric) is not None] | |
| rows.sort(key=lambda r: r[metric]) | |
| top = [{"cohort": f"{r['class']}/{r['lighting']}/{r['weather']}", | |
| "n": r["n"], metric: r[metric], "fn": r.get("fn"), "fp": r.get("fp")} | |
| for r in rows[:8]] | |
| return {"metric": metric, "filters": filters or {}, "failure_count": int(total), | |
| "factors": factors, "top_cohorts": top, | |
| "method": "primary-factor attribution over the error index " | |
| "(priority: occlusion > low illumination > long range > " | |
| "sensor disagreement > other)"} | |
| # ------------------------------------------------------------------ hybrid similarity | |
| def similar_containers(store: MegaStore, run: EvaluationRun, container_id: int, | |
| filters: Optional[Dict] = None, k: int = 12) -> Dict: | |
| art = store.artifacts(run.run_id) | |
| ids, emb = art["emb_ids"], art["emb"] | |
| containers = art["containers"].set_index("container_id") | |
| pos = np.where(ids == container_id)[0] | |
| if pos.size == 0: | |
| raise KeyError(f"Unknown container {container_id}") | |
| q = emb[pos[0]] | |
| sims = emb @ q | |
| mask = np.ones(ids.shape[0], dtype=bool) | |
| for dim, values in (filters or {}).items(): | |
| if dim not in pop_mod.CONTAINER_DIMS: | |
| continue | |
| vocab = DIMENSIONS[dim] | |
| codes = [vocab.index(v) for v in (values if isinstance(values, list) else [values]) | |
| if v in vocab] | |
| col = containers.loc[ids, dim].to_numpy() | |
| mask &= np.isin(col, codes) | |
| mask[pos[0]] = False | |
| sims = np.where(mask, sims, -np.inf) | |
| kk = min(k, int(mask.sum())) | |
| if kk <= 0: | |
| return {"query_container": container_id, "results": []} | |
| top = np.argpartition(-sims, kk - 1)[:kk] | |
| top = top[np.argsort(-sims[top])] | |
| results = [] | |
| for i in top.tolist(): | |
| cid = int(ids[i]) | |
| row = containers.loc[cid] | |
| results.append({ | |
| "container_id": cid, | |
| "similarity": round(float(sims[i]), 4), | |
| **{dim: DIMENSIONS[dim][int(row[dim])] for dim in pop_mod.CONTAINER_DIMS}, | |
| "n_objects": int(row["n_objects"]), | |
| "fn": int(row["fn"]), "fp": int(row["fp"]), | |
| "anomalies": int(row["anomalies"]), | |
| "risk_score": round(float(row["risk_score"]), 4), | |
| }) | |
| return {"query_container": container_id, | |
| "retrieval": "cosine over 32-d structural embeddings + structured filters", | |
| "results": results} | |