Spaces:
Running
Running
File size: 6,588 Bytes
8d1e644 598a072 8d1e644 6c0aef4 8d1e644 598a072 8d1e644 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | """Validation harness — does the engine actually predict? Receipts, not claims.
The glass-box thesis lives or dies on this: every score we surface is framed
as a prediction to verify, so we owe the user proof the predictions track
reality. This harness correlates the engine's zero-shot ESM-2 ΔLL ranking
against measured deep-mutational-scanning (DMS) fitness from published studies
(e.g. ProteinGym) and reports the two numbers a protein engineer actually
cares about:
* Spearman ρ — does the ranking order match measured fitness order?
* top-decile precision — of the variants we rank in the top 10%, what
fraction are genuinely high-fitness (top quartile measured)? i.e. "if I
only make the picks you put on top, how often are they real?"
Pure numpy, predictions injected — so it's unit-testable without ESM. The
reproducible CLI (scripts/run_benchmarks.py) runs it against real DMS with the
live model where the weights exist; the app only ever serves results a real
run produced. No number is fabricated here.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from typing import List, Optional, Sequence
import numpy as np
_LABEL_RE = re.compile(r"^([A-Za-z])(\d+)([A-Za-z*])$")
def predict_additive(scores_df, labels: Sequence[str]) -> List[float]:
"""Sum-of-single-site ESM-2 ΔLL for each (possibly multi-site) DMS label
('A1C', 'A1C:D5E', or 'A1C,D5E') — the SAME additive scoring the design
engine itself uses (dee.optimizer.search), so this validates exactly what
we ship rather than a different metric. ``scores_df`` is the long-format
output of ``ESM2Scorer.score_all_substitutions`` (columns: position
0-indexed, mut_aa, delta_ll).
Single source of truth for both the reproducible CLI
(scripts/run_benchmarks.py) and the admin in-Space endpoint — never
duplicated, so they can't silently drift apart.
A label with any unparseable or out-of-table site becomes NaN (never
silently coerced to 0) — spearman()/top_decile_precision() mask NaNs out
via ``np.isfinite``, so a bad label reduces the effective N rather than
corrupting the correlation."""
lut = {(int(r.position), str(r.mut_aa)): float(r.delta_ll)
for r in scores_df.itertuples(index=False)}
out: List[float] = []
for lab in labels:
total, ok = 0.0, True
for tok in re.split(r"[:,]", str(lab).strip()):
m = _LABEL_RE.match(tok.strip())
if not m:
ok = False
break
pos, mut = int(m.group(2)) - 1, m.group(3).upper()
key = (pos, mut)
if key not in lut:
ok = False
break
total += lut[key]
out.append(total if ok else float("nan"))
return out
def _average_ranks(x: np.ndarray) -> np.ndarray:
"""Ranks with ties resolved to the average rank (proper Spearman ties)."""
order = np.argsort(x, kind="mergesort")
ranks = np.empty(len(x), dtype=np.float64)
sx = x[order]
i = 0
n = len(x)
while i < n:
j = i
while j + 1 < n and sx[j + 1] == sx[i]:
j += 1
avg = (i + j) / 2.0 + 1.0 # 1-indexed average rank over the tie block
ranks[order[i:j + 1]] = avg
i = j + 1
return ranks
def spearman(a: Sequence[float], b: Sequence[float]) -> Optional[float]:
"""Spearman rank correlation. None if <3 points or no variance."""
a = np.asarray(a, dtype=np.float64)
b = np.asarray(b, dtype=np.float64)
mask = np.isfinite(a) & np.isfinite(b)
a, b = a[mask], b[mask]
if len(a) < 3:
return None
ra, rb = _average_ranks(a), _average_ranks(b)
if ra.std() < 1e-9 or rb.std() < 1e-9:
return None
return float(np.corrcoef(ra, rb)[0, 1])
def top_decile_precision(
predicted: Sequence[float], measured: Sequence[float],
*, pred_frac: float = 0.10, true_frac: float = 0.25,
) -> Optional[float]:
"""Of the top ``pred_frac`` by prediction, the fraction that land in the
top ``true_frac`` by measured fitness. The 'are your top picks real?' number.
None if too few points to form a meaningful top set."""
p = np.asarray(predicted, dtype=np.float64)
m = np.asarray(measured, dtype=np.float64)
mask = np.isfinite(p) & np.isfinite(m)
p, m = p[mask], m[mask]
n = len(p)
k = int(round(n * pred_frac))
if n < 10 or k < 1:
return None
top_pred_idx = np.argsort(-p)[:k]
true_cut = np.quantile(m, 1.0 - true_frac)
hits = int(np.sum(m[top_pred_idx] >= true_cut))
return float(hits / k)
@dataclass
class DatasetResult:
"""One DMS assay's validation result."""
name: str
protein: str # e.g. UniProt/DMS id
n: int # variants scored
spearman: Optional[float]
top_decile_precision: Optional[float]
source: str = "" # citation / DOI / dataset id (provenance)
def as_dict(self) -> dict:
return {
"name": self.name, "protein": self.protein, "n": self.n,
"spearman": None if self.spearman is None else round(self.spearman, 4),
"top_decile_precision": None if self.top_decile_precision is None
else round(self.top_decile_precision, 4),
"source": self.source,
}
def evaluate_dataset(
name: str, protein: str,
predicted: Sequence[float], measured: Sequence[float],
*, source: str = "",
) -> DatasetResult:
"""Score one aligned (predicted, measured) DMS assay."""
p = np.asarray(predicted, dtype=np.float64)
m = np.asarray(measured, dtype=np.float64)
mask = np.isfinite(p) & np.isfinite(m)
return DatasetResult(
name=name, protein=protein, n=int(mask.sum()),
spearman=spearman(p, m),
top_decile_precision=top_decile_precision(p, m),
source=source,
)
def summarize(results: Sequence[DatasetResult]) -> dict:
"""Headline across datasets: median Spearman, median top-decile precision,
dataset + variant counts. Medians (robust to a couple of hard assays)."""
rhos = [r.spearman for r in results if r.spearman is not None]
precs = [r.top_decile_precision for r in results if r.top_decile_precision is not None]
return {
"n_datasets": len(results),
"n_variants": int(sum(r.n for r in results)),
"median_spearman": None if not rhos else round(float(np.median(rhos)), 4),
"median_top_decile_precision": None if not precs
else round(float(np.median(precs)), 4),
}
|