-
-
Notifications
You must be signed in to change notification settings - Fork 25.9k
ENH allows checks generator to be pluggable #18750
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
base: main
Are you sure you want to change the base?
ENH allows checks generator to be pluggable #18750
Conversation
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.
For now I'm a bit concerned about the tradeoff between complexity and usefulness.
This will be mostly useful to libraries that implement lots of scikit-learn compatible estimators and that want to reuse our check infrastructure (which is quite unstable). I don't have the entire landscape in mind but I'm not sure this will be used by any other lib except for imbalance learn?
purpose. This parameter is a generator that yield callables such as:: | ||
|
||
|
||
def check_estimator_has_fit(name, instance, strict_mode=True): |
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.
this will be api_only
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.
Yep but we need to merge your PR first.
I would say that it can be enlarged to |
So if I understand correctly, the use case is that you have some additional estimator checks that you would like to run in imbalanced-learn and you don't want to re-implement Having more flexibility in check generation wouldn't hurt I think. For instance, currently in tslearn common checks are monkeypatched (#14057 (comment)) mostly to address the difference in input types, but I guess this could also be useful maybe @rtavenar? As there are common checks that would make for time series input that we don't include in scikit-learn. Generally even if it only helps @glemaitre and @chkoar maintaining imbalanced-learn and doesn't have much cost for us, and we are clear that this is experimental (I wouldn't say it adds that much complexity) I would still be +1 for it. I think facilitating maintenance of projects in scikit-learn-contrib is something that we want to do, when possible. |
exactly |
I kinda disagree here because this PR makes a quite strong assumption: it assumes that the checks come from a generator of callables (whose signatures are "experimental", but still), while that's supposed to be an implementation detail. When it's in, we can't really go back on this - and yet we might want to refactor our check framework someday, considering how difficult it is to extend (see e.g. the 4 prototype PRs for just adding 1 new "api_only" parameter).
|
if checks_generator is None: | ||
checks_generator = _yield_all_checks | ||
|
||
def _checks_generator(): |
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.
Nit but we don't need the newly-added leading underscore here since there's no notion of private/public (it might simplify the diff also)
Basically it makes me uncomfortable because this means that with this PR, For example, Also, what exactly needs to be re-written in downstream libraries? It seems to me that only |
For taking the advanatge of the tags, you need to take the way the At least, it will work. However, if I want to follow the progress of scikit-learn, then I just want to modify as little as possible of some scikit-learn utilities. In this case, I am importing some private things which at the end are going to break (which I cannot complain). The change could be from a couple of lines wihtin a function or just a private function that does not exist anymore. That's why, I would rather deal with some private API changes instead of having to investigate internal change of code since the last release. However, I agree that in both cases, it is my problem and not the issue of scikit-learn since they are private changes. |
Only for the And worst case scenario, it's always possible to set |
We were importing: from sklearn.utils.estimator_checks import _mark_xfail_checks
from sklearn.utils.estimator_checks import _set_check_estimator_ids but these 2 functions do not exist anymore and have been replaced. So we need to change the import and modify our own
Yes we could potentially monkey patch: @parametrize_with_checks(
list(_tested_estimators()), checks_generator=sklearn_yielder,
)
def test_estimators_compatibility_sklearn(estimator, check, request):
_set_checking_parameters(estimator)
check(estimator)
@parametrize_with_checks(
list(_tested_estimators()), checks_generator=imblearn_yielder,
)
def test_estimators_imblearn(estimator, check, request):
# Common tests for estimator instances
with ignore_warnings(category=(FutureWarning,
ConvergenceWarning,
UserWarning, FutureWarning)):
_set_checking_parameters(estimator)
check(estimator) So it means that we should be careful about test ordering while overwriting |
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 like this, we can even have different pre-defined generators for users to try, like all and api and ...
available in scikit-learn. It is common for a third-party library to extend | ||
the test suite with its own estimator checks. |
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 like your usage of "common" here :D :D
The generator yielding checks for the estimators. By default, the | ||
common checks from scikit-learn will be yielded. | ||
|
||
.. versionadded:: 0.24 |
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.
also note experimental
|
||
def test_check_estimator_checks_generator(): | ||
# Check that we can pass a custom checks generator in `check_estimator` | ||
assert_warns_message( |
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.
with pytest.warns(...):
?
test_estimator = decorator(test_estimator) | ||
for _mark in test_estimator.pytestmark: | ||
for estimator, check in _mark.args[1]: | ||
assert_warns_message( |
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.
with pytest.warns
?
That would be redundant with the |
I agree. In our case, we are mostly interested in tuning the data that comes in the checks, but I guess there are some other uses cases where checks themselves should be tuned. I don't know which is the best technical solution for that but having a principled way to do it at some point would be great for downstream libraries. |
Another way to look at it, is that |
I agree with your comment @adrinjalali and I feel like it actually illustrates one of the numerous ways in which the check framework isn't mature enough ;) , in particular this part:
|
Maybe we can meet halfway: is there a way we can implement all this via a completely private logic? Like, instead of adding a new parameter, can we instead implement a private way to "register" the generator to be used by This way, libraries can plug their own generators, but we keep this private so that we don't have to constrain ourselves in the future when we'll refactor our check logic (which IMHO will have to happen soonish if we want to keep expanding it efficiently. An entire reworking would likely be beneficial for all in the long term) |
What would be the practical difference with an experimental
Do you mean not mature in terms or implementation/maintenance or usage? If the implementation complexity is a concern, one a bit radical step could to require pytest for common tests, and deprecate |
In the current state of this PR, the parameter isn't experimental; only the signature of the check is noted as experimental. But we're stuck with the parameter once it's introduced. I'd be more comfortable if the whole parameter was experimental, but even more so if we kept everything private: those few that really need this will use it at their own risk, but we don't advertise it.
Not mature in terms of implementation/maintenance. The usage/public API is fine as far as I can tell.
I've been submitting quite a bit of PRs to simplify the logic lately, while I was working on the We pass useless parameters to most checks:
I'd be favorable to that, not relying on pytest has been an impediment for maintaining the check suite so far. |
This PR allows passing a third-party generator that yields custom checks.
In imbalanced-learn, we reimplement the infrastructure developed in scikit-learn just to overwrite
_yield_all_checks
with our own generator. It would be more friendly to allow plugging any generator.However, we should mention that the signature of the
check
functions can be changed at any time.