flash-crash-watchdog / scripts /audit_dataset.py
Dev2506's picture
Publish Flash Crash Watchdog — source, configs, docs, ml package, trained models, demo data
d699ea7
Raw History Blame Contribute Delete
3.29 kB
#!/usr/bin/env python3
"""RSR-16: checkpoint/dataset provenance audit.
Verifies that a trained checkpoint's recorded provenance is honest and that its
training inputs do not leak the held-out validation days.
New-style checkpoints (trained by train_tcn_windows.py after RSR-09) carry a
``provenance`` dict: {trainer, seed, torch_version, input_files, label_mode,
normalize}. This script:
1. reads the provenance (if present),
2. checks ``label_mode`` is wall-clock (RSR-02) and ``normalize`` is
rolling-z (BUG-03),
3. cross-checks the training window files against configs/operating.yml's
held-out validation days — any overlap is a train/test leak.
Legacy checkpoints (e.g. the current stage3_tcn_prod.pt, trained before
stamping) have no provenance: they are reported as unstamped (exit 0 with a
warning — the day-level split is still enforced by scripts/audit_dataset_split.py).
Usage:
PYTHONPATH=ml python scripts/audit_dataset.py \
--model models/stage3_tcn_prod.pt --config configs/operating.yml
"""
import argparse
import sys
from pathlib import Path
from typing import Any
import torch
import yaml
from flash_crash_watchdog.models.stage3_tcn import TCNConfig
def main() -> int:
ap = argparse.ArgumentParser(description="RSR-16 checkpoint provenance audit")
ap.add_argument("--model", required=True, help="checkpoint .pt")
ap.add_argument("--config", default="configs/operating.yml")
args = ap.parse_args()
torch.serialization.add_safe_globals([TCNConfig]) # MLOPS-06
state = torch.load(args.model, map_location="cpu", weights_only=True)
prov = state.get("provenance") if isinstance(state, dict) else None
with open(args.config, encoding="utf-8") as f:
op = yaml.safe_load(f)
held = {(d["symbol"], d["date"]) for d in op["validation"]["days"]}
problems = []
model = Path(args.model).name
if not isinstance(prov, dict):
print(f"[audit] {model}: NO provenance stamp (legacy checkpoint). "
f"Recorded sources cannot be verified; "
f"day-level split enforced by audit_dataset_split.py instead.")
return 0
print(f"[audit] {model}: provenance present")
for k in ("trainer", "seed", "torch_version", "label_mode", "normalize"):
if k in prov:
print(f" {k}: {prov[k]}")
files = prov.get("input_files", [])
print(f" input_files: {files}")
if prov.get("label_mode") != "wall-clock":
problems.append("label_mode is not wall-clock (RSR-02 violated)")
if prov.get("normalize") != "rolling-z-500":
problems.append("normalize is not rolling-z-500 (BUG-03 violated)")
# Infer which (symbol, date) each training window covers from the filename.
for f in files:
f = Path(f).name
for sym, date in held:
# npz names like LUNAUSDT_2022-05-10_slice_norm500.npz
if sym in f and date in f:
problems.append(f"LEAK: training input '{f}' matches held-out ({sym}, {date})")
if problems:
for p in problems:
print(f" ERROR: {p}", file=sys.stderr)
return 1
print("[audit] OK: provenance consistent; no training/held-out overlap.")
return 0
if __name__ == "__main__":
raise SystemExit(main())