feat(strategies): BatchLexicase and UnprunedPareto candidate selectors - #432
Draft
Benzhang2004 wants to merge 3 commits into
Draft
feat(strategies): BatchLexicase and UnprunedPareto candidate selectors#432Benzhang2004 wants to merge 3 commits into
Benzhang2004 wants to merge 3 commits into
Conversation
…ctors
On binary metrics with large candidate pools, ParetoCandidateSelector's
dominance pruning can assign zero selection probability to the
highest-aggregate candidates: sole-solver specialists (often minted by
single-rollout noise) are unremovable, while broadly strong candidates
hold no unique per-instance slot and get pruned as redundant. Replaying
the sampler on nine 15k-budget LiveBench-Math runs showed the best-val
candidate receiving zero mass in five of them.
- BatchLexicaseCandidateSelector: batch lexicase selection (Helmuth,
Spector & Matheson 2015; Aenugu & Spector 2019) over per-instance val
scores. No candidate is ever excluded outright; batch_size tunes
selection pressure between plain lexicase (1) and argmax aggregate
(len(valset)).
- UnprunedParetoCandidateSelector: frontier-frequency sampling without
the dominance-pruning step, as an ablation control.
Both are registered as candidate_selection_strategy strings
("batch_lexicase", "unpruned_pareto") in api.py and gepa_launcher.py.
Includes a regression test where the best-aggregate candidate is
excluded by dominance pruning but keeps mass under batch lexicase.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018q6wnLomxs6LdMvhY8FL1A
Changed Files
|
Always selects the most recently accepted candidate, so the lineage grows as a single chain (depth = number of accepts). With GEPA's minibatch acceptance gate this is a greedy incumbent loop; registered as the "latest" candidate_selection_strategy. Useful for isolating sequential-depth effects from pool selection in ablations. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018q6wnLomxs6LdMvhY8FL1A
Accepts every proposal unconditionally (registered as "always"), removing selection pressure at the acceptance step. Ablation control for separating the contribution of the acceptance gate from the reflective proposals themselves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018q6wnLomxs6LdMvhY8FL1A
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
On binary metrics with large candidate pools,
ParetoCandidateSelector's dominance-pruning step (remove_dominated_programs) can assign zero selection probability to the highest-aggregate candidates:is_dominatedcan never return true for it. With single-rollout evaluation, sole-solver slots are frequently minted by noise.Replaying the deployed sampler on nine 15,000-metric-call LiveBench-Math optimization runs (~126-candidate pools, binary scoring): the best-val candidate received exactly zero selection mass in five of nine runs, and lineage analysis showed the resulting search trees grow as stars around a static set of early sole-solver hubs (69-75% of accepted candidates never breed) with max depth at or below what uniform random parent attachment would produce. On a continuous-metric task (deterministic simulator scores, mostly singleton fronts), the same code behaves well: champions keep 2-3x uniform mass. The pathology is specific to large tie sets, consistent with dominance resistance in many-objective optimization (Purshouse & Fleming 2007).
This does not contradict the GEPA paper's Table 3 ablation (Pareto sampling >> SelectBestCandidate): those experiments ran 1.8k-7k rollouts, where pools are small and unique slots are meaningful. The failure emerges at larger pool sizes.
Changes
BatchLexicaseCandidateSelector(batch_size=8, rng)— batch lexicase selection (Helmuth, Spector & Matheson 2015; Aenugu & Spector 2019, whose batched variant targets noisy binary case scores). Validation instances are shuffled and grouped into batches; the pool is filtered batch-by-batch keeping max-batch-score survivors until one remains. Every candidate wins under some ordering, so no candidate is ever excluded outright;batch_sizetunes selection pressure continuously between plain lexicase (1) and argmax-by-aggregate (whole valset). In counterfactual replays on the nine saved runs, batch lexicase at b=8-16 raised the expected val score of the selected parent in every run while keeping 27-66 effective parents (diversity preserved).UnprunedParetoCandidateSelector(rng)— frontier-frequency sampling with the dominance-pruning step skipped. Ablation control to isolate the pruning step's contribution.candidate_selection_strategystrings ("batch_lexicase","unpruned_pareto") inapi.pyandgepa_launcher.py.ParetoCandidateSelectoracross 200 draws and majority mass under batch lexicase.Status