Skip to content

feat(bench): strict triage benchmark with composition-matched decoys - #128

Merged
cschanhniem merged 1 commit into
mainfrom
agent/oamp-loop-20260702-6
Jul 2, 2026
Merged

cschanhniem merged 1 commit into
mainfrom
agent/oamp-loop-20260702-6

Conversation

@cschanhniem

Copy link
Copy Markdown
Collaborator

Summary

The standard triage benchmark (v0.5.12) uses random background peptides as decoys, which are trivially distinguishable from AMPs due to protein-like composition. This inflates selective_vs_decoy and hemolytic_vs_decoy AUROCs, making scorers appear to triage well when they are really only solving the easy AMP vs random problem.

This PR adds a strict triage benchmark that replaces random decoys with composition-matched scrambled versions of selective AMPs (same amino acids, permuted order). This destroys amphipathic helical phase, hydrophobic moment, and charge distribution while preserving all composition-based features.

Key Honest Finding

No scorer triages correctly with composition-matched decoys. The standard triage success of selectivity_proxy and expert_composite was an artifact of trivially distinguishable decoys.

Scorer Std sel_vs_dec Strict sel_vs_dec Std correct Strict correct
selectivity_proxy 0.782 0.500 YES NO
expert_composite 0.757 0.510 YES NO
ensemble 0.848 0.572 NO NO
triage_score 0.863 0.674 NO NO
  • selectivity_proxy collapses to exactly 0.5000 on selective_vs_decoy (purely composition-driven)
  • Ensemble drops from 0.848 to 0.572 (most signal was composition, not order)
  • selective_vs_hemolytic is identical between standard and strict (expected, both classes are real AMPs)
  • Ensemble admits 7 scrambled decoys into top-20 (vs 0 with random decoys)

Implication

The pipeline triage signal is almost entirely composition-driven. The real bottleneck (selective-vs-hemolytic discrimination) requires structural or contextual features beyond what current 1D physicochemical scorers can capture. Any future virtual assay layer must demonstrate order-dependent triage signal on this strict benchmark before claiming to improve candidate selection.

Changes

  • benchmark/triage.py: run_strict_triage_benchmark() + scramble helpers
  • benchmark/metrics_snapshot.py: strict_triage section in snapshot
  • tests/test_triage_benchmark.py: 12 new tests (structure + findings)
  • outputs/metrics_snapshot.json: regenerated with full 2000-iteration bootstrap
  • docs/METRICS_CURRENT.md: strict triage results table, interpretation, changelog
  • docs/ROADMAP.md: v0.5.14 entry + v1.1 triage item updated

Verification

  • uv run pytest -q: 1516 passed, 7 skipped
  • git diff --check: clean
  • tests/test_docs_consistent.py: all 9 doc-consistency guards pass
  • make metrics-snapshot: regenerated with strict_triage section

Tested: uv run pytest -q (1516 passed, 7 skipped), git diff --check (clean)
Confidence: high - composition-matched decoy AUROCs are deterministic (seed=42)
Co-authored-by: OmX omx@oh-my-codex.dev

The standard triage benchmark uses random background peptides as decoys,
which are trivially distinguishable from AMPs due to protein-like composition.
This inflates selective_vs_decoy and hemolytic_vs_decoy AUROCs, making scorers
appear to triage well when they are really only solving the easy
AMP vs random problem.

The strict triage benchmark replaces random decoys with composition-matched
scrambled versions of selective AMPs (same amino acids, permuted order). This
destroys amphipathic helical phase, hydrophobic moment, and charge distribution
while preserving all composition-based features.

Key honest finding: NO scorer triages correctly with composition-matched decoys.
The standard triage success of selectivity_proxy (0.782 sel_vs_dec) and
expert_composite (0.757) was inflated by trivially distinguishable decoys.

- selectivity_proxy collapses to exactly 0.5000 on selective_vs_decoy
  (purely composition-driven: charge and GRAVY are identical between
  a sequence and its scrambled version)
- ensemble drops from 0.848 to 0.572
- selective_vs_hemolytic is identical between standard and strict (expected:
  both classes are real AMPs, only the decoy class changes)
- ensemble admits 7 scrambled decoys into top-20 (vs 0 with random decoys)

Changes:
- benchmark/triage.py: run_strict_triage_benchmark() + scramble helpers
- benchmark/metrics_snapshot.py: strict_triage section in snapshot
- tests/test_triage_benchmark.py: 12 new tests (structure + findings)
- outputs/metrics_snapshot.json: regenerated with full 2000-iteration bootstrap
- docs/METRICS_CURRENT.md: strict triage results table and interpretation
- docs/ROADMAP.md: v0.5.14 entry + v1.1 triage item updated

1516 passed, 7 skipped.

Tested: uv run pytest -q (1516 passed, 7 skipped), git diff --check (clean)
Confidence: high — composition-matched decoy AUROCs are deterministic (seed=42)
Co-authored-by: OmX <omx@oh-my-codex.dev>
@cschanhniem
cschanhniem force-pushed the agent/oamp-loop-20260702-6 branch from 6521ab5 to 1025b22 Compare July 2, 2026 17:16
@cschanhniem
cschanhniem merged commit ca0ced8 into main Jul 2, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant