Chris Leo commited on
Commit
edd9a4c
·
verified ·
1 Parent(s): 6467064

scorevision: push artifact

Browse files
Files changed (1) hide show
  1. training/milestones_watch.py +104 -0
training/milestones_watch.py ADDED
@@ -0,0 +1,104 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Poll Ultralytics results.csv and append one summary line per N-epoch milestone."""
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import csv
7
+ import time
8
+ from datetime import datetime, timezone
9
+ from pathlib import Path
10
+
11
+
12
+ def fmt_row(row: dict) -> str:
13
+ ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
14
+ ep = int(float(row["epoch"]))
15
+ return (
16
+ f"{ts} | epoch={ep} | "
17
+ f"mAP50={float(row['metrics/mAP50(B)']):.4f} mAP={float(row['metrics/mAP50-95(B)']):.4f} | "
18
+ f"P={float(row['metrics/precision(B)']):.4f} R={float(row['metrics/recall(B)']):.4f} | "
19
+ f"train_box={float(row['train/box_loss']):.4f} train_cls={float(row['train/cls_loss']):.4f}"
20
+ )
21
+
22
+
23
+ def recover_done(out_path: Path) -> set[int]:
24
+ done: set[int] = set()
25
+ if not out_path.exists():
26
+ return set()
27
+ for line in out_path.read_text().splitlines():
28
+ if "| epoch=" not in line:
29
+ continue
30
+ try:
31
+ part = line.split("| epoch=")[1].split()[0]
32
+ done.add(int(part))
33
+ except (IndexError, ValueError):
34
+ continue
35
+ return done
36
+
37
+
38
+ def main() -> int:
39
+ ap = argparse.ArgumentParser()
40
+ ap.add_argument("--csv", required=True, help="path to results.csv (created by Ultralytics)")
41
+ ap.add_argument("--every", type=int, default=10)
42
+ ap.add_argument("--max-epoch", type=int, default=200)
43
+ ap.add_argument("--out", default=None, help="append logs here (default: <run-dir>/milestones_every_N.log)")
44
+ ap.add_argument("--poll", type=float, default=15.0, help="seconds between reads")
45
+ ap.add_argument("--idle-exit-sec", type=float, default=180.0,
46
+ help="exit if results.csv is untouched this long and milestones caught up")
47
+ args = ap.parse_args()
48
+
49
+ csv_path = Path(args.csv)
50
+ run_dir = csv_path.parent
51
+ out_path = Path(args.out) if args.out else run_dir / f"milestones_every_{args.every}.log"
52
+ out_path.parent.mkdir(parents=True, exist_ok=True)
53
+ if not out_path.exists():
54
+ ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
55
+ out_path.write_text(
56
+ f"{ts} | milestones_watch | status=started next_report=epoch_{args.every} csv={csv_path}\n"
57
+ )
58
+ print(f"[milestones_watch] created {out_path} (metric lines appear every {args.every} epochs)", flush=True)
59
+ done = recover_done(out_path)
60
+
61
+ while True:
62
+ if not csv_path.exists():
63
+ time.sleep(args.poll)
64
+ continue
65
+ try:
66
+ with csv_path.open(newline="") as f:
67
+ rows = list(csv.DictReader(f))
68
+ except Exception:
69
+ time.sleep(args.poll)
70
+ continue
71
+ if not rows:
72
+ time.sleep(args.poll)
73
+ continue
74
+
75
+ last_ep = int(float(rows[-1]["epoch"]))
76
+ for m in range(args.every, min(last_ep, args.max_epoch) + 1, args.every):
77
+ if m in done:
78
+ continue
79
+ match = [r for r in rows if int(float(r["epoch"])) == m]
80
+ if not match:
81
+ continue
82
+ line = fmt_row(match[0])
83
+ out_path.parent.mkdir(parents=True, exist_ok=True)
84
+ with out_path.open("a") as o:
85
+ o.write(line + "\n")
86
+ print(line, flush=True)
87
+ done.add(m)
88
+
89
+ mtime = csv_path.stat().st_mtime
90
+ idle = time.time() - mtime
91
+ max_reportable = (last_ep // args.every) * args.every
92
+ need = list(range(args.every, max_reportable + 1, args.every))
93
+ caught_up = all(m in done for m in need)
94
+ if idle >= args.idle_exit_sec and caught_up and last_ep >= 1:
95
+ print(f"milestones_watch: idle {idle:.0f}s and milestones caught up → exit", flush=True)
96
+ break
97
+
98
+ time.sleep(args.poll)
99
+
100
+ return 0
101
+
102
+
103
+ if __name__ == "__main__":
104
+ raise SystemExit(main())