Repository navigation
Conversation
ba44509 to
a7d73c2
Compare
a7d73c2 to
abcd67d
Compare
d9a7e30 to
584a25d
Compare
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Shallow component sharing and stale delegate state can produce incorrect models, while the delegated return type also violates its API annotation.
Review effort: Balanced
Findings: 1
Open (3)
What changed in this PR
Adds IDENTITY and RGPE transfer-learning modes, including public kernel/surrogate APIs, GP dispatch, benchmarks, documentation, and tests.
Changes:
- Implements pooled identity-kernel modeling and rank-weighted GP ensembles.
- Dispatches GP surrogates to RGPE and supports composite BoTorch models.
- Updates benchmarks, serialization strategies, documentation, and coverage.
| File | Description |
|---|---|
baybe/kernels/basic.py |
Adds IdentityKernel. |
baybe/kernels/__init__.py |
Exports the identity kernel. |
baybe/parameters/enum.py |
Adds IDENTITY and RGPE modes. |
baybe/parameters/categorical.py |
Resolves task-mode overrides. |
baybe/searchspace/core.py |
Adds task-mode cloning. |
baybe/surrogates/transfer_learning/rgpe.py |
Implements RGPE fitting and prediction. |
baybe/surrogates/transfer_learning/__init__.py |
Exports RGPE. |
baybe/surrogates/gaussian_process/core.py |
Adds RGPE delegation. |
baybe/surrogates/gaussian_process/presets/baybe.py |
Selects MLL for identity mode. |
baybe/surrogates/composite.py |
Supports generic BoTorch model lists. |
baybe/surrogates/__init__.py |
Publishes RGPE. |
tests/test_rgpe_surrogate.py |
Tests RGPE behavior and dispatch. |
tests/test_parameter_kernel_overrides.py |
Tests identity overrides. |
tests/test_iterations.py |
Adjusts iteration coverage. |
tests/serialization/test_surrogate_serialization.py |
Tests RGPE serialization. |
tests/hypothesis_strategies/surrogates.py |
Adds RGPE generation. |
tests/hypothesis_strategies/kernels.py |
Adds identity-kernel generation. |
benchmarks/domains/michalewicz/convergence_tl.py |
Uses identity-mode baseline. |
benchmarks/domains/hartmann/convergence_tl.py |
Uses identity-mode baseline. |
benchmarks/domains/easom/convergence_tl.py |
Uses identity-mode baseline. |
benchmarks/domains/direct_arylation/convergence_tl.py |
Uses identity-mode baseline. |
benchmarks/domains/aryl_halides/core.py |
Uses identity-mode baseline. |
docs/concepts/transfer_learning.md |
Documents ensemble transfer learning. |
docs/references.bib |
Adds the RGPE citation. |
CHANGELOG.md |
Records the new APIs and benchmarks. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| import torch | ||
|
|
||
| assert self._searchspace is not None # ensured by base class | ||
| assert self._objective is not None # ensured by base class | ||
| assert self._measurements is not None # ensured by base class | ||
|
|
||
| identity_searchspace, sources, target_measurements = self._split_measurements() | ||
|
|
||
| # Fit one source GP per source task that has data. | ||
| source_gps = [] | ||
| for _, source_measurements in sources: | ||
| source_gp = evolve(self.base_surrogate) | ||
| source_gp.fit(identity_searchspace, self._objective, source_measurements) | ||
| source_gps.append(source_gp) |
| task_param = self._searchspace._task_parameter | ||
| if ( | ||
| task_param is not None | ||
| and task_param.override_transfer_learning_mode is TransferLearningMode.RGPE | ||
| ): |
| def to_botorch(self) -> GPyTorchModel: | ||
| if self._delegate is not None: | ||
| return self._delegate.to_botorch() |
AVHopp
left a comment
There was a problem hiding this comment.
Initial look into some of the architecture - did in particular not look at the surrogate itself so far, because I feel that we first need to talk about the design regarding _model and _delegate
| raise IncompatibleSurrogateError( | ||
| f"Providing a posterior mean function for a " | ||
| f"'{self.__class__.__name__}' that dispatches to a " | ||
| f"'{type(self._delegate).__name__}' transfer learning ensemble is not " | ||
| f"implemented." | ||
| ) |
There was a problem hiding this comment.
Is this not possible or not implemented? Because depending on this, I'd say that a NotImplementedError might be more suitable.
| # IDENTITY mode keeps the task dimension but makes it inert (constant task | ||
| # kernel), so the model is effectively single-task and is fit like one. | ||
| elif ( | ||
| task_param is not None |
There was a problem hiding this comment.
DO we need that check here or is this already guaranteed since we are in the else branch? In the second case, I would prefer to rather have a full "if-else" here with an explicit assert on task_param before that check, as this would read a bit nicer imo (but only my preference, so feel free to just resolve if you disagree)
| """:class:`botorch.models.kernels.positive_index.PositiveIndexKernel` for positive | ||
| correlations.""" | ||
|
|
||
| RGPE = "RGPE" |
There was a problem hiding this comment.
Maybe RGPE_SURROGATE to be more in the same style as ..._KERNEL?
| class TransferLearningMode(Enum): | ||
| """Transfer learning modes for :class:`.TaskParameter`.""" | ||
|
|
||
| IDENTITY = "IDENTITY" |
There was a problem hiding this comment.
Should this maybe be renamed into IDENTITY_KERNEL or CONSTANT_KERNEL such that it fits the other members?
| delegate = RGPESurrogate(base_surrogate=evolve(self)) | ||
| delegate.fit(self._searchspace, self._objective, self._measurements) | ||
| self._delegate = delegate | ||
| return |
There was a problem hiding this comment.
Should this already return at this point? Don't we then loose some of the errors and warning regarding deprecations, misconfigurations and so on silently?
|
|
||
| # TODO: type should be `Surrogate | None` but is currently omitted due to: | ||
| # https://github.com/python-attrs/cattrs/issues/531 | ||
| _delegate = field(init=False, default=None, eq=False) |
There was a problem hiding this comment.
I am sure that you investigated this, but why can't we simply use _model? From what I see, there is basically never a situation where we actually need both _model and _delegate, and also semantically, the RGPE model is the model - so what is the reason for this separation?
| @@ -381,6 +404,8 @@ def posterior_mean_function( | |||
|
|
|||
| @override | |||
| def to_botorch(self) -> GPyTorchModel: | |||
There was a problem hiding this comment.
This holds for a lot of the functions here: If I would use the same GaussianProcessSurrogate object for different spaces, it could happen that both _delegate and _model are being set and used. However, as soon as _delegate has been set, the object will always return with respect to this model. Questionable if this is a misuse of the GaussianProcessSurrogate and whether or not it is our responsibility to do something regarding this, but I at least want to flag it.
If we are able to somehow get rid of this duality regarding _model and _delegate, this issue might also just resolve itself.
| source_tasks=source_tasks, | ||
| target_tasks=target_tasks, | ||
| transfer_learning_mode=TransferLearningMode.IDENTITY, |
There was a problem hiding this comment.
Why did you explicitly add source_tasks and target_tasks here, but not in some of the other benchmarks?
| return st.builds( | ||
| RGPESurrogate, | ||
| base_surrogate=gaussian_process_surrogates(), | ||
| n_mc_samples=st.integers(min_value=1, max_value=512), |
There was a problem hiding this comment.
Why such a high max_value? Is there a special reason for such a variety of values or would 1 and >1 already suffice?
- constant task kernel that leaves the base kernel untouched, pooling all tasks
- rank-weighted ensemble of per-task GPs, usable directly or via the RGPE task override - inner GPs fit on the identity-mode space so the task dimension stays inert
584a25d to
fb6338a
Compare



Transfer learning:
IDENTITYandRGPEmodesTwo new options for
TaskParameter.override_transfer_learning_modebuilding on the parameter kernel-override machinery from #904.IDENTITYis backed by a new publicIdentityKernel.IDENTITYUses a new public
IdentityKernel(baybe.kernels). It contributes no covariance on the task dimension, so every task collapses into a single pooled model (equivalent to the "naive" baseline where the task label is effectively ignored). Like the(POSITIVE_)INDEX_KERNELmodes, it's a plain kernel override and treated the same way in the factory.An
IDENTITYsearch space is a genuine single-task problem, so it is fit with the marginal log-likelihood rather than the leave-one-out pseudo-likelihood used for the true multi-task kernels.RGPEInstead of modelling task correlations with a kernel, it trains one GP per source task plus one target GP and combines their posteriors with weights derived from a leave-one-out ranking loss. With fewer than two target points there's nothing to rank, so it falls back to a uniform average. With no target data it averages the source models alone.
Each inner GP is fit over the same search space under the
IDENTITYTL override (searchspace._with_task_mode(IDENTITY)), so the task dimension is inert and every inner model is a clean single-task GP over the shared features. Because the inner models are just evolved copies of the base surrogate, any parameter-specific kernel overrides carry through unchanged (supports_kernel_overrides = True).Dispatching in the GP factory
GaussianProcessSurrogate._fitchecks the task parameter's override.IDENTITYis just a kernel override and treated like(POSITIVE_)INDEX_KERNELoverrides.RGPEcan't be expressed as a kernel, so the factory builds anRGPESurrogatefrom the current GP's own configuration (viaevolve) and stashes it as a_delegate;to_botorch,_posterior, etc. then forward to it. This keeps all the usual GP settings (kernel, priors, scaling) for the inner models.Benchmarks
The transfer-learning convergence benchmarks previously built their "naive" baseline by dropping the
TaskParameterfrom the search space entirely. They now build it from aTaskParameterwithoverride_transfer_learning_mode="IDENTITY"instead.Tests
tests/test_rgpe_surrogate.py— weighting, cold-start and<2-target fallbacks,source/target splitting, single-output/single-task guards, serialization roundtrip,
and
IDENTITY-matches-naive-pooling equivalence.tests/test_parameter_kernel_overrides.py— IDENTITY override and its interactionwith kernel overrides.
IdentityKernel.