-
-
Notifications
You must be signed in to change notification settings - Fork 25.9k
EHN Improve variable names in KernelPCA
#19908
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
Merged
glemaitre
merged 24 commits into
scikit-learn:main
from
kstoneriv3:improve/kernel_pca_variable_names
Jul 23, 2021
Merged
Changes from 6 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
5c0d2d6
change variable names
86a6380
change `fit_inverse_tranform` to `enable_inverse_tranform`
01c6f97
fix lines with >=80 chars
4748f9b
fix intend for pylint
8779ca5
fix pylint again
d7b4cd1
rename `lambda_` to `eigenvalues_`
ec5cc56
undo renaming "fit_inverse_transform" to "enable_inverse_transform"
5a895b8
add change log
fbf567b
support deprecated attributes
5a3b5aa
add tests for deprecation of lambdas_ and alphas_
3fdd3a7
fix linter
cea05f2
remove unnecessary changes
141b941
Apply suggestions from code review
kstoneriv3 fe0d284
Merge branch 'main' into improve/kernel_pca_variable_names
faf7ac9
fix linter
e1ca8e0
fix linter
ef1df18
Merge branch 'main' into improve/kernel_pca_variable_names
03f7c81
Update sklearn/decomposition/_kernel_pca.py
kstoneriv3 d4ef4a7
fix linter
aa34a6a
Merge remote-tracking branch 'origin/main' into pr/kstoneriv3/19908
glemaitre 93e8f5c
make a mistake in merge
glemaitre 502c56b
fix tests
glemaitre d87e8b4
do not use alphas_ and lambdas_ in tesst
glemaitre 4a4faec
fix docstring
glemaitre File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 |
---|---|---|
|
@@ -53,9 +53,9 @@ class KernelPCA(TransformerMixin, BaseEstimator): | |
|
||
alpha : float, default=1.0 | ||
Hyperparameter of the ridge regression that learns the | ||
inverse transform (when fit_inverse_transform=True). | ||
inverse transform (when enable_inverse_transform=True). | ||
|
||
fit_inverse_transform : bool, default=False | ||
enable_inverse_transform : bool, default=False | ||
Learn the inverse transform for non-precomputed kernels. | ||
(i.e. learn to find the pre-image of a point) | ||
|
||
|
@@ -103,22 +103,22 @@ class KernelPCA(TransformerMixin, BaseEstimator): | |
|
||
Attributes | ||
---------- | ||
lambdas_ : ndarray of shape (n_components,) | ||
eigenvalues_ : ndarray of shape (n_components,) | ||
Eigenvalues of the centered kernel matrix in decreasing order. | ||
If `n_components` and `remove_zero_eig` are not set, | ||
then all values are stored. | ||
|
||
alphas_ : ndarray of shape (n_samples, n_components) | ||
eigenvectors_ : ndarray of shape (n_samples, n_components) | ||
Eigenvectors of the centered kernel matrix. If `n_components` and | ||
`remove_zero_eig` are not set, then all components are stored. | ||
|
||
dual_coef_ : ndarray of shape (n_samples, n_features) | ||
Inverse transform matrix. Only available when | ||
``fit_inverse_transform`` is True. | ||
``enable_inverse_transform`` is True. | ||
|
||
X_transformed_fit_ : ndarray of shape (n_samples, n_components) | ||
Projection of the fitted data on the kernel principal components. | ||
Only available when ``fit_inverse_transform`` is True. | ||
Only available when ``enable_inverse_transform`` is True. | ||
|
||
X_fit_ : ndarray of shape (n_samples, n_features) | ||
The data used to fit the model. If `copy_X=False`, then `X_fit_` is | ||
|
@@ -145,20 +145,21 @@ class KernelPCA(TransformerMixin, BaseEstimator): | |
@_deprecate_positional_args | ||
def __init__(self, n_components=None, *, kernel="linear", | ||
gamma=None, degree=3, coef0=1, kernel_params=None, | ||
alpha=1.0, fit_inverse_transform=False, eigen_solver='auto', | ||
tol=0, max_iter=None, remove_zero_eig=False, | ||
random_state=None, copy_X=True, n_jobs=None): | ||
if fit_inverse_transform and kernel == 'precomputed': | ||
alpha=1.0, enable_inverse_transform=False, | ||
eigen_solver='auto', tol=0, max_iter=None, | ||
remove_zero_eig=False, random_state=None, | ||
copy_X=True, n_jobs=None): | ||
if enable_inverse_transform and kernel == 'precomputed': | ||
raise ValueError( | ||
"Cannot fit_inverse_transform with a precomputed kernel.") | ||
"Cannot enable_inverse_transform with a precomputed kernel.") | ||
self.n_components = n_components | ||
self.kernel = kernel | ||
self.kernel_params = kernel_params | ||
self.gamma = gamma | ||
self.degree = degree | ||
self.coef0 = coef0 | ||
self.alpha = alpha | ||
self.fit_inverse_transform = fit_inverse_transform | ||
self.enable_inverse_transform = enable_inverse_transform | ||
self.eigen_solver = eigen_solver | ||
self.remove_zero_eig = remove_zero_eig | ||
self.tol = tol | ||
|
@@ -206,33 +207,33 @@ def _fit_transform(self, K): | |
eigen_solver = self.eigen_solver | ||
|
||
if eigen_solver == 'dense': | ||
self.lambdas_, self.alphas_ = linalg.eigh( | ||
self.eigenvalues_, self.eigenvectors_ = linalg.eigh( | ||
K, eigvals=(K.shape[0] - n_components, K.shape[0] - 1)) | ||
elif eigen_solver == 'arpack': | ||
v0 = _init_arpack_v0(K.shape[0], self.random_state) | ||
self.lambdas_, self.alphas_ = eigsh(K, n_components, | ||
self.eigenvalues_, self.eigenvectors_ = eigsh(K, n_components, | ||
which="LA", | ||
tol=self.tol, | ||
maxiter=self.max_iter, | ||
v0=v0) | ||
|
||
# make sure that the eigenvalues are ok and fix numerical issues | ||
self.lambdas_ = _check_psd_eigenvalues(self.lambdas_, | ||
self.eigenvalues_ = _check_psd_eigenvalues(self.eigenvalues_, | ||
enable_warnings=False) | ||
kstoneriv3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
# flip eigenvectors' sign to enforce deterministic output | ||
self.alphas_, _ = svd_flip(self.alphas_, | ||
np.zeros_like(self.alphas_).T) | ||
self.eigenvectors_, _ = svd_flip(self.eigenvectors_, | ||
np.zeros_like(self.eigenvectors_).T) | ||
|
||
# sort eigenvectors in descending order | ||
indices = self.lambdas_.argsort()[::-1] | ||
self.lambdas_ = self.lambdas_[indices] | ||
self.alphas_ = self.alphas_[:, indices] | ||
indices = self.eigenvalues_.argsort()[::-1] | ||
self.eigenvalues_ = self.eigenvalues_[indices] | ||
self.eigenvectors_ = self.eigenvectors_[:, indices] | ||
|
||
# remove eigenvectors with a zero eigenvalue (null space) if required | ||
if self.remove_zero_eig or self.n_components is None: | ||
self.alphas_ = self.alphas_[:, self.lambdas_ > 0] | ||
self.lambdas_ = self.lambdas_[self.lambdas_ > 0] | ||
self.eigenvectors_ = self.eigenvectors_[:, self.eigenvalues_ > 0] | ||
self.eigenvalues_ = self.eigenvalues_[self.eigenvalues_ > 0] | ||
|
||
# Maintenance note on Eigenvectors normalization | ||
# ---------------------------------------------- | ||
|
@@ -243,12 +244,12 @@ def _fit_transform(self, K): | |
# if u is an eigenvector of Phi(X)Phi(X)' | ||
# then Phi(X)'u is an eigenvector of Phi(X)'Phi(X) | ||
# | ||
# At this stage our self.alphas_ (the v) have norm 1, we need to scale | ||
# them so that eigenvectors in kernel feature space (the u) have norm=1 | ||
# instead | ||
# At this stage our self.eigenvectors_ (the v) have norm 1, we need to | ||
# scale them so that eigenvectors in kernel feature space (the u) have | ||
# norm=1 instead | ||
# | ||
# We COULD scale them here: | ||
# self.alphas_ = self.alphas_ / np.sqrt(self.lambdas_) | ||
# self.eigenvectors_ = self.eigenvectors_ / np.sqrt(self.eigenvalues_) | ||
# | ||
# But choose to perform that LATER when needed, in `fit()` and in | ||
# `transform()`. | ||
|
@@ -285,9 +286,9 @@ def fit(self, X, y=None): | |
K = self._get_kernel(X) | ||
self._fit_transform(K) | ||
|
||
if self.fit_inverse_transform: | ||
if self.enable_inverse_transform: | ||
# no need to use the kernel to transform X, use shortcut expression | ||
X_transformed = self.alphas_ * np.sqrt(self.lambdas_) | ||
X_transformed = self.eigenvectors_ * np.sqrt(self.eigenvalues_) | ||
|
||
self._fit_inverse_transform(X_transformed, X) | ||
|
||
|
@@ -310,9 +311,9 @@ def fit_transform(self, X, y=None, **params): | |
self.fit(X, **params) | ||
|
||
# no need to use the kernel to transform X, use shortcut expression | ||
X_transformed = self.alphas_ * np.sqrt(self.lambdas_) | ||
X_transformed = self.eigenvectors_ * np.sqrt(self.eigenvalues_) | ||
|
||
if self.fit_inverse_transform: | ||
if self.enable_inverse_transform: | ||
self._fit_inverse_transform(X_transformed, X) | ||
|
||
return X_transformed | ||
|
@@ -335,10 +336,10 @@ def transform(self, X): | |
K = self._centerer.transform(self._get_kernel(X, self.X_fit_)) | ||
|
||
# scale eigenvectors (properly account for null-space for dot product) | ||
non_zeros = np.flatnonzero(self.lambdas_) | ||
scaled_alphas = np.zeros_like(self.alphas_) | ||
scaled_alphas[:, non_zeros] = (self.alphas_[:, non_zeros] | ||
/ np.sqrt(self.lambdas_[non_zeros])) | ||
non_zeros = np.flatnonzero(self.eigenvalues_) | ||
scaled_alphas = np.zeros_like(self.eigenvectors_) | ||
scaled_alphas[:, non_zeros] = (self.eigenvectors_[:, non_zeros] | ||
/ np.sqrt(self.eigenvalues_[non_zeros])) | ||
|
||
# Project with a scalar product between K and the scaled eigenvectors | ||
return np.dot(K, scaled_alphas) | ||
kstoneriv3 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
@@ -358,10 +359,11 @@ def inverse_transform(self, X): | |
---------- | ||
"Learning to Find Pre-Images", G BakIr et al, 2004. | ||
""" | ||
if not self.fit_inverse_transform: | ||
raise NotFittedError("The fit_inverse_transform parameter was not" | ||
" set to True when instantiating and hence " | ||
"the inverse transform is not available.") | ||
if not self.enable_inverse_transform: | ||
raise NotFittedError("The enable_inverse_transform parameter was" | ||
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. As the codecov bot discovered, the existing tests for not trigger this branch of the code. Could you please add a quick test for this (using |
||
" not set to True when instantiating and" | ||
" hence the inverse transform is not" | ||
" available.") | ||
|
||
K = self._get_kernel(X, self.X_transformed_fit_) | ||
n_samples = self.X_transformed_fit_.shape[0] | ||
|
This file contains hidden or 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
This file contains hidden or 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
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.
Uh oh!
There was an error while loading. Please reload this page.