Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simplify and extend meta recommender logic #457

Open
wants to merge 26 commits into
base: main
Choose a base branch
from

Conversation

AdrianSosic
Copy link
Collaborator

@AdrianSosic AdrianSosic commented Jan 10, 2025

This PR refines the semantics of Meta recommenders and extends their use. It was motivated by two problems:

The important things changed:

  • MetaRecommenders now have a much simpler logic, i.e. they only contain a select_recommender, which always just returns the recommender appropriate for the context of the call. Previous statefulness, which was only relevant to sequential recommenders, has been moved to the corresponding classes.
  • They now have an is_stateful class variable
  • They now have an get_inner_recommender method to leave the meta level
  • They can now be composed of arbitrary other meta recommenders. The previous restriction that they need to be composed of pure recommenders was an unnecessary limitation. Allowing other meta recommenders as building blocks can indeed be indeed useful, for example, using a two-phase recommender where the second phase uses an adaptive (e.g. batch-size dependent) meta recommender.
  • Added the new BatchSizeAdaptiveMetaRecommender, which can be useful, e.g. to avoid too costly optimizations for large batch sizes.
  • TwoPhaseMetaRecommender now has an explicit remain_switch option to clarify its behavior.

@AdrianSosic AdrianSosic added enhancement Expand / change existing functionality refactor labels Jan 10, 2025
@AdrianSosic AdrianSosic self-assigned this Jan 10, 2025
@AdrianSosic AdrianSosic marked this pull request as ready for review January 10, 2025 14:33
@AdrianSosic AdrianSosic marked this pull request as draft January 10, 2025 14:34
baybe/recommenders/meta/sequential.py Show resolved Hide resolved
(StreamingSequentialMetaRecommender, None),
],
)
def test_sequential_meta_recommender(cls, mode):
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I drafted this part before Christmas and only remember that it was a big brain fuck. Instead of going once again over it, I'd simply give it for you to review with a fresh mind 🙃 Perhaps you can find some logic flaw that I overlooked or a test condition that is missing. Potentially, the whole thing can also be implement much more elegantly? Don't know ...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really don't like all of the manipulation happening here. Why not crafting a very simple explicit search space (or what ever is necessary) and actually do a few recommendations?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how your comment relates to the test, tbh. Even if there was a searchspace involved, the point of the test is to check if the recommendation is made by the correct recommender – the actual recommendation output does not matter. So what is tested here if the recommender selection is done correctly, not the recommendation itself. And for that, the search space is not relevant at all.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me formulate it that way: I would prefer an example that does not require you to explicitly manipulate stuff like _was_used by hand and that we do not need to "pretend" that stuff happens. I'd prefer a test where this actually happens.

My impression was that this would be achieved the easiest by having a "full" example including a search space.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, now I understand what you mean. I agree with the _was_used part, I didn't like that either. But at the same time, we should take more care that our tests are actual unit tests, i.e. that they don't run unnecessary costly code. In particular, we should avoid the anti-pattern that we also have in our examples where you construct something and then – regardless of the limited context – you do a full campaign-recommend loop.

So probably the "correct" way to achieve best of both worlds is to separate the "caching" of the meta recommends from the actual selection logic, so that in the test, we can focus only on the latter without having to modify flags. Let me see if this is easily doable...

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't get me wrong, I fully get your point and would also prefer to simplify tests as much as possible. I think we are on the same page here. Maybe we should also ask @Scienfitz for his opinion?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tried to come up with a better design that wouldn't require manually setting was_used in the tests but I always ended up with a more complicated class in the end. So unless one of you has a concrete proposal that reduces overall complexity, I'd stick with the current approach, i.e. rather have one inelegant step in the test then in the class itself. Will leave open until fully reviewed

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you mean with "more complicated class"? What class exactly are you talking about, why would you need to adjust a class for a test?

@AdrianSosic AdrianSosic marked this pull request as ready for review January 10, 2025 14:45
@AdrianSosic
Copy link
Collaborator Author

@AVHopp @Scienfitz
Note, I've just added one more commit introducing a get_inner_recommender method, which is useful for #423.

Copy link
Collaborator

@AVHopp AVHopp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Incomplete review since there was another issues that required my attention, but still a first bunch of comments.

CHANGELOG.md Outdated Show resolved Hide resolved
CHANGELOG.md Outdated Show resolved Hide resolved
baybe/recommenders/meta/base.py Outdated Show resolved Hide resolved
baybe/recommenders/meta/sequential.py Show resolved Hide resolved
baybe/recommenders/meta/sequential.py Show resolved Hide resolved
baybe/recommenders/meta/sequential.py Outdated Show resolved Hide resolved
baybe/recommenders/meta/sequential.py Show resolved Hide resolved
baybe/recommenders/meta/sequential.py Show resolved Hide resolved
baybe/recommenders/meta/sequential.py Show resolved Hide resolved
baybe/recommenders/meta/sequential.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@AVHopp AVHopp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Last bit of my review that was still missing.

baybe/recommenders/meta/adaptive.py Outdated Show resolved Hide resolved
baybe/recommenders/meta/adaptive.py Outdated Show resolved Hide resolved
MetaRecommender,
lambda x: unstructure_base(
x,
# TODO: Remove once deprecation got expired:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was the deprecation expired or why was this removed?

"""Determines if the recommender should remain switched even if the number of
experiments falls below the threshold value in subsequent calls."""

_has_switched: bool = False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why no attrs syntax for this and remain_switched?

# If the training dataset size has decreased, something went wrong
if (
n_data := len(measurements) if measurements is not None else 0
) < self._n_last_measurements:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does this condition come from? Why is it not allowed to decrease data size? For 2phase you explicitly allow it and implement something

if this is simply our convention/definition if ssequential metarecommenders then it needs o be well mentioned and documented

"""The recommenders for the individual batch size intervals."""

partition: Partition = field(
converter=lambda x: Partition(x) if not isinstance(x, Partition) else x
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we want to provide reasonable defaults for recommenders and partition ? Otherwise this class seems useless as most users have little clue how to set it up reasonably

objective: Objective | None = None,
measurements: pd.DataFrame | None = None,
pending_experiments: pd.DataFrame | None = None,
) -> PureRecommender:
) -> RecommenderProtocol:
"""Select a pure recommender for the given experimentation context.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring still says pure but i guess is should say non-meta?

@@ -453,7 +460,13 @@ def get_surrogate(self) -> SurrogateProtocol:

pure_recommender: RecommenderProtocol
if isinstance(self.recommender, MetaRecommender):
pure_recommender = self.recommender.get_current_recommender()
pure_recommender = self.recommender.select_recommender(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it guaranteed that this returns a pure recommender?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement Expand / change existing functionality refactor
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unintuitive switching behavior of Meta-Recommenders in combination with Campaign
3 participants