offtargeteffect's picture
Add liability/QC, cluster & tree, and experiment tracking
bdd3f19 verified
Raw
History Blame Contribute Delete
4.01 kB
"""
Model run / experiment tracking.
A lightweight MLOps layer: every time a scoring model is run against a worklist
we record a ``ModelRun`` (model name + version, timestamp, score statistics, and
per-sequence scores). ``RunHistory`` stores them and can compare two runs — e.g.
two versions of the same model — to show how scores shifted.
Pure-Python (uses only the stdlib ``statistics`` module).
"""
from __future__ import annotations
import math
import statistics
import uuid
from dataclasses import dataclass, field
from typing import Dict, List, Optional
@dataclass
class ModelRun:
"""A single scoring run over a set of sequences."""
model_name: str
model_version: str
model_source: str
worklist_name: str
n_sequences: int
score_min: float
score_max: float
score_mean: float
score_std: float
timestamp: str # ISO-8601
run_id: str = field(default_factory=lambda: str(uuid.uuid4())[:8])
notes: str = ""
scores: Dict[str, float] = field(default_factory=dict) # seq_id -> score
@property
def label(self) -> str:
return f"{self.model_name} v{self.model_version} · {self.timestamp}"
def summarize_run(
model_name: str,
model_version: str,
model_source: str,
worklist_name: str,
scores: Dict[str, float],
timestamp: str,
notes: str = "",
) -> ModelRun:
"""Build a ModelRun from a {seq_id: score} mapping (ignoring NaNs in stats)."""
valid = [v for v in scores.values() if v is not None and not math.isnan(v)]
n = len(valid)
smin = min(valid) if valid else float("nan")
smax = max(valid) if valid else float("nan")
smean = statistics.fmean(valid) if valid else float("nan")
sstd = statistics.pstdev(valid) if len(valid) > 1 else 0.0
return ModelRun(
model_name=model_name, model_version=model_version,
model_source=model_source, worklist_name=worklist_name,
n_sequences=n, score_min=smin, score_max=smax,
score_mean=smean, score_std=sstd, timestamp=timestamp,
notes=notes, scores=dict(scores),
)
@dataclass
class RunComparison:
"""Per-sequence delta between two runs over their shared sequences."""
run_a: ModelRun
run_b: ModelRun
shared_ids: List[str]
deltas: Dict[str, float] # seq_id -> (b - a)
mean_delta: float
n_improved: int # b > a
n_worsened: int # b < a
n_unchanged: int
class RunHistory:
"""Append-only store of ModelRun records."""
def __init__(self) -> None:
self.runs: List[ModelRun] = []
def add(self, run: ModelRun) -> None:
self.runs.append(run)
def for_model(self, model_name: str) -> List[ModelRun]:
return [r for r in self.runs if r.model_name == model_name]
def model_names(self) -> List[str]:
# preserve first-seen order
seen: List[str] = []
for r in self.runs:
if r.model_name not in seen:
seen.append(r.model_name)
return seen
@staticmethod
def compare(run_a: ModelRun, run_b: ModelRun) -> RunComparison:
shared = [sid for sid in run_a.scores if sid in run_b.scores]
deltas: Dict[str, float] = {}
improved = worsened = unchanged = 0
for sid in shared:
a, b = run_a.scores[sid], run_b.scores[sid]
if a is None or b is None or math.isnan(a) or math.isnan(b):
continue
d = b - a
deltas[sid] = d
if d > 1e-9:
improved += 1
elif d < -1e-9:
worsened += 1
else:
unchanged += 1
mean_delta = statistics.fmean(deltas.values()) if deltas else 0.0
return RunComparison(
run_a=run_a, run_b=run_b, shared_ids=list(deltas.keys()),
deltas=deltas, mean_delta=mean_delta,
n_improved=improved, n_worsened=worsened, n_unchanged=unchanged,
)