-
Notifications
You must be signed in to change notification settings - Fork 0
FIX Make eignvectors consistent #1
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
|
|
||
| @pytest.mark.parametrize("add_noise", [True, False]) | ||
| @pytest.mark.parametrize("seed", range(2)) | ||
| def test_fastica_simple_different_solvers(add_noise, seed): |
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.
This is a new test to check for consistency based on what you had.
| outs[solver] = sources | ||
| assert ica.components_.shape == (2, 2) | ||
| assert sources.shape == (1000, 2) | ||
| _, _, sources_fun = fastica(m.T, fun=nl, algorithm=algo, random_state=seed) |
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.
This reverts the tests to what they were on upstream/main.
| d, u = d[sort_indices], u[sort_indices] | ||
| # Resize and reorder to match svd | ||
| u = u[::-1, : min(X.shape) : -1] | ||
| d, u = d[sort_indices], u[:, sort_indices] |
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.
Based on eigh docs, the eigenvectors has shape (M, N) where N is the number of eigenvalues. Thus, I think the sorting should be done over axis=1.
|
Nevermind I think this works. |
|
Here is a code snippet to demonstrate: from scipy import linalg
import numpy as np
from numpy.random import default_rng
from numpy.testing import assert_allclose
def my_svd(X):
u, d = linalg.svd(X, full_matrices=False)[:2]
u *= np.sign(u[0])
return u, d
def my_eigh(X):
d, u = linalg.eigh(X.T.dot(X))
sort_indicies = np.argsort(d)[::-1]
d = d[sort_indicies]
u = u[:, sort_indicies]
u *= np.sign(u[0])
return u, np.sqrt(d)
rng = default_rng()
for i in range(10):
m, n = 9, 6
X = rng.standard_normal((m, n))
u_svd, d_svd = my_svd(X.T)
u_eigen, d_eigen = my_eigh(X)
assert_allclose(d_svd, d_eigen)
assert_allclose(u_svd, u_eigen) |
|
Although, we can use the align the eigenvectors based on the "maximum": from scipy import linalg
import numpy as np
from numpy.random import default_rng
from numpy.testing import assert_allclose
def my_svd(X):
u, d = linalg.svd(X, full_matrices=False)[:2]
max_abs_cols = np.argmax(np.abs(u), axis=0)
signs = np.sign(u[max_abs_cols, range(u.shape[1])])
u *= signs
return u, d
def my_eigh(X):
d, u = linalg.eigh(X.T.dot(X))
sort_indicies = np.argsort(d)[::-1]
d = d[sort_indicies]
u = u[:, sort_indicies]
max_abs_cols = np.argmax(np.abs(u), axis=0)
signs = np.sign(u[max_abs_cols, range(u.shape[1])])
u *= signs
return u, np.sqrt(d)
rng = default_rng()
for i in range(10):
m, n = 9, 6
X = rng.standard_normal((m, n))
u_svd, d_svd = my_svd(X.T)
u_eigen, d_eigen = my_eigh(X)
assert_allclose(d_svd, d_eigen)
assert_allclose(u_svd, u_eigen) |
| u, d = linalg.svd(XT, full_matrices=False, check_finite=False)[:2] | ||
|
|
||
| # Give consistent eigenvectors for both svd solvers | ||
| u *= np.sign(u[0]) |
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.
The alternative solution is:
max_abs_cols = np.argmax(np.abs(u), axis=0)
signs = np.sign(u[max_abs_cols, range(u.shape[1])])
u *= signsIf we want to be strictly the same as svd_flip.
Here is a quick fix to make the eigenvectors consistent.