-
Notifications
You must be signed in to change notification settings - Fork 47
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
base: main
Are you sure you want to change the base?
Conversation
Extract common logic into new base class
Requiring that they are composed of pure recommenders is an unnecessary limitation. Allowing other meta recommenders as building blocks can indeed be useful, for example, using a two-phase recommender where the second phase uses an adaptive (e.g. batch-size dependent) meta recommender
(StreamingSequentialMetaRecommender, None), | ||
], | ||
) | ||
def test_sequential_meta_recommender(cls, mode): |
There was a problem hiding this comment.
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 ...
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
@AVHopp @Scienfitz |
There was a problem hiding this 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.
There was a problem hiding this 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.
MetaRecommender, | ||
lambda x: unstructure_base( | ||
x, | ||
# TODO: Remove once deprecation got expired: |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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( |
There was a problem hiding this comment.
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?
This PR refines the semantics of Meta recommenders and extends their use. It was motivated by two problems:
allow_*
flags toCampaign
#423 wherepending_experiments
should only be passed toBayesianRecommenders
. Unfortunately, with the current meta recommender interface, it is is not straightforward (or impossible?) to identify what exactly the next recommender will be – which indicates a suboptimal design.The important things changed:
MetaRecommender
s now have a much simpler logic, i.e. they only contain aselect_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.is_stateful
class variableget_inner_recommender
method to leave the meta levelBatchSizeAdaptiveMetaRecommender
, which can be useful, e.g. to avoid too costly optimizations for large batch sizes.TwoPhaseMetaRecommender
now has an explicitremain_switch
option to clarify its behavior.