Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[MRG] Add common test and estimator tag for preserving float32 dtype in transformers #16290

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

Conversation

ksslng
Copy link
Contributor

@ksslng ksslng commented Jan 29, 2020

Reference Issues/PRs

Part of #11000.

What does this implement/fix? Explain your changes.

It will make testing and implementing of #11000 easier. Also added a new tag preserve_32bit_type, which skips the test if false.

Any other comments?

Verifying that values and results are similar as without conversion is needed to be added.
Also it's part of Paris sprint.

@rth rth changed the title [MRG] Add test and tag whether Estimator keeps float32 as dtype [MRG] Add common test and estimator tag for preserving float32 dtype in transformers Jan 29, 2020
Copy link
Member

@rth rth left a comment

Choose a reason for hiding this comment

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

Thanks! Overall sounds reasonable to me. I'm not sure it we should keep the default preserves_32bit_dtype as False or True, but I think there should be no expectation for transformers to pass this test by default.

@@ -1339,6 +1343,35 @@ def check_estimators_dtypes(name, estimator_orig):
getattr(estimator, method)(X_train)


def check_estimators_preserve_dtypes(name, estimator_orig):

if not _safe_tags(estimator_orig, 'preserves_32bit_dtype'):
Copy link
Member

Choose a reason for hiding this comment

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

Usually we don't implement in this way. We avoid to call the function if the tag is false. see above.

Copy link
Member

Choose a reason for hiding this comment

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

Here it was done inside, so that we would have a list of estimators that fail and that needs fixing, as a temporary approach.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I moved it now, as one needs to change the default value of the tag anyways to true to check if an estimator actually fails the test and is not just is skipped.
And after the meta issue is resolved one doesn't need to change anything.

@@ -197,6 +198,9 @@ def _yield_transformer_checks(name, transformer):
yield check_transformer_data_not_an_array
# these don't actually fit the data, so don't raise errors
yield check_transformer_general
# it's not important to preserve types with Clustering
if not isinstance(transformer, ClusterMixin):
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if not isinstance(transformer, ClusterMixin):
if (not isinstance(transformer, ClusterMixin) and
_safe_tags(transformer, "preserves_32bit_dtype")):

assert X_trans.dtype == dtype_out, \
('Estimator transform dtype: {} - orginal/expected dtype: {}'
.format(X_trans.dtype, dtype_out.__name__))

Copy link
Member

Choose a reason for hiding this comment

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

In the end maybe we should add the check comparing transforms in this PR, say using,

   assert_allclose(X_trans_32, X_trans_64, rtol=1e-2)

with a high enough tolerance. Before we start modifying other estimators to pass this.

Copy link
Member

Choose a reason for hiding this comment

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

why such a high tolerance ?

Copy link
Member

Choose a reason for hiding this comment

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

OK, maybe that was too pessimistic. Maybve rtol=1e-4 then, what value would be good you think?

Copy link
Member

Choose a reason for hiding this comment

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

I really don't know :/ Ideally it would be around 1e-6, but it's not realistic.
The output precision depends of the algorithm and we probably can't expect machine precision for all algorithms even with the best possible implementation.
I put 1e-5 in the tests of our wrappers of scipy blas but I'm fine with 1e-4

@@ -1339,6 +1344,31 @@ def check_estimators_dtypes(name, estimator_orig):
getattr(estimator, method)(X_train)


def check_estimators_preserve_dtypes(name, estimator_orig):

Copy link
Member

Choose a reason for hiding this comment

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

Please skip this test for all estimators that currently fail,

if name in [...]:
   raise SkipTest('Known failure to preserve dtypes')

so we can merge this.

The XFAIL support from #16306 could be used once that PR is merged.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@rth
Copy link
Member

rth commented Jan 30, 2020

Also shouldn't more estimators have the preserves_32bit_dtype=True flag? Say StandardScaler should preserve dtype but currently doesn't have this flag.

@glemaitre glemaitre self-assigned this Feb 13, 2020
@@ -521,6 +521,9 @@ poor_score (default=``False``)
are based on current estimators in sklearn and might be replaced by
something more systematic.

preserves_32bit_dtype (default=``False``)
Copy link
Member

Choose a reason for hiding this comment

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

Actually, the common test is a bit broader than only preserving 32 bits. I think that we should go with the tag preserves_dtype which should be an array of dtype which will be preserved. By default, it will be only 64 bits

so in short

Suggested change
preserves_32bit_dtype (default=``False``)
preserves_dtype (default=``['float64']``)

and one could give ['float64', 'float'32', 'float16']

Copy link
Contributor Author

Choose a reason for hiding this comment

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

great idea!

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.

@ksslng Would you be able to finish the PR or shall I take over?


for dtype_in, dtype_out in [(np.float32, np.float32),
(np.float64, np.float64),
(np.float16, np.float64)]:
Copy link
Member

Choose a reason for hiding this comment

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

StandardScaler will not pass this test because it preserving even 16 bits without casting which is fine.

Basically, for dtype in the preserves_dtype list, we should ensure that the dtype is the same, otherwise, it should be casted to 64 bits.

# FIXME: should we check that the dtype of some attributes are the
# same than dtype and check that the value of attributes
# between 32bit and 64bit are close
assert X_trans.dtype == dtype_out, \
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
assert X_trans.dtype == dtype_out, \
assert X_trans.dtype.name == dtype_out, \

Copy link
Member

Choose a reason for hiding this comment

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

This will be the changes with we use a string in the list of preserves_dtype

for dtype_in, dtype_out in [(np.float32, np.float32),
(np.float64, np.float64),
(np.float16, np.float64)]:
X_cast = X.copy().astype(dtype_in)
Copy link
Member

Choose a reason for hiding this comment

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

This will work directly with string name of a dtype.

@ksslng
Copy link
Contributor Author

ksslng commented Feb 14, 2020

I'll have time this weekend and I have a couple of changes locally and not pushed yet.

I also added the test to compare the float32 and float64 output of transform, should I leave it in there, or make a different pr out of it? As the question, is if one want to actually guarantee it?

@glemaitre
Copy link
Member

I'll have time this weekend and I have a couple of changes locally and not pushed yet.

great

I also added the test to compare the float32 and float64 output of transform

It can be added here and we can check when is it failing as well

@ksslng
Copy link
Contributor Author

ksslng commented Feb 17, 2020

So a couple of things to add:

  • I don't test MissingIndicator as it returns bool
  • I keep the Cross Decomposition methods, even though they return x_scores (I'm not sure how much sense it makes though)
  • MetaEstimatorMixIn is not useful as some of them the required parameter('estimator') is removed (I guess it is replaced 'base_estimator', but it is not specified either), also I didn't find any code that uses it. Should I open another issues for that? I think it would make sense to create a specifc test for the MetaEstimators.

So to the transformers that fails.
These are the ones that have a dtype mismatch:

BernoulliRBM()
CCA()
DictionaryLearning()
FactorAnalysis()
FastICA()
GaussianRandomProjection()
IncrementalPCA()
Isomap()
KBinsDiscretizer()
KNeighborsTransformer()
LatentDirichletAllocation()
LinearDiscriminantAnalysis()
LocallyLinearEmbedding()
MiniBatchDictionaryLearning()
MiniBatchSparsePCA()
NMF()
NeighborhoodComponentsAnalysis()
PLSCanonical()
PLSRegression()
PLSSVD()
RBFSampler()
RadiusNeighborsTransformer()
RandomTreesEmbedding()
RobustScaler()PASSED
SkewedChi2Sampler()
SparsePCA()
SparseRandomProjection()

These are the ones that return not close enough transformations:


FastICA()
IncrementalPCA()
KernelPCA()
MiniBatchSparsePCA()
Nystroem()
PCA()
PowerTransformer()
SkewedChi2Sampler()

@cmarmo
Copy link
Contributor

cmarmo commented Mar 28, 2020

@ksslng, it would be great if you could find some time to sync your PR with upstream and check build failures. Then, maybe reviewers will show up again... :)
Thanks for your work!

@rth rth added this to the 0.24 milestone Jun 23, 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.

5 participants