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
[ENH] Refactoring and Generalizing DelegatedForecaster #2570
Conversation
Thank you for the feedback! I am working on implementing these/other changes this weekend/will request feedback when they are integrated/the code is passing the CI. Just to follow up on some of your comments in the meantime:
|
@@ -53,10 +54,10 @@ def __init__( | |||
self.scoring = scoring | |||
self.verbose = verbose | |||
self.return_n_best_forecasters = return_n_best_forecasters | |||
self.best_forecaster_ = forecaster |
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 think __init__
is not the right place for attributes. See also here: https://scikit-learn.org/stable/developers/develop.html#instantiation and here https://scikit-learn.org/stable/developers/develop.html#estimated-attributes
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.
Thanks @aiwalter, I definitely want to be consistent! Think I have a working work-around now so that defining self.best_forecaster_
no longer happens in init
.
|
||
def _fit(self, y, X=None, fh=None): | ||
def fit(self, y, X=None, fh=None): |
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 do you rename here to fit()
? This means you overwrite the parent fit()
and I think we should not do so 🤔
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.
So, it was actually intentional that I overwrote the parent fit()
, though I no longer do so now. :) Long story short - the overwriting was a way to get around the delegation of fit()
, but turns out this solution requires some sort of hacky workaround anyway.... so now this is _fit()
again, and we no longer attempt to delegate BaseGricCV
's fit()
- though we do still delegate the other functions! If you get a chance I would appreciate your thoughts on the new version! :) Thank you for flagging this, it was kind of a subtle point, and I think the new version that doesn't do this is cleaner.
Can you explain why you put the "delegate" functions there in the first place? I thought these should apply only to the inner methods such as
Hm, I think it might be a good idea overall, but it is a separate change and changes the base interface. So, could you open a separate PR where you apply this change to Formally, it´s a core interface change (how the base class functions), so it requires a STEP document (whicih can be in the PR for small changes). |
They actually apply to the non - underscore methods directly. As I mentioned in my initial comment, the way this is implemented actually allows the user to bypass the call to fit on some of the wrapped estimator types, which was intentional, and seems to work well! As to why I put it there in the first place - it was a simple mistake/I totally agreed with your point to move it - hence why I was happy to move it when you pointed it out! :)
So, it is worth noting this change doesn't actually break anything for anything referencing
In my mind - the best of these options is number 2, so that's the one I will plan to go with. It should also help with the |
Oh, it would be an additional interface point?
I actually do feel differently, since we are probably diverging in conceptualization of what the delegator should do. In my mind, it's a mixin - you would not use it directly, but inherit from it, in order to patch methods through quickly. Then, you would override methods which you would not want to have a patch through logic. As every mixin, it does not work by itself, but only in combination with some custom implementation. Besides the patching through of methods, I don't think anything else should happen, especially not things that are hard to override such as copying |
…into general_delegator
Hey! So I have made a few changes - though it is important to note it is only passing tests now because I commented out the
Well yes! Though as I mentioned I ended up changing the way I did this..... It is worth noting though, this is how the code already works for
I also implemented your suggestion to have a As for how to set the attributes - I have work around via an additional attribute - called
Again as always - no rush to check out these changes/I really appreciate all the helpful advice so far! Also - if we really think this approach doesn't work well, I am happy to scrap this PR. Though as it stands now it seems like a reasonable approach/solves some problems the mixin approach might not! :) (though I am obviously biased). |
Reference Issues/PRs
While implementing
MultiplexTransformer
for #2459 to mimic the changes in #2458 it became clear that writing a more general delegator function would be helpful.This PR technically depends on #2540.... was making sure there weren't any clashes with #2458... but if needed I can rebase and get rid of that dependency
What does this implement/fix? Explain your changes.
This replaces the
DelegatedForecaster
mixin with a decorator. The decorator will defer the function being wrapped to a delegated estimator if one is provided, and will also check that the current function is supported for the wrapped estimator (which should allow the decorating ability to be more general). So far it has only been implemented for Forecasters (with particular focus on making sure it works for the solution inMultiplexForecaster
, but I believe it should be easy to extend to transformers as well! (Note - it now becomes very important that whatever we are wrapping the estimator in has all the same supported functions as the wrapped estimator itself, thought I think this was the case/practice before!)this PR implements the following changes:
DelegatedForecaster
and associated references to it.delegate_name
attribute toBaseObject
as well asget_delegate
function (borrowed fromDelegateForecaster
)delegate_if_needed
) which will now handle the delegation and type checking. Currently lives in sktime.utils.check_estimators - but there might be a better place for it! (Let me know what you think!)MultiplexForecaster
to facilitate changing theselected_forecaster
to ensureforecaster_
is also updated (Used to be done withinfit()
, but now you have to ensure the newforecaster_
is set before callingfit()
on it.)BaseForecaster
toMultiplexForecaster
.Does your contribution introduce a new dependency? If yes, which one?
No
What should a reviewer concentrate their feedback on?
Any other comments?
Still have some Todos before I would consider this "ready to merge":
TypeError
if you try to use a method not supported by the wrapped estimator.(Also note - using the decorator actually solves some of the issues that were brough up in #2458 in that the
MultiplexForecaster
no longer needs to dynamically set the tags in order for the fit to work since it is technically 'bypassed' in the fit process. I left theclone_tags
so thatget_tags
on aMultiplexForecaster
will return the correct tags for the wrapped forecaster (sinceget_tags
is not delegated)- but perhaps this could be delegated as well, and we wouldn't need to set the tags dynamically at all. Thoughts??)Also - very much ok if we don't refactor in this way! I stopped before applying it to the transformers as well to get feedback on what you all thought of this solution! :)