feat: add validation_frequency option to fine-tuning#1098
Conversation
Add validation_frequency parameter to FinetunedTabPFNBase/Classifier/ Regressor. Defaults to 1 (validate every epoch, current behavior). N>1 validates every N epochs. None skips validation entirely for the run and disables early stopping (warns if early_stopping=True was also requested). Closes PriorLabs#811
There was a problem hiding this comment.
Code Review
This pull request introduces a validation_frequency parameter to FinetunedTabPFNClassifier and FinetunedTabPFNRegressor, allowing validation and early stopping to run every N epochs or be disabled entirely when set to None. The feedback highlights two important issues: first, validation_frequency needs input validation to ensure it is a positive integer or None to avoid potential runtime errors like ZeroDivisionError; second, setting the primary metric to NaN on skipped epochs inadvertently prevents interval checkpoints from being saved, which should be corrected to allow checkpointing independently of validation runs.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Address review comments on PriorLabs#1098: - Raise ValueError for validation_frequency <= 0 or non-int (previously a bad value like 0 would cause a ZeroDivisionError in the epoch loop). - Decouple interval checkpoint saving from the NaN validation-skip sentinel: only the "is this the best model" comparison needs a real metric, so interval checkpoints now save on schedule even during epochs where validation was skipped.
Issue
Closes #811
Motivation and Context
Fine-tuning currently validates every epoch unconditionally. For long runs
or large validation sets this adds overhead, and there's no way to disable
validation (and the early stopping that depends on it) when you don't need it.
This adds a
validation_frequency: int | None = 1parameter toFinetunedTabPFNBase(and threads it throughFinetunedTabPFNClassifier/FinetunedTabPFNRegressor):validation_frequency=1(default): validates every epoch, identical tocurrent behavior.
validation_frequency=N: validates (and checks early stopping) only everyN epochs.
validation_frequency=None: skips validation for the entire run, whichalso disables early stopping. If
early_stopping=Trueis passed alongsidevalidation_frequency=None, aUserWarningis raised since the flag wouldotherwise silently have no effect.
Skipped epochs report
NaNas the validation metric, reusing the existingnot np.isnan(...)guards already in the checkpoint-saving andearly-stopping code paths. Early stopping's restore-to-best-weights logic is
gated on a derived
effective_early_stoppingflag rather than the rawself.early_stopping, so a run withvalidation_frequency=Nonecan'taccidentally revert to the pre-training weights at the end.
Public API Changes
New optional keyword-only parameter
validation_frequencyonFinetunedTabPFNBase.__init__,FinetunedTabPFNClassifier.__init__, andFinetunedTabPFNRegressor.__init__. Defaults to1, which preservesexisting behavior exactly, no breaking changes.
How Has This Been Tested?
test__finetuned_tabpfn_classifier__validation_frequency_skips_epochs:mocks
_evaluate_modeland asserts it's called only on the expectedepochs (plus the one pre-loop baseline eval) when
validation_frequency=2.test__finetuned_tabpfn_classifier__validation_frequency_none_disables_es:asserts the
UserWarningfires forearly_stopping=True+validation_frequency=None, and that fine-tuned weights are not revertedto the base model despite
early_stopping=True, proving early stopping isactually disabled rather than just skipped-but-still-restoring.
ruff check/ruff format --checkpass on all changed files.validation_frequency=1reproduces the prior code path exactly).
Checklist
changelog/README.md), or "no changelog needed" label requested.