Skip to content

[MRG] make transformer_from_metric more robust #191

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

Conversation

wdevazelhes
Copy link
Member

@wdevazelhes wdevazelhes commented Apr 11, 2019

Fixes #175

This PR fixes the problem we had with transformer_from_metric, by checking if the matrix is diagonal depending on the relative gap between the maximum absolute value of the outer diagonal coeffs, and the minimum absolute value of the diagonal coefficients, rather than what was done before.

It is also more robust for detecting whether to use Cholesky or the eigendecomposition: instead of computing the determinant to check if the matrix is not definite, it tries to do Cholesky and if an error is returned it does the eigendecomposition. Indeed, maybe the determinant can be not null if there is a very small eigenvalue (v=1e-5) (that should be considered as null), but a lot of big eigenvalues (see one example in the test)

It also raises an error message when the input is not symmetric and a warning when switching from the Cholesky decomposition to the eigendecompostion if the Cholesky decomposition is not doable.

TODO:

  • to make a good test, check the value of v for which cholesky will fail, and from it build the example where the determinant is not "np.close" to zero

@wdevazelhes wdevazelhes changed the title [WIP] make transformer_from_metric more robust [MRG] make transformer_from_metric more robust Apr 11, 2019
@wdevazelhes
Copy link
Member Author

wdevazelhes commented Apr 11, 2019

Now that I think about it, maybe the warning is not a good thing to throw ? Maybe it would be better a verbose flag to decide whether to print the warning (which would not be anymore a warning but a regular print) ? What do you think ? (And the verbose attribute of the estimator that uses this would be given as an argument to this function)

Copy link
Member

@bellet bellet left a comment

Choose a reason for hiding this comment

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

As discussed I think it would be better to only allow symmetric PSD matrices as input and throw an error if it is not (but allowing for some tolerance for numerical errors where some eigenvalues are very close to zero but negative). Also, for the diagonal case, we can go for the safe solution of simply checking if the off diagonal terms are exactly zero (which will cover the most concrete case where the matrix is learned to be diagonal in the first place as in MMC with diagonal=True) and treat the matrix as non-diagonal otherwise.

@wdevazelhes
Copy link
Member Author

I agree, I just pushed a more recent version that takes into account these remarks

Copy link
Member

@bellet bellet left a comment

Choose a reason for hiding this comment

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

Some nitpicks.

Also the docstring seems overly detailed. I think "Computes the transformation matrix from the Mahalanobis matrix, i.e. the matrix L such that metric=L.T.dot(L)." is enough, the rest could be put as comments inside the function to explain what is being done?


tol : float, optional
Negative eigenvalues above - tol are considered zero. If
tol is None, and w are `metric`'s eigenvalues, and eps is the
Copy link
Member

Choose a reason for hiding this comment

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

"and w are metric's eigenvalues" is not needed

Copy link
Member Author

Choose a reason for hiding this comment

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

That's right, done

"""
if tol is None:
tol = w.max() * len(w) * np.finfo(w.dtype).eps
if any(w[w < 0] < - tol):
Copy link
Member

Choose a reason for hiding this comment

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

w<0 not needed

Copy link
Member

Choose a reason for hiding this comment

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

although it actually prevents that anything is done in case the provided tol is negative (which is not supposed to be the case according to the docstring but is not checked)

Copy link
Member Author

Choose a reason for hiding this comment

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

That's right, I'll let it be w < - tol and add a quick test to check that tol is positive


tol : positive float, optional
Eigenvalues of `metric` between 0 and - tol are considered zero. If tol is
None, and w are `metric`'s eigenvalues, and eps is the epsilon value for
Copy link
Member

Choose a reason for hiding this comment

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

same as above

Copy link
Member Author

Choose a reason for hiding this comment

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

How to explain how the tolerance is set here then without talking about the eigenvalues ? Though maybe an explanation talking about the highest eigenvalue could be better ? Like:

    Eigenvalues of `metric` between 0 and - tol are considered zero. If tol is
    None, and w_max is `metric`'s largest eigenvalue, and eps is the epsilon
    value for datatype of w, then tol is set to w_max * metric.shape[0] * eps.

Copy link
Member

Choose a reason for hiding this comment

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

nevermind, indeed here we need to mention eigenvalues
the suggested reformulation looks good

P = ortho_group.rvs(7, random_state=rng)
M = P.dot(D).dot(P.T)
with pytest.raises(ValueError) as raised_error:
transformer_from_metric(M)
Copy link
Member

Choose a reason for hiding this comment

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

here you could also easily test that the error is raised in the diagonal case, for instance when calling transformer_from_metric(D)

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 agree, will do

@wdevazelhes
Copy link
Member Author

Thanks for the review @bellet, I addressed all the comments, there's just this one that needs to be resolved

@bellet
Copy link
Member

bellet commented Apr 17, 2019

LGTM!

@bellet bellet merged commit 99b0322 into scikit-learn-contrib:master Apr 17, 2019
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.

transformer_from_metric can return wrong results or nan
2 participants