-
Notifications
You must be signed in to change notification settings - Fork 232
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
Fix covariance initialization when matrix is not invertible #277
Merged
Merged
Changes from 7 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
3ef4f9c
Fix covariance init when matrix is not invertible
grudloff 2c1e308
replaced import scipy for only required functions
grudloff eff07d4
Change inv for pseudo-inv on custom matrix init
grudloff bd690c8
Change from EVD to SVD
grudloff 0982559
Roll back to EVD and pseudo inverse of EVD
grudloff baad5a7
Fix non-ASCII char
grudloff 80ab922
rephrasing warnings
grudloff 66e84d8
added tests
grudloff 5787734
more rephrasing
grudloff 9e7ed19
fix test
grudloff f6dd83a
add test
grudloff 1e4664f
fixes & adds singular pinv test fron eig
grudloff 5abab24
fix tolerance of assert
grudloff 6b3b02e
fix tolerance of assert
grudloff a5c476b
fix tolerance of assert
grudloff f2bc3e2
fix random seed
grudloff d1d7ec4
isolate random seed setting
grudloff File filter
Filter by extension
Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
import numpy as np | ||
import scipy | ||
import six | ||
from numpy.linalg import LinAlgError | ||
from sklearn.datasets import make_spd_matrix | ||
|
@@ -8,9 +7,10 @@ | |
from sklearn.utils.validation import check_X_y, check_random_state | ||
from .exceptions import PreprocessorError, NonPSDError | ||
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis | ||
from scipy.linalg import pinvh | ||
from scipy.linalg import pinvh, eigh | ||
import sys | ||
import time | ||
import warnings | ||
|
||
# hack around lack of axis kwarg in older numpy versions | ||
try: | ||
|
@@ -678,17 +678,20 @@ def _initialize_metric_mahalanobis(input, init='identity', random_state=None, | |
|
||
random_state = check_random_state(random_state) | ||
M = init | ||
if isinstance(init, np.ndarray): | ||
s, u = scipy.linalg.eigh(init) | ||
init_is_definite = _check_sdp_from_eigen(s) | ||
if isinstance(M, np.ndarray): | ||
w, V = eigh(M, check_finite=False) | ||
init_is_definite = _check_sdp_from_eigen(w) | ||
if strict_pd and not init_is_definite: | ||
raise LinAlgError("You should provide a strictly positive definite " | ||
"matrix as `{}`. This one is not definite. Try another" | ||
" {}, or an algorithm that does not " | ||
"require the {} to be strictly positive definite." | ||
.format(*((matrix_name,) * 3))) | ||
elif return_inverse and not init_is_definite: | ||
warnings.warn('The initialization matrix is not invertible: ' | ||
'using the pseudo-inverse instead.') | ||
if return_inverse: | ||
M_inv = np.dot(u / s, u.T) | ||
M_inv = _pseudo_inverse_from_eig(w, V) | ||
return M, M_inv | ||
else: | ||
return M | ||
|
@@ -707,15 +710,23 @@ def _initialize_metric_mahalanobis(input, init='identity', random_state=None, | |
X = input | ||
# atleast2d is necessary to deal with scalar covariance matrices | ||
M_inv = np.atleast_2d(np.cov(X, rowvar=False)) | ||
s, u = scipy.linalg.eigh(M_inv) | ||
cov_is_definite = _check_sdp_from_eigen(s) | ||
w, V = eigh(M_inv, check_finite=False) | ||
cov_is_definite = _check_sdp_from_eigen(w) | ||
if strict_pd and not cov_is_definite: | ||
raise LinAlgError("Unable to get a true inverse of the covariance " | ||
"matrix since it is not definite. Try another " | ||
"`{}`, or an algorithm that does not " | ||
"require the `{}` to be strictly positive definite." | ||
.format(*((matrix_name,) * 2))) | ||
M = np.dot(u / s, u.T) | ||
elif not cov_is_definite: | ||
warnings.warn('The initialization matrix is not invertible: ' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. initialization matrix --> covariance matrix |
||
'using the pseudo-inverse instead.' | ||
'To make the inverse covariance matrix invertible' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the inverse covariance --> the covariance |
||
' you can remove any linearly dependent features and/or ' | ||
'reduce the dimensionality of your input, ' | ||
'for instance using `sklearn.decomposition.PCA` as a ' | ||
'preprocessing step.') | ||
M = _pseudo_inverse_from_eig(w, V) | ||
if return_inverse: | ||
return M, M_inv | ||
else: | ||
|
@@ -742,3 +753,36 @@ def _check_n_components(n_features, n_components): | |
if 0 < n_components <= n_features: | ||
return n_components | ||
raise ValueError('Invalid n_components, must be in [1, %d]' % n_features) | ||
|
||
|
||
def _pseudo_inverse_from_eig(w, V, tol=None): | ||
"""Compute the (Moore-Penrose) pseudo-inverse of the EVD of a symetric | ||
matrix. | ||
|
||
Parameters | ||
---------- | ||
w : (..., M) ndarray | ||
The eigenvalues in ascending order, each repeated according to | ||
its multiplicity. | ||
|
||
v : {(..., M, M) ndarray, (..., M, M) matrix} | ||
The column ``v[:, i]`` is the normalized eigenvector corresponding | ||
to the eigenvalue ``w[i]``. Will return a matrix object if `a` is | ||
a matrix object. | ||
|
||
tol : positive `float`, optional | ||
Absolute eigenvalues below tol are considered zero. | ||
|
||
Returns | ||
------- | ||
output : (..., M, N) array_like | ||
The pseudo-inverse given by the EVD. | ||
""" | ||
if tol is None: | ||
tol = np.amax(w) * np.max(w.shape) * np.finfo(w.dtype).eps | ||
# discard small eigenvalues and invert the rest | ||
large = np.abs(w) > tol | ||
w = np.divide(1, w, where=large, out=w) | ||
w[~large] = 0 | ||
|
||
return np.dot(V * w, np.conjugate(V).T) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
why did you turn off
check_finite
? I think it is better to keep itThere 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.
check_array()
does it previously, so it shouldn't be necessary to do it again.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.
Indeed!