Skip to content

[MRG] MNT Api only mode in check_estimator #18582

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

Closed
wants to merge 19 commits into from

Conversation

NicolasHug
Copy link
Member

@NicolasHug NicolasHug commented Oct 9, 2020

Closes #13969
Towards #16241

This PR introduces the possibility to only run API-compatibility checks in check_estimator and parametrize_with_checks, as discussed in the previous monthly meeting. This is a follow-up to #17361, though I had to rename strict_mode=True into
api_only=False.

I had to make a bunch of decisions on what we consider to be an API check. In general, my line of thought was:

  • API checks:
    • checks for __init__, set/get_params(), attributes etc
    • ensuring basic errors that may lead to wrong results (e.g. discrepancies between n_samples and len(sample_weight))
  • Non-API checks:
    • ensuring other errors (for user-friendlyness)
    • error message checking
    • prediction checks (discrimination power, minimal score, consistency between predict_proba and decision_function)

However I don't feel strongly about any of these, so please feel free to directly push to this PR if you disagree ;)

CC @jnothman @rth @thomasjpfan @ogrisel @adrinjalali @glemaitre

@NicolasHug NicolasHug changed the title Api only mode [MRG] MNT Api only mode in check_estimator Oct 9, 2020
Copy link
Member

@thomasjpfan thomasjpfan left a comment

Choose a reason for hiding this comment

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

At some point, I think we need to have docstrings for at least the API-only checks, so we can include it in the docs. This way there is a human readable description of "what is the API".

'check_n_features_in',
# set of checks that do not check API-compatibility. They are ignored when
# api_only is True.
_NON_API_CHECKS = set([
Copy link
Member

Choose a reason for hiding this comment

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

I think we should hard code the complement of this set, _API_CHECKS so it is easier to tell "what is the API".

In the docs, we can point to _API_CHECKS to be "what the API is".

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't think we can immediately do that, because there are 3 kinds of checks, not 2:

  1. the ones that don't check the API at all (in this dict)
  2. the ones that only check the API
  3. the ones that check the API in some parts, and do some other kinds of checks in other parts.

1 and 2 don't use the api_only parameter. The checks in 3 are the only ones to use it (we agreed in previous PRs that this was the easiest way to go).

separating 2 and 3 would be a lot of work, probably worth it I agree, but out of scope here.

Copy link
Member

Choose a reason for hiding this comment

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

separating 2 and 3 would be a lot of work, probably worth it I agree, but out of scope here.

I see defining the API mainly about communication. Having 3 makes it harder to communicate what the API is.

I guess as a first step, we can add this api_only=True, and state "Your estimator is API compliant if you pass check_estimator(..., api_only=True)". In a future PR, we can separate 2 and 3 and then we can firmly say "These are the checks that test the API".

Copy link
Member Author

Choose a reason for hiding this comment

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

Agreed. Merging this PR isn't the end of the road, it's only the beginning.
Which is why I think we should keep things as simple as possible here, and improve incrementally in the future.

@NicolasHug
Copy link
Member Author

At some point, I think we need to have docstrings for at least the API-only checks, so we can include it in the docs. This way there is a human readable description of "what is the API".

Totally agree, the checks need much better docs. Most of them were completely undocumented. In this PR I tried to document them at least a little bit to ease the review.

Copy link
Member

@jnothman jnothman left a comment

Choose a reason for hiding this comment

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

I like this redefinition of "strict". I wonder whether I would have designed it as mode="compatible" vs mode="strict". This is looking good to me, but I've not had time to review in full that the implementation matches the designation of what is "API only".

@NicolasHug NicolasHug added this to the 0.24 milestone Oct 12, 2020
Copy link
Member

@adrinjalali adrinjalali left a comment

Choose a reason for hiding this comment

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

Thanks @NicolasHug

Haven't gone through all the tests yet. But I guess I'd include quite a few more tests in API tests than what we have now. And there are a bunch of tests which have api_only=False defaults, but also have if not api_only sections, not sure if that makes sense.

# When api_only is True, check_fit2d_1sample is skipped along
# with the rest of the xfail_checks
with pytest.warns(SkipTestWarning, match=skip_match):
check_estimator(NuSVC(), api_only=True)
Copy link
Member

Choose a reason for hiding this comment

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

if we're calling this with api_only=True, then why is there a need to ignore a check_fit2d_1sample is not an API check warning?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry I don't understand the question. We're not ignoring a warning here, we're making sure it's raised.

The SkipTestWarning is raised by check_fit2d_1sample because it is a non-API check and we have set api_only=True. There are other warnings that are raised by check_estimator, we just assert that at least this one is raised.

Copy link
Member

Choose a reason for hiding this comment

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

What I mean here is, as a user, if I call check_estimator(..., api_only=True), I'm explicitly saying I only want the api_only tests. Then why would I get a warning which says check_fit2d_1sample is not an API check? Should I just not be getting that warning?

Copy link
Member Author

Choose a reason for hiding this comment

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

We're treating these checks as if they were in the _xfail_checks tag and for this tag we decided to ignore with a warning. It makes things much simpler to re-use that logic rather than implement a new check-ignoring logic (I believe there are discussions in previous PRs like #17361)

I personally don't have a strong opinion on whether the ignored checks should raise a warning or not. Note that for parametrize_with_checks, which is sort of the sister of check_estimators, those checks would be marked as XFAIL, so pytest would still output them in stdout saying something like "this was xfailed" or "this was xpassed".

Maybe @rth @thomasjpfan can remind us why we decided to ignore with a warning instead of just ignoring?

Copy link
Member

Choose a reason for hiding this comment

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

I think this is better to raise a skip warning. It allows us to know which tests were not run and for which reason.

# this case, check_fit_non_negative() will not check the exact error
# messsage. (We still assert that the warning from
# check_fit2d_1sample is raised)
with pytest.warns(SkipTestWarning, match=skip_match):
Copy link
Member

Choose a reason for hiding this comment

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

same question here

Copy link
Member Author

Choose a reason for hiding this comment

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

same answer ;)

Copy link
Member Author

@NicolasHug NicolasHug left a comment

Choose a reason for hiding this comment

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

Thanks @adrinjalali for the review, I replied in the comments.

I'd include quite a few more tests in API tests than what we have now

I believe most of these already are API checks. Any check that isn't in the NON_API_CHECKS will (at least partially) check the API. The value of the api_only param determines which parts of these checks are part of the API. Some checks are actually full API checks, in which case api_only isn't even used.

And there are a bunch of tests which have api_only=False defaults, but also have if not api_only sections, not sure if that makes sense

We want the default api_only=False to be False because we want the check to be strict by default. All checks need to have this default (whether they use it or not) so that calls to checks (with partials, etc.) don't get too complicated. The value of the default is not an indiation of whether the check is part of the API checks or not. I agree this is not completely ideal but this is the solution we went for, after quite a lot of alternatives (#17361 (review)). We also need to think about backward compatibility here.

In general the whole check suite could use a big re-design. Even the "name" parameters mostly don't make sense and are usually not used. I'd love to see that happen, but that's not in scope here.

# When api_only is True, check_fit2d_1sample is skipped along
# with the rest of the xfail_checks
with pytest.warns(SkipTestWarning, match=skip_match):
check_estimator(NuSVC(), api_only=True)
Copy link
Member Author

Choose a reason for hiding this comment

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

Sorry I don't understand the question. We're not ignoring a warning here, we're making sure it's raised.

The SkipTestWarning is raised by check_fit2d_1sample because it is a non-API check and we have set api_only=True. There are other warnings that are raised by check_estimator, we just assert that at least this one is raised.

# this case, check_fit_non_negative() will not check the exact error
# messsage. (We still assert that the warning from
# check_fit2d_1sample is raised)
with pytest.warns(SkipTestWarning, match=skip_match):
Copy link
Member Author

Choose a reason for hiding this comment

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

same answer ;)

@glemaitre
Copy link
Member

The split between API/non-API seems OK with me.

I think that we should split the test into these 2 groups later on. It would allow having nicer documentation that we can expose publicly as well as pointed out by @thomasjpfan

@glemaitre
Copy link
Member

mode="compatible" vs mode="strict"

I am wondering if it would not be a good solution. This might allow grouping in more fine-grained the test maybe.

@@ -816,7 +816,7 @@ def check_estimator_sparse_data(name, estimator_orig, strict_mode=True):


@ignore_warnings(category=FutureWarning)
def check_sample_weights_pandas_series(name, estimator_orig, strict_mode=True):
def check_sample_weights_pandas_series(name, estimator_orig, api_only=False):
Copy link
Member

Choose a reason for hiding this comment

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

My latest comment regarding several mode would be nice here. One mode would be the interoperability of array-like.
One might want to be API and array-like compliant while not be strict on the error message raised. In this case maybe mode is not a good name but I have in mind something like model=["api", "interoperability"]

Copy link
Member

@glemaitre glemaitre left a comment

Choose a reason for hiding this comment

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

I think I am fine with the change. I would maybe prefer to introduce a list of str instead of single keyword thought. WDYT @NicolasHug ?

NicolasHug and others added 2 commits October 28, 2020 18:02
Co-authored-by: Guillaume Lemaitre <g.lemaitre58@gmail.com>
@NicolasHug
Copy link
Member Author

I would maybe prefer to introduce a list of str instead of single keyword thought.

The mode="compatible" vs mode="strict" suggestion (and variations) is preferable if we ever plan to have more than 2 levels of checks. For now we only have 2 levels (API and non-API).

No strong opinion, but I'm not sure having lots of check levels is something we want to get into.

Copy link
Member

@glemaitre glemaitre left a comment

Choose a reason for hiding this comment

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

I am fine with this step forward.

@glemaitre glemaitre self-assigned this Nov 6, 2020
@glemaitre
Copy link
Member

I just tested the approach in LightGBM. Once microsoft/LightGBM#3533 is merged (changes that were required after announcing some deprecation in 0.23), the api_only is working. I will try to do the same in xgboost.

@ogrisel
Copy link
Member

ogrisel commented Nov 6, 2020

(changes that were required after announcing some deprecation in 0.23)

Did we announced in 0.22 or 0.23?

@ogrisel
Copy link
Member

ogrisel commented Nov 6, 2020

Maybe also hdbscan and UMAP?

@glemaitre
Copy link
Member

we announce in 0.23 for changing in 0.24

@jeremiedbb
Copy link
Member

I tested check_estimator on cuml estimators and all fail because they don't have estimator tags (they do not even inherit from BaseEstimator). I wonder if having estimator tags is mandatory to be a compatible estimator. Do you think check_estimator could somehow work on estimators without tags, maybe by assuming default tags in this case (seems brittle though...) ?

@glemaitre
Copy link
Member

glemaitre commented Nov 9, 2020 via email

@NicolasHug
Copy link
Member Author

NicolasHug commented Nov 23, 2020

(please edit if I missed anything)

We just had a meeting with @glemaitre @ogrisel @jeremiedbb @amueller @adrinjalali @thomasjpfan

With the approaching release and the potential for a cleaner/more modular implementation (#18750), we decided on the following (among other things):

  • not merge this and revert [MRG] Prototype 4 for strict check_estimator mode #17361
    In effect, this keeps the status quo regarding the "api only" checks for 1 version, which is slightly inconvenient for third parties, but this allows us to not introduce changes that we might need to deprecate.
  • break checks into more unitary checks, in order to only have "full api checks" and "full non-api checks". We'll consider that all the checks are private, so that we don't need to worry about potentially breaking backward compat. Considering them private would be too constraining.
  • try to clean and simplify estimator_checks.py. For example we don't need to pass the estimator's name to any check.
  • implement the strict/api_only mode via ENH allows checks generator to be pluggable #18750 or similar

@NicolasHug NicolasHug removed this from the 0.24 milestone Nov 23, 2020
@NicolasHug NicolasHug closed this Nov 25, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add non-strict mode to check_estimator
7 participants