Feat: Add held-out set evaluation for final candidate selection#308
Open
UgurKap wants to merge 19 commits into
Open
Feat: Add held-out set evaluation for final candidate selection#308UgurKap wants to merge 19 commits into
UgurKap wants to merge 19 commits into
Conversation
Introduces a 4th data split (held-out) alongside the existing feedback/pareto/test splits. The valset leader is evaluated on the held-out set whenever it changes; the final best candidate is selected by held-out score rather than valset score, reducing overfitting to the pareto/valset examples. Key changes: - GEPAState: new prog_candidate_held_out_subscores and num_held_out_evals fields (separate budget counter so held-out evals don't consume max_metric_calls) - HeldOutEvaluation dataclass mirroring ValsetEvaluation - HeldOutSetEvaluationPolicy: reads held-out scores from state, exposes get_valset_leader() for the engine to determine who to evaluate - GEPAEngine: _evaluate_on_held_out_set(), held_out_evaluator closure, seed held-out eval at initialization (skipped on resume), held-out check in _run_full_eval_and_add() before get_best_program() - on_held_out_evaluated callback event with symmetric seed firing at iteration 0 - GEPAResult: held_out_scores, num_held_out_evals, valset_best_idx; best_idx uses held-out when available - Logging/metrics cleaned up: removed stale variable refs, consistent key names, best_program_held_out_score in experiment tracker - api.py: held_out parameter, auto-selects HeldOutSetEvaluationPolicy, validates policy/held_out combination up front - Cache bypass for held-out evaluation to avoid id-space collisions with valset
Changed Files
|
Collaborator
|
Thanks for the PR. However, this does not serve a good purpose for the following reasons:
|
Author
Hello @Shangyint , thank you for the comment. Sorry, about not putting in more context. I had previously talked with @LakshyAAAgrawal , and showed why this approach could be good here: https://uguralikaplan.com/blog/gepa-is-better-than-you-think/ (Essentially showing pareto set cannot measure generalization capability at least for AIME-2026). I didn't consider if this would be possible with the callbacks though. |
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.
Summary
This PR adds held-out-set support to
gepa.optimize()so final candidate selection can happen on a split that did not drive candidate survival during optimization.The motivation is that
valsetalready participates in the optimization loop through Pareto tracking and candidate selection. Re-using that same split for final selection can bias selection toward candidates that survived search pressure on the Pareto/validation set, even if they generalize worse on unseen data.Changes
held_outsupport togepa.optimize()HeldOutSetEvaluationPolicyGEPAResultGEPAResultJSON round-trip deserialization for integer-keyed maps such asheld_out_scores(i.e. reading the candidate_ids back from the disk turns them into strings)Key Decisions / Assumptions
gepa.optimize();optimize_anythingwas left unchangedheld_outis provided andval_evaluation_policy is None, GEPA auto-selectsHeldOutSetEvaluationPolicy"full_eval"still meansFullEvaluationPolicy()"full_eval"orFullEvaluationPolicy()is used together withheld_out, GEPA raises an error"heldout_eval"is available as an explicit held-out-aware option"heldout_eval"without aheld_outsplit is allowed but emits a warning and falls back to valset-only semanticsbest_score_on_valsetremains the true best valset scorepolicy_selected_valset_scoreis logged only whenheld_outis configuredbest_score_on_held_outis the held-out score of the policy-selected candidateValidation
uv run pytest tests/test_held_out_eval_policy.pyuv run pyright src/full_evalandheldout_evalbehavior locally on a short AIME slice