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

[MRG+1] test method subset invariance (Fixes #10420) #10428

Merged
merged 7 commits into from Jan 11, 2018

Conversation

Johayon
Copy link
Contributor

@Johayon Johayon commented Jan 8, 2018

Fixes #10420

  • added a test for all estimators to check if any of the methods {predict, predict_proba, decision_function, score_samples, transform} produces a different result if applied on all the data or a subset (in this case all the elements one by one).

  • the test currently fails in 4 cases

  1. SVC with decision_function (SVC and OneVsOneClassifier decision_function inconsistent on sub-sample #9174)
  2. SparsePCA with transform (SparsePCA inconsistent on sub-sample #10431)
  3. MiniBatchSparsePCA with transform (SparsePCA inconsistent on sub-sample #10431)
  4. BernoulliRBM with score_samples (due to stochasticity; can't easily fix)

@jnothman
Copy link
Member

jnothman commented Jan 8, 2018 via email

@jnothman
Copy link
Member

jnothman commented Jan 8, 2018 via email

@Johayon
Copy link
Contributor Author

Johayon commented Jan 8, 2018

I have created the issue for SparsePCA.
The test seems to fail on Birch on the build for python 3.4 because the absolute tolerance is too low, should I put a higher tolerance 1e-7 or 1e-6 ? (I do not have any issue on my build)

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.

Check if an increased tolerance works. It's still a bit surprising that that should be necessary.

res_all = res_all[0]
res_one = list(map(lambda x: x[0], res_one))
# TODO remove cases when corrected
if [name, method] in [['SVC', 'decision_function'],
Copy link
Member

Choose a reason for hiding this comment

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

use tuples. Like if name, method in [('SVC', 'decision_function'), ...]

tuples are intended for struct-like objects where each field means a different thing. Arrays are usually for homogeneous semantics

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.

This should probably not be marked WIP anymore :P

@jnothman jnothman changed the title [WIP] test method subset invariance (Fixes #10420) [MRG+1] test method subset invariance (Fixes #10420) Jan 9, 2018
@jnothman
Copy link
Member

Please add an entry to the change log at doc/whats_new/v0.20.rst under "Changes to estimator checks". Like the other entries there, please reference this pull request with :issue: and credit yourself (and other contributors if applicable) with :user:

@ignore_warnings(category=(DeprecationWarning, FutureWarning))
def check_methods_subset_invariance(name, estimator_orig):
# check that method gives invariant results if applied
# one by one or on all elements together.
Copy link
Member

Choose a reason for hiding this comment

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

I would slightly rephrase the comment by replacing one by one by something like "if applied on mini bathes or the whole set"

if hasattr(estimator, method):
msg = ("{method} of {name} is not invariant when applied "
"to a subset.").format(method=method, name=name)
func = getattr(estimator, method)
Copy link
Member

Choose a reason for hiding this comment

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

I would personally make a small private function to compute and unpack the data.

def _apply_func(func, X):
    result_full = func(X)
    n_features = X.shape[1]
    result_by_batch = [func(batch.reshape(1, n_features))
                       for batch in X]
    # func can output tuple (e.g. score_samples)
    if type(res_all) == tuple:
        result_full = result_full[0]
        result_by_batch = list(map(lambda x: x[0], result_by_batch))

    return np.ravel(result_full), np.ravel(result_by_batch)

def check_methods_subset_invariance(name, estimator_orig):
    ...
    result_full, result_by_batch = _apply_func(get_attr(estimator, method))
    ...
    assert_allclose(results_full, results_by_batch,
                    atol=1e-7, err_msg=msg)

@glemaitre
Copy link
Member

LGTM apart of a change in a comment and some coding style.

for method in ["predict", "transform", "decision_function",
"score_samples", "predict_proba"]:

msg = ("{method} of {name} is not invariant when applied "
Copy link
Member

Choose a reason for hiding this comment

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

we can actually move this message before the assert_close in fact.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@glemaitre you want to put it outside the block for and format it inside the assert_close and SkipTest ?

Copy link
Member

Choose a reason for hiding this comment

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

oh my bad I did not see the occurrence in SkipTest. Good as it is

raise SkipTest(msg)

if hasattr(estimator, method):
result_full, result_by_batch = _apply_func(getattr(estimator,
Copy link
Member

Choose a reason for hiding this comment

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

It will be easier to read as :

result_full, result_by_batch = _apply_func(
    getattr(estimator, method), X)

@glemaitre
Copy link
Member

@Johayon 2 small nitpicks and this is good to be merged once it will be green :)

@glemaitre glemaitre merged commit 4a9034a into scikit-learn:master Jan 11, 2018
@glemaitre
Copy link
Member

@Johayon Thanks!!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add common test to ensure all(predict(X[mask]) == predict(X)[mask])
3 participants