Uploaded Test files
This commit is contained in:
parent
f584ad9d97
commit
2e81cb7d99
16627 changed files with 2065359 additions and 102444 deletions
|
@ -0,0 +1,4 @@
|
|||
from ._pls import PLSCanonical, PLSRegression, PLSSVD
|
||||
from ._cca import CCA
|
||||
|
||||
__all__ = ['PLSCanonical', 'PLSRegression', 'PLSSVD', 'CCA']
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
112
venv/Lib/site-packages/sklearn/cross_decomposition/_cca.py
Normal file
112
venv/Lib/site-packages/sklearn/cross_decomposition/_cca.py
Normal file
|
@ -0,0 +1,112 @@
|
|||
from ._pls import _PLS
|
||||
from ..base import _UnstableArchMixin
|
||||
from ..utils.validation import _deprecate_positional_args
|
||||
|
||||
__all__ = ['CCA']
|
||||
|
||||
|
||||
class CCA(_UnstableArchMixin, _PLS):
|
||||
"""CCA Canonical Correlation Analysis.
|
||||
|
||||
CCA inherits from PLS with mode="B" and deflation_mode="canonical".
|
||||
|
||||
Read more in the :ref:`User Guide <cross_decomposition>`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n_components : int, (default 2).
|
||||
number of components to keep.
|
||||
|
||||
scale : boolean, (default True)
|
||||
whether to scale the data?
|
||||
|
||||
max_iter : an integer, (default 500)
|
||||
the maximum number of iterations of the NIPALS inner loop
|
||||
|
||||
tol : non-negative real, default 1e-06.
|
||||
the tolerance used in the iterative algorithm
|
||||
|
||||
copy : boolean
|
||||
Whether the deflation be done on a copy. Let the default value
|
||||
to True unless you don't care about side effects
|
||||
|
||||
Attributes
|
||||
----------
|
||||
x_weights_ : array, [p, n_components]
|
||||
X block weights vectors.
|
||||
|
||||
y_weights_ : array, [q, n_components]
|
||||
Y block weights vectors.
|
||||
|
||||
x_loadings_ : array, [p, n_components]
|
||||
X block loadings vectors.
|
||||
|
||||
y_loadings_ : array, [q, n_components]
|
||||
Y block loadings vectors.
|
||||
|
||||
x_scores_ : array, [n_samples, n_components]
|
||||
X scores.
|
||||
|
||||
y_scores_ : array, [n_samples, n_components]
|
||||
Y scores.
|
||||
|
||||
x_rotations_ : array, [p, n_components]
|
||||
X block to latents rotations.
|
||||
|
||||
y_rotations_ : array, [q, n_components]
|
||||
Y block to latents rotations.
|
||||
|
||||
coef_ : array of shape (p, q)
|
||||
The coefficients of the linear model: ``Y = X coef_ + Err``
|
||||
|
||||
n_iter_ : array-like
|
||||
Number of iterations of the NIPALS inner loop for each
|
||||
component.
|
||||
|
||||
Notes
|
||||
-----
|
||||
For each component k, find the weights u, v that maximizes
|
||||
max corr(Xk u, Yk v), such that ``|u| = |v| = 1``
|
||||
|
||||
Note that it maximizes only the correlations between the scores.
|
||||
|
||||
The residual matrix of X (Xk+1) block is obtained by the deflation on the
|
||||
current X score: x_score.
|
||||
|
||||
The residual matrix of Y (Yk+1) block is obtained by deflation on the
|
||||
current Y score.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from sklearn.cross_decomposition import CCA
|
||||
>>> X = [[0., 0., 1.], [1.,0.,0.], [2.,2.,2.], [3.,5.,4.]]
|
||||
>>> Y = [[0.1, -0.2], [0.9, 1.1], [6.2, 5.9], [11.9, 12.3]]
|
||||
>>> cca = CCA(n_components=1)
|
||||
>>> cca.fit(X, Y)
|
||||
CCA(n_components=1)
|
||||
>>> X_c, Y_c = cca.transform(X, Y)
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
Jacob A. Wegelin. A survey of Partial Least Squares (PLS) methods, with
|
||||
emphasis on the two-block case. Technical Report 371, Department of
|
||||
Statistics, University of Washington, Seattle, 2000.
|
||||
|
||||
In french but still a reference:
|
||||
Tenenhaus, M. (1998). La regression PLS: theorie et pratique. Paris:
|
||||
Editions Technic.
|
||||
|
||||
See also
|
||||
--------
|
||||
PLSCanonical
|
||||
PLSSVD
|
||||
"""
|
||||
|
||||
@_deprecate_positional_args
|
||||
def __init__(self, n_components=2, *, scale=True,
|
||||
max_iter=500, tol=1e-06, copy=True):
|
||||
super().__init__(n_components=n_components, scale=scale,
|
||||
deflation_mode="canonical", mode="B",
|
||||
norm_y_weights=True, algorithm="nipals",
|
||||
max_iter=max_iter, tol=tol, copy=copy)
|
973
venv/Lib/site-packages/sklearn/cross_decomposition/_pls.py
Normal file
973
venv/Lib/site-packages/sklearn/cross_decomposition/_pls.py
Normal file
|
@ -0,0 +1,973 @@
|
|||
"""
|
||||
The :mod:`sklearn.pls` module implements Partial Least Squares (PLS).
|
||||
"""
|
||||
|
||||
# Author: Edouard Duchesnay <edouard.duchesnay@cea.fr>
|
||||
# License: BSD 3 clause
|
||||
|
||||
import warnings
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import numpy as np
|
||||
from scipy.linalg import pinv2, svd
|
||||
from scipy.sparse.linalg import svds
|
||||
|
||||
from ..base import BaseEstimator, RegressorMixin, TransformerMixin
|
||||
from ..base import MultiOutputMixin
|
||||
from ..utils import check_array, check_consistent_length
|
||||
from ..utils.extmath import svd_flip
|
||||
from ..utils.validation import check_is_fitted, FLOAT_DTYPES
|
||||
from ..utils.validation import _deprecate_positional_args
|
||||
from ..exceptions import ConvergenceWarning
|
||||
|
||||
__all__ = ['PLSCanonical', 'PLSRegression', 'PLSSVD']
|
||||
|
||||
|
||||
def _nipals_twoblocks_inner_loop(X, Y, mode="A", max_iter=500, tol=1e-06,
|
||||
norm_y_weights=False):
|
||||
"""Inner loop of the iterative NIPALS algorithm.
|
||||
|
||||
Provides an alternative to the svd(X'Y); returns the first left and right
|
||||
singular vectors of X'Y. See PLS for the meaning of the parameters. It is
|
||||
similar to the Power method for determining the eigenvectors and
|
||||
eigenvalues of a X'Y.
|
||||
"""
|
||||
for col in Y.T:
|
||||
if np.any(np.abs(col) > np.finfo(np.double).eps):
|
||||
y_score = col.reshape(len(col), 1)
|
||||
break
|
||||
|
||||
x_weights_old = 0
|
||||
ite = 1
|
||||
X_pinv = Y_pinv = None
|
||||
eps = np.finfo(X.dtype).eps
|
||||
|
||||
if mode == "B":
|
||||
# Uses condition from scipy<1.3 in pinv2 which was changed in
|
||||
# https://github.com/scipy/scipy/pull/10067. In scipy 1.3, the
|
||||
# condition was changed to depend on the largest singular value
|
||||
X_t = X.dtype.char.lower()
|
||||
Y_t = Y.dtype.char.lower()
|
||||
factor = {'f': 1E3, 'd': 1E6}
|
||||
|
||||
cond_X = factor[X_t] * eps
|
||||
cond_Y = factor[Y_t] * eps
|
||||
|
||||
# Inner loop of the Wold algo.
|
||||
while True:
|
||||
# 1.1 Update u: the X weights
|
||||
if mode == "B":
|
||||
if X_pinv is None:
|
||||
# We use slower pinv2 (same as np.linalg.pinv) for stability
|
||||
# reasons
|
||||
X_pinv = pinv2(X, check_finite=False, cond=cond_X)
|
||||
x_weights = np.dot(X_pinv, y_score)
|
||||
else: # mode A
|
||||
# Mode A regress each X column on y_score
|
||||
x_weights = np.dot(X.T, y_score) / np.dot(y_score.T, y_score)
|
||||
# If y_score only has zeros x_weights will only have zeros. In
|
||||
# this case add an epsilon to converge to a more acceptable
|
||||
# solution
|
||||
if np.dot(x_weights.T, x_weights) < eps:
|
||||
x_weights += eps
|
||||
# 1.2 Normalize u
|
||||
x_weights /= np.sqrt(np.dot(x_weights.T, x_weights)) + eps
|
||||
# 1.3 Update x_score: the X latent scores
|
||||
x_score = np.dot(X, x_weights)
|
||||
# 2.1 Update y_weights
|
||||
if mode == "B":
|
||||
if Y_pinv is None:
|
||||
# compute once pinv(Y)
|
||||
Y_pinv = pinv2(Y, check_finite=False, cond=cond_Y)
|
||||
y_weights = np.dot(Y_pinv, x_score)
|
||||
else:
|
||||
# Mode A regress each Y column on x_score
|
||||
y_weights = np.dot(Y.T, x_score) / np.dot(x_score.T, x_score)
|
||||
# 2.2 Normalize y_weights
|
||||
if norm_y_weights:
|
||||
y_weights /= np.sqrt(np.dot(y_weights.T, y_weights)) + eps
|
||||
# 2.3 Update y_score: the Y latent scores
|
||||
y_score = np.dot(Y, y_weights) / (np.dot(y_weights.T, y_weights) + eps)
|
||||
# y_score = np.dot(Y, y_weights) / np.dot(y_score.T, y_score) ## BUG
|
||||
x_weights_diff = x_weights - x_weights_old
|
||||
if np.dot(x_weights_diff.T, x_weights_diff) < tol or Y.shape[1] == 1:
|
||||
break
|
||||
if ite == max_iter:
|
||||
warnings.warn('Maximum number of iterations reached',
|
||||
ConvergenceWarning)
|
||||
break
|
||||
x_weights_old = x_weights
|
||||
ite += 1
|
||||
return x_weights, y_weights, ite
|
||||
|
||||
|
||||
def _svd_cross_product(X, Y):
|
||||
C = np.dot(X.T, Y)
|
||||
U, s, Vh = svd(C, full_matrices=False)
|
||||
u = U[:, [0]]
|
||||
v = Vh.T[:, [0]]
|
||||
return u, v
|
||||
|
||||
|
||||
def _center_scale_xy(X, Y, scale=True):
|
||||
""" Center X, Y and scale if the scale parameter==True
|
||||
|
||||
Returns
|
||||
-------
|
||||
X, Y, x_mean, y_mean, x_std, y_std
|
||||
"""
|
||||
# center
|
||||
x_mean = X.mean(axis=0)
|
||||
X -= x_mean
|
||||
y_mean = Y.mean(axis=0)
|
||||
Y -= y_mean
|
||||
# scale
|
||||
if scale:
|
||||
x_std = X.std(axis=0, ddof=1)
|
||||
x_std[x_std == 0.0] = 1.0
|
||||
X /= x_std
|
||||
y_std = Y.std(axis=0, ddof=1)
|
||||
y_std[y_std == 0.0] = 1.0
|
||||
Y /= y_std
|
||||
else:
|
||||
x_std = np.ones(X.shape[1])
|
||||
y_std = np.ones(Y.shape[1])
|
||||
return X, Y, x_mean, y_mean, x_std, y_std
|
||||
|
||||
|
||||
class _PLS(TransformerMixin, RegressorMixin, MultiOutputMixin, BaseEstimator,
|
||||
metaclass=ABCMeta):
|
||||
"""Partial Least Squares (PLS)
|
||||
|
||||
This class implements the generic PLS algorithm, constructors' parameters
|
||||
allow to obtain a specific implementation such as:
|
||||
|
||||
- PLS2 regression, i.e., PLS 2 blocks, mode A, with asymmetric deflation
|
||||
and unnormalized y weights such as defined by [Tenenhaus 1998] p. 132.
|
||||
With univariate response it implements PLS1.
|
||||
|
||||
- PLS canonical, i.e., PLS 2 blocks, mode A, with symmetric deflation and
|
||||
normalized y weights such as defined by [Tenenhaus 1998] (p. 132) and
|
||||
[Wegelin et al. 2000]. This parametrization implements the original Wold
|
||||
algorithm.
|
||||
|
||||
We use the terminology defined by [Wegelin et al. 2000].
|
||||
This implementation uses the PLS Wold 2 blocks algorithm based on two
|
||||
nested loops:
|
||||
(i) The outer loop iterate over components.
|
||||
(ii) The inner loop estimates the weights vectors. This can be done
|
||||
with two algo. (a) the inner loop of the original NIPALS algo. or (b) a
|
||||
SVD on residuals cross-covariance matrices.
|
||||
|
||||
n_components : int, number of components to keep. (default 2).
|
||||
|
||||
scale : boolean, scale data? (default True)
|
||||
|
||||
deflation_mode : str, "canonical" or "regression". See notes.
|
||||
|
||||
mode : "A" classical PLS and "B" CCA. See notes.
|
||||
|
||||
norm_y_weights : boolean, normalize Y weights to one? (default False)
|
||||
|
||||
algorithm : string, "nipals" or "svd"
|
||||
The algorithm used to estimate the weights. It will be called
|
||||
n_components times, i.e. once for each iteration of the outer loop.
|
||||
|
||||
max_iter : int (default 500)
|
||||
The maximum number of iterations
|
||||
of the NIPALS inner loop (used only if algorithm="nipals")
|
||||
|
||||
tol : non-negative real, default 1e-06
|
||||
The tolerance used in the iterative algorithm.
|
||||
|
||||
copy : boolean, default True
|
||||
Whether the deflation should be done on a copy. Let the default
|
||||
value to True unless you don't care about side effects.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
x_weights_ : array, [p, n_components]
|
||||
X block weights vectors.
|
||||
|
||||
y_weights_ : array, [q, n_components]
|
||||
Y block weights vectors.
|
||||
|
||||
x_loadings_ : array, [p, n_components]
|
||||
X block loadings vectors.
|
||||
|
||||
y_loadings_ : array, [q, n_components]
|
||||
Y block loadings vectors.
|
||||
|
||||
x_scores_ : array, [n_samples, n_components]
|
||||
X scores.
|
||||
|
||||
y_scores_ : array, [n_samples, n_components]
|
||||
Y scores.
|
||||
|
||||
x_rotations_ : array, [p, n_components]
|
||||
X block to latents rotations.
|
||||
|
||||
y_rotations_ : array, [q, n_components]
|
||||
Y block to latents rotations.
|
||||
|
||||
x_mean_ : array, [p]
|
||||
X mean for each predictor.
|
||||
|
||||
y_mean_ : array, [q]
|
||||
Y mean for each response variable.
|
||||
|
||||
x_std_ : array, [p]
|
||||
X standard deviation for each predictor.
|
||||
|
||||
y_std_ : array, [q]
|
||||
Y standard deviation for each response variable.
|
||||
|
||||
coef_ : array, [p, q]
|
||||
The coefficients of the linear model: ``Y = X coef_ + Err``
|
||||
|
||||
n_iter_ : array-like
|
||||
Number of iterations of the NIPALS inner loop for each
|
||||
component. Not useful if the algorithm given is "svd".
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
Jacob A. Wegelin. A survey of Partial Least Squares (PLS) methods, with
|
||||
emphasis on the two-block case. Technical Report 371, Department of
|
||||
Statistics, University of Washington, Seattle, 2000.
|
||||
|
||||
In French but still a reference:
|
||||
Tenenhaus, M. (1998). La regression PLS: theorie et pratique. Paris:
|
||||
Editions Technic.
|
||||
|
||||
See also
|
||||
--------
|
||||
PLSCanonical
|
||||
PLSRegression
|
||||
CCA
|
||||
PLS_SVD
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def __init__(self, n_components=2, *, scale=True,
|
||||
deflation_mode="regression",
|
||||
mode="A", algorithm="nipals", norm_y_weights=False,
|
||||
max_iter=500, tol=1e-06, copy=True):
|
||||
self.n_components = n_components
|
||||
self.deflation_mode = deflation_mode
|
||||
self.mode = mode
|
||||
self.norm_y_weights = norm_y_weights
|
||||
self.scale = scale
|
||||
self.algorithm = algorithm
|
||||
self.max_iter = max_iter
|
||||
self.tol = tol
|
||||
self.copy = copy
|
||||
|
||||
def fit(self, X, Y):
|
||||
"""Fit model to data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : array-like of shape (n_samples, n_features)
|
||||
Training vectors, where n_samples is the number of samples and
|
||||
n_features is the number of predictors.
|
||||
|
||||
Y : array-like of shape (n_samples, n_targets)
|
||||
Target vectors, where n_samples is the number of samples and
|
||||
n_targets is the number of response variables.
|
||||
"""
|
||||
|
||||
# copy since this will contains the residuals (deflated) matrices
|
||||
check_consistent_length(X, Y)
|
||||
X = self._validate_data(X, dtype=np.float64, copy=self.copy,
|
||||
ensure_min_samples=2)
|
||||
Y = check_array(Y, dtype=np.float64, copy=self.copy, ensure_2d=False)
|
||||
if Y.ndim == 1:
|
||||
Y = Y.reshape(-1, 1)
|
||||
|
||||
n = X.shape[0]
|
||||
p = X.shape[1]
|
||||
q = Y.shape[1]
|
||||
|
||||
if self.n_components < 1 or self.n_components > p:
|
||||
raise ValueError('Invalid number of components: %d' %
|
||||
self.n_components)
|
||||
if self.algorithm not in ("svd", "nipals"):
|
||||
raise ValueError("Got algorithm %s when only 'svd' "
|
||||
"and 'nipals' are known" % self.algorithm)
|
||||
if self.algorithm == "svd" and self.mode == "B":
|
||||
raise ValueError('Incompatible configuration: mode B is not '
|
||||
'implemented with svd algorithm')
|
||||
if self.deflation_mode not in ["canonical", "regression"]:
|
||||
raise ValueError('The deflation mode is unknown')
|
||||
# Scale (in place)
|
||||
X, Y, self.x_mean_, self.y_mean_, self.x_std_, self.y_std_ = (
|
||||
_center_scale_xy(X, Y, self.scale))
|
||||
# Residuals (deflated) matrices
|
||||
Xk = X
|
||||
Yk = Y
|
||||
# Results matrices
|
||||
self.x_scores_ = np.zeros((n, self.n_components))
|
||||
self.y_scores_ = np.zeros((n, self.n_components))
|
||||
self.x_weights_ = np.zeros((p, self.n_components))
|
||||
self.y_weights_ = np.zeros((q, self.n_components))
|
||||
self.x_loadings_ = np.zeros((p, self.n_components))
|
||||
self.y_loadings_ = np.zeros((q, self.n_components))
|
||||
self.n_iter_ = []
|
||||
|
||||
# NIPALS algo: outer loop, over components
|
||||
Y_eps = np.finfo(Yk.dtype).eps
|
||||
for k in range(self.n_components):
|
||||
if np.all(np.dot(Yk.T, Yk) < np.finfo(np.double).eps):
|
||||
# Yk constant
|
||||
warnings.warn('Y residual constant at iteration %s' % k)
|
||||
break
|
||||
# 1) weights estimation (inner loop)
|
||||
# -----------------------------------
|
||||
if self.algorithm == "nipals":
|
||||
# Replace columns that are all close to zero with zeros
|
||||
Yk_mask = np.all(np.abs(Yk) < 10 * Y_eps, axis=0)
|
||||
Yk[:, Yk_mask] = 0.0
|
||||
|
||||
x_weights, y_weights, n_iter_ = \
|
||||
_nipals_twoblocks_inner_loop(
|
||||
X=Xk, Y=Yk, mode=self.mode, max_iter=self.max_iter,
|
||||
tol=self.tol, norm_y_weights=self.norm_y_weights)
|
||||
self.n_iter_.append(n_iter_)
|
||||
elif self.algorithm == "svd":
|
||||
x_weights, y_weights = _svd_cross_product(X=Xk, Y=Yk)
|
||||
# Forces sign stability of x_weights and y_weights
|
||||
# Sign undeterminacy issue from svd if algorithm == "svd"
|
||||
# and from platform dependent computation if algorithm == 'nipals'
|
||||
x_weights, y_weights = svd_flip(x_weights, y_weights.T)
|
||||
y_weights = y_weights.T
|
||||
# compute scores
|
||||
x_scores = np.dot(Xk, x_weights)
|
||||
if self.norm_y_weights:
|
||||
y_ss = 1
|
||||
else:
|
||||
y_ss = np.dot(y_weights.T, y_weights)
|
||||
y_scores = np.dot(Yk, y_weights) / y_ss
|
||||
# test for null variance
|
||||
if np.dot(x_scores.T, x_scores) < np.finfo(np.double).eps:
|
||||
warnings.warn('X scores are null at iteration %s' % k)
|
||||
break
|
||||
# 2) Deflation (in place)
|
||||
# ----------------------
|
||||
# Possible memory footprint reduction may done here: in order to
|
||||
# avoid the allocation of a data chunk for the rank-one
|
||||
# approximations matrix which is then subtracted to Xk, we suggest
|
||||
# to perform a column-wise deflation.
|
||||
#
|
||||
# - regress Xk's on x_score
|
||||
x_loadings = np.dot(Xk.T, x_scores) / np.dot(x_scores.T, x_scores)
|
||||
# - subtract rank-one approximations to obtain remainder matrix
|
||||
Xk -= np.dot(x_scores, x_loadings.T)
|
||||
if self.deflation_mode == "canonical":
|
||||
# - regress Yk's on y_score, then subtract rank-one approx.
|
||||
y_loadings = (np.dot(Yk.T, y_scores)
|
||||
/ np.dot(y_scores.T, y_scores))
|
||||
Yk -= np.dot(y_scores, y_loadings.T)
|
||||
if self.deflation_mode == "regression":
|
||||
# - regress Yk's on x_score, then subtract rank-one approx.
|
||||
y_loadings = (np.dot(Yk.T, x_scores)
|
||||
/ np.dot(x_scores.T, x_scores))
|
||||
Yk -= np.dot(x_scores, y_loadings.T)
|
||||
# 3) Store weights, scores and loadings # Notation:
|
||||
self.x_scores_[:, k] = x_scores.ravel() # T
|
||||
self.y_scores_[:, k] = y_scores.ravel() # U
|
||||
self.x_weights_[:, k] = x_weights.ravel() # W
|
||||
self.y_weights_[:, k] = y_weights.ravel() # C
|
||||
self.x_loadings_[:, k] = x_loadings.ravel() # P
|
||||
self.y_loadings_[:, k] = y_loadings.ravel() # Q
|
||||
# Such that: X = TP' + Err and Y = UQ' + Err
|
||||
|
||||
# 4) rotations from input space to transformed space (scores)
|
||||
# T = X W(P'W)^-1 = XW* (W* : p x k matrix)
|
||||
# U = Y C(Q'C)^-1 = YC* (W* : q x k matrix)
|
||||
self.x_rotations_ = np.dot(
|
||||
self.x_weights_,
|
||||
pinv2(np.dot(self.x_loadings_.T, self.x_weights_),
|
||||
check_finite=False))
|
||||
if Y.shape[1] > 1:
|
||||
self.y_rotations_ = np.dot(
|
||||
self.y_weights_,
|
||||
pinv2(np.dot(self.y_loadings_.T, self.y_weights_),
|
||||
check_finite=False))
|
||||
else:
|
||||
self.y_rotations_ = np.ones(1)
|
||||
|
||||
if True or self.deflation_mode == "regression":
|
||||
# FIXME what's with the if?
|
||||
# Estimate regression coefficient
|
||||
# Regress Y on T
|
||||
# Y = TQ' + Err,
|
||||
# Then express in function of X
|
||||
# Y = X W(P'W)^-1Q' + Err = XB + Err
|
||||
# => B = W*Q' (p x q)
|
||||
self.coef_ = np.dot(self.x_rotations_, self.y_loadings_.T)
|
||||
self.coef_ = self.coef_ * self.y_std_
|
||||
return self
|
||||
|
||||
def transform(self, X, Y=None, copy=True):
|
||||
"""Apply the dimension reduction learned on the train data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : array-like of shape (n_samples, n_features)
|
||||
Training vectors, where n_samples is the number of samples and
|
||||
n_features is the number of predictors.
|
||||
|
||||
Y : array-like of shape (n_samples, n_targets)
|
||||
Target vectors, where n_samples is the number of samples and
|
||||
n_targets is the number of response variables.
|
||||
|
||||
copy : boolean, default True
|
||||
Whether to copy X and Y, or perform in-place normalization.
|
||||
|
||||
Returns
|
||||
-------
|
||||
x_scores if Y is not given, (x_scores, y_scores) otherwise.
|
||||
"""
|
||||
check_is_fitted(self)
|
||||
X = check_array(X, copy=copy, dtype=FLOAT_DTYPES)
|
||||
# Normalize
|
||||
X -= self.x_mean_
|
||||
X /= self.x_std_
|
||||
# Apply rotation
|
||||
x_scores = np.dot(X, self.x_rotations_)
|
||||
if Y is not None:
|
||||
Y = check_array(Y, ensure_2d=False, copy=copy, dtype=FLOAT_DTYPES)
|
||||
if Y.ndim == 1:
|
||||
Y = Y.reshape(-1, 1)
|
||||
Y -= self.y_mean_
|
||||
Y /= self.y_std_
|
||||
y_scores = np.dot(Y, self.y_rotations_)
|
||||
return x_scores, y_scores
|
||||
|
||||
return x_scores
|
||||
|
||||
def inverse_transform(self, X):
|
||||
"""Transform data back to its original space.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : array-like of shape (n_samples, n_components)
|
||||
New data, where n_samples is the number of samples
|
||||
and n_components is the number of pls components.
|
||||
|
||||
Returns
|
||||
-------
|
||||
x_reconstructed : array-like of shape (n_samples, n_features)
|
||||
|
||||
Notes
|
||||
-----
|
||||
This transformation will only be exact if n_components=n_features
|
||||
"""
|
||||
check_is_fitted(self)
|
||||
X = check_array(X, dtype=FLOAT_DTYPES)
|
||||
# From pls space to original space
|
||||
X_reconstructed = np.matmul(X, self.x_loadings_.T)
|
||||
|
||||
# Denormalize
|
||||
X_reconstructed *= self.x_std_
|
||||
X_reconstructed += self.x_mean_
|
||||
return X_reconstructed
|
||||
|
||||
def predict(self, X, copy=True):
|
||||
"""Apply the dimension reduction learned on the train data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : array-like of shape (n_samples, n_features)
|
||||
Training vectors, where n_samples is the number of samples and
|
||||
n_features is the number of predictors.
|
||||
|
||||
copy : boolean, default True
|
||||
Whether to copy X and Y, or perform in-place normalization.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This call requires the estimation of a p x q matrix, which may
|
||||
be an issue in high dimensional space.
|
||||
"""
|
||||
check_is_fitted(self)
|
||||
X = check_array(X, copy=copy, dtype=FLOAT_DTYPES)
|
||||
# Normalize
|
||||
X -= self.x_mean_
|
||||
X /= self.x_std_
|
||||
Ypred = np.dot(X, self.coef_)
|
||||
return Ypred + self.y_mean_
|
||||
|
||||
def fit_transform(self, X, y=None):
|
||||
"""Learn and apply the dimension reduction on the train data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : array-like of shape (n_samples, n_features)
|
||||
Training vectors, where n_samples is the number of samples and
|
||||
n_features is the number of predictors.
|
||||
|
||||
y : array-like of shape (n_samples, n_targets)
|
||||
Target vectors, where n_samples is the number of samples and
|
||||
n_targets is the number of response variables.
|
||||
|
||||
Returns
|
||||
-------
|
||||
x_scores if Y is not given, (x_scores, y_scores) otherwise.
|
||||
"""
|
||||
return self.fit(X, y).transform(X, y)
|
||||
|
||||
def _more_tags(self):
|
||||
return {'poor_score': True,
|
||||
'requires_y': False}
|
||||
|
||||
|
||||
class PLSRegression(_PLS):
|
||||
"""PLS regression
|
||||
|
||||
PLSRegression implements the PLS 2 blocks regression known as PLS2 or PLS1
|
||||
in case of one dimensional response.
|
||||
This class inherits from _PLS with mode="A", deflation_mode="regression",
|
||||
norm_y_weights=False and algorithm="nipals".
|
||||
|
||||
Read more in the :ref:`User Guide <cross_decomposition>`.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n_components : int, (default 2)
|
||||
Number of components to keep.
|
||||
|
||||
scale : boolean, (default True)
|
||||
whether to scale the data
|
||||
|
||||
max_iter : an integer, (default 500)
|
||||
the maximum number of iterations of the NIPALS inner loop (used
|
||||
only if algorithm="nipals")
|
||||
|
||||
tol : non-negative real
|
||||
Tolerance used in the iterative algorithm default 1e-06.
|
||||
|
||||
copy : boolean, default True
|
||||
Whether the deflation should be done on a copy. Let the default
|
||||
value to True unless you don't care about side effect
|
||||
|
||||
Attributes
|
||||
----------
|
||||
x_weights_ : array, [p, n_components]
|
||||
X block weights vectors.
|
||||
|
||||
y_weights_ : array, [q, n_components]
|
||||
Y block weights vectors.
|
||||
|
||||
x_loadings_ : array, [p, n_components]
|
||||
X block loadings vectors.
|
||||
|
||||
y_loadings_ : array, [q, n_components]
|
||||
Y block loadings vectors.
|
||||
|
||||
x_scores_ : array, [n_samples, n_components]
|
||||
X scores.
|
||||
|
||||
y_scores_ : array, [n_samples, n_components]
|
||||
Y scores.
|
||||
|
||||
x_rotations_ : array, [p, n_components]
|
||||
X block to latents rotations.
|
||||
|
||||
y_rotations_ : array, [q, n_components]
|
||||
Y block to latents rotations.
|
||||
|
||||
coef_ : array, [p, q]
|
||||
The coefficients of the linear model: ``Y = X coef_ + Err``
|
||||
|
||||
n_iter_ : array-like
|
||||
Number of iterations of the NIPALS inner loop for each
|
||||
component.
|
||||
|
||||
Notes
|
||||
-----
|
||||
Matrices::
|
||||
|
||||
T: x_scores_
|
||||
U: y_scores_
|
||||
W: x_weights_
|
||||
C: y_weights_
|
||||
P: x_loadings_
|
||||
Q: y_loadings_
|
||||
|
||||
Are computed such that::
|
||||
|
||||
X = T P.T + Err and Y = U Q.T + Err
|
||||
T[:, k] = Xk W[:, k] for k in range(n_components)
|
||||
U[:, k] = Yk C[:, k] for k in range(n_components)
|
||||
x_rotations_ = W (P.T W)^(-1)
|
||||
y_rotations_ = C (Q.T C)^(-1)
|
||||
|
||||
where Xk and Yk are residual matrices at iteration k.
|
||||
|
||||
`Slides explaining
|
||||
PLS <http://www.eigenvector.com/Docs/Wise_pls_properties.pdf>`_
|
||||
|
||||
|
||||
For each component k, find weights u, v that optimizes:
|
||||
``max corr(Xk u, Yk v) * std(Xk u) std(Yk u)``, such that ``|u| = 1``
|
||||
|
||||
Note that it maximizes both the correlations between the scores and the
|
||||
intra-block variances.
|
||||
|
||||
The residual matrix of X (Xk+1) block is obtained by the deflation on
|
||||
the current X score: x_score.
|
||||
|
||||
The residual matrix of Y (Yk+1) block is obtained by deflation on the
|
||||
current X score. This performs the PLS regression known as PLS2. This
|
||||
mode is prediction oriented.
|
||||
|
||||
This implementation provides the same results that 3 PLS packages
|
||||
provided in the R language (R-project):
|
||||
|
||||
- "mixOmics" with function pls(X, Y, mode = "regression")
|
||||
- "plspm " with function plsreg2(X, Y)
|
||||
- "pls" with function oscorespls.fit(X, Y)
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from sklearn.cross_decomposition import PLSRegression
|
||||
>>> X = [[0., 0., 1.], [1.,0.,0.], [2.,2.,2.], [2.,5.,4.]]
|
||||
>>> Y = [[0.1, -0.2], [0.9, 1.1], [6.2, 5.9], [11.9, 12.3]]
|
||||
>>> pls2 = PLSRegression(n_components=2)
|
||||
>>> pls2.fit(X, Y)
|
||||
PLSRegression()
|
||||
>>> Y_pred = pls2.predict(X)
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
Jacob A. Wegelin. A survey of Partial Least Squares (PLS) methods, with
|
||||
emphasis on the two-block case. Technical Report 371, Department of
|
||||
Statistics, University of Washington, Seattle, 2000.
|
||||
|
||||
In french but still a reference:
|
||||
Tenenhaus, M. (1998). La regression PLS: theorie et pratique. Paris:
|
||||
Editions Technic.
|
||||
"""
|
||||
@_deprecate_positional_args
|
||||
def __init__(self, n_components=2, *, scale=True,
|
||||
max_iter=500, tol=1e-06, copy=True):
|
||||
super().__init__(
|
||||
n_components=n_components, scale=scale,
|
||||
deflation_mode="regression", mode="A",
|
||||
norm_y_weights=False, max_iter=max_iter, tol=tol,
|
||||
copy=copy)
|
||||
|
||||
|
||||
class PLSCanonical(_PLS):
|
||||
""" PLSCanonical implements the 2 blocks canonical PLS of the original Wold
|
||||
algorithm [Tenenhaus 1998] p.204, referred as PLS-C2A in [Wegelin 2000].
|
||||
|
||||
This class inherits from PLS with mode="A" and deflation_mode="canonical",
|
||||
norm_y_weights=True and algorithm="nipals", but svd should provide similar
|
||||
results up to numerical errors.
|
||||
|
||||
Read more in the :ref:`User Guide <cross_decomposition>`.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n_components : int, (default 2).
|
||||
Number of components to keep
|
||||
|
||||
scale : boolean, (default True)
|
||||
Option to scale data
|
||||
|
||||
algorithm : string, "nipals" or "svd"
|
||||
The algorithm used to estimate the weights. It will be called
|
||||
n_components times, i.e. once for each iteration of the outer loop.
|
||||
|
||||
max_iter : an integer, (default 500)
|
||||
the maximum number of iterations of the NIPALS inner loop (used
|
||||
only if algorithm="nipals")
|
||||
|
||||
tol : non-negative real, default 1e-06
|
||||
the tolerance used in the iterative algorithm
|
||||
|
||||
copy : boolean, default True
|
||||
Whether the deflation should be done on a copy. Let the default
|
||||
value to True unless you don't care about side effect
|
||||
|
||||
Attributes
|
||||
----------
|
||||
x_weights_ : array, shape = [p, n_components]
|
||||
X block weights vectors.
|
||||
|
||||
y_weights_ : array, shape = [q, n_components]
|
||||
Y block weights vectors.
|
||||
|
||||
x_loadings_ : array, shape = [p, n_components]
|
||||
X block loadings vectors.
|
||||
|
||||
y_loadings_ : array, shape = [q, n_components]
|
||||
Y block loadings vectors.
|
||||
|
||||
x_scores_ : array, shape = [n_samples, n_components]
|
||||
X scores.
|
||||
|
||||
y_scores_ : array, shape = [n_samples, n_components]
|
||||
Y scores.
|
||||
|
||||
x_rotations_ : array, shape = [p, n_components]
|
||||
X block to latents rotations.
|
||||
|
||||
y_rotations_ : array, shape = [q, n_components]
|
||||
Y block to latents rotations.
|
||||
|
||||
coef_ : array of shape (p, q)
|
||||
The coefficients of the linear model: ``Y = X coef_ + Err``
|
||||
|
||||
n_iter_ : array-like
|
||||
Number of iterations of the NIPALS inner loop for each
|
||||
component. Not useful if the algorithm provided is "svd".
|
||||
|
||||
Notes
|
||||
-----
|
||||
Matrices::
|
||||
|
||||
T: x_scores_
|
||||
U: y_scores_
|
||||
W: x_weights_
|
||||
C: y_weights_
|
||||
P: x_loadings_
|
||||
Q: y_loadings__
|
||||
|
||||
Are computed such that::
|
||||
|
||||
X = T P.T + Err and Y = U Q.T + Err
|
||||
T[:, k] = Xk W[:, k] for k in range(n_components)
|
||||
U[:, k] = Yk C[:, k] for k in range(n_components)
|
||||
x_rotations_ = W (P.T W)^(-1)
|
||||
y_rotations_ = C (Q.T C)^(-1)
|
||||
|
||||
where Xk and Yk are residual matrices at iteration k.
|
||||
|
||||
`Slides explaining PLS
|
||||
<http://www.eigenvector.com/Docs/Wise_pls_properties.pdf>`_
|
||||
|
||||
For each component k, find weights u, v that optimize::
|
||||
|
||||
max corr(Xk u, Yk v) * std(Xk u) std(Yk u), such that ``|u| = |v| = 1``
|
||||
|
||||
Note that it maximizes both the correlations between the scores and the
|
||||
intra-block variances.
|
||||
|
||||
The residual matrix of X (Xk+1) block is obtained by the deflation on the
|
||||
current X score: x_score.
|
||||
|
||||
The residual matrix of Y (Yk+1) block is obtained by deflation on the
|
||||
current Y score. This performs a canonical symmetric version of the PLS
|
||||
regression. But slightly different than the CCA. This is mostly used
|
||||
for modeling.
|
||||
|
||||
This implementation provides the same results that the "plspm" package
|
||||
provided in the R language (R-project), using the function plsca(X, Y).
|
||||
Results are equal or collinear with the function
|
||||
``pls(..., mode = "canonical")`` of the "mixOmics" package. The difference
|
||||
relies in the fact that mixOmics implementation does not exactly implement
|
||||
the Wold algorithm since it does not normalize y_weights to one.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from sklearn.cross_decomposition import PLSCanonical
|
||||
>>> X = [[0., 0., 1.], [1.,0.,0.], [2.,2.,2.], [2.,5.,4.]]
|
||||
>>> Y = [[0.1, -0.2], [0.9, 1.1], [6.2, 5.9], [11.9, 12.3]]
|
||||
>>> plsca = PLSCanonical(n_components=2)
|
||||
>>> plsca.fit(X, Y)
|
||||
PLSCanonical()
|
||||
>>> X_c, Y_c = plsca.transform(X, Y)
|
||||
|
||||
References
|
||||
----------
|
||||
|
||||
Jacob A. Wegelin. A survey of Partial Least Squares (PLS) methods, with
|
||||
emphasis on the two-block case. Technical Report 371, Department of
|
||||
Statistics, University of Washington, Seattle, 2000.
|
||||
|
||||
Tenenhaus, M. (1998). La regression PLS: theorie et pratique. Paris:
|
||||
Editions Technic.
|
||||
|
||||
See also
|
||||
--------
|
||||
CCA
|
||||
PLSSVD
|
||||
"""
|
||||
@_deprecate_positional_args
|
||||
def __init__(self, n_components=2, *, scale=True, algorithm="nipals",
|
||||
max_iter=500, tol=1e-06, copy=True):
|
||||
super().__init__(
|
||||
n_components=n_components, scale=scale,
|
||||
deflation_mode="canonical", mode="A",
|
||||
norm_y_weights=True, algorithm=algorithm,
|
||||
max_iter=max_iter, tol=tol, copy=copy)
|
||||
|
||||
|
||||
class PLSSVD(TransformerMixin, BaseEstimator):
|
||||
"""Partial Least Square SVD
|
||||
|
||||
Simply perform a svd on the crosscovariance matrix: X'Y
|
||||
There are no iterative deflation here.
|
||||
|
||||
Read more in the :ref:`User Guide <cross_decomposition>`.
|
||||
|
||||
.. versionadded:: 0.8
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n_components : int, default 2
|
||||
Number of components to keep.
|
||||
|
||||
scale : boolean, default True
|
||||
Whether to scale X and Y.
|
||||
|
||||
copy : boolean, default True
|
||||
Whether to copy X and Y, or perform in-place computations.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
x_weights_ : array, [p, n_components]
|
||||
X block weights vectors.
|
||||
|
||||
y_weights_ : array, [q, n_components]
|
||||
Y block weights vectors.
|
||||
|
||||
x_scores_ : array, [n_samples, n_components]
|
||||
X scores.
|
||||
|
||||
y_scores_ : array, [n_samples, n_components]
|
||||
Y scores.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from sklearn.cross_decomposition import PLSSVD
|
||||
>>> X = np.array([[0., 0., 1.],
|
||||
... [1.,0.,0.],
|
||||
... [2.,2.,2.],
|
||||
... [2.,5.,4.]])
|
||||
>>> Y = np.array([[0.1, -0.2],
|
||||
... [0.9, 1.1],
|
||||
... [6.2, 5.9],
|
||||
... [11.9, 12.3]])
|
||||
>>> plsca = PLSSVD(n_components=2)
|
||||
>>> plsca.fit(X, Y)
|
||||
PLSSVD()
|
||||
>>> X_c, Y_c = plsca.transform(X, Y)
|
||||
>>> X_c.shape, Y_c.shape
|
||||
((4, 2), (4, 2))
|
||||
|
||||
See also
|
||||
--------
|
||||
PLSCanonical
|
||||
CCA
|
||||
"""
|
||||
@_deprecate_positional_args
|
||||
def __init__(self, n_components=2, *, scale=True, copy=True):
|
||||
self.n_components = n_components
|
||||
self.scale = scale
|
||||
self.copy = copy
|
||||
|
||||
def fit(self, X, Y):
|
||||
"""Fit model to data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : array-like of shape (n_samples, n_features)
|
||||
Training vectors, where n_samples is the number of samples and
|
||||
n_features is the number of predictors.
|
||||
|
||||
Y : array-like of shape (n_samples, n_targets)
|
||||
Target vectors, where n_samples is the number of samples and
|
||||
n_targets is the number of response variables.
|
||||
"""
|
||||
# copy since this will contains the centered data
|
||||
check_consistent_length(X, Y)
|
||||
X = self._validate_data(X, dtype=np.float64, copy=self.copy,
|
||||
ensure_min_samples=2)
|
||||
Y = check_array(Y, dtype=np.float64, copy=self.copy, ensure_2d=False)
|
||||
if Y.ndim == 1:
|
||||
Y = Y.reshape(-1, 1)
|
||||
|
||||
if self.n_components > max(Y.shape[1], X.shape[1]):
|
||||
raise ValueError("Invalid number of components n_components=%d"
|
||||
" with X of shape %s and Y of shape %s."
|
||||
% (self.n_components, str(X.shape), str(Y.shape)))
|
||||
|
||||
# Scale (in place)
|
||||
X, Y, self.x_mean_, self.y_mean_, self.x_std_, self.y_std_ = (
|
||||
_center_scale_xy(X, Y, self.scale))
|
||||
# svd(X'Y)
|
||||
C = np.dot(X.T, Y)
|
||||
|
||||
# The arpack svds solver only works if the number of extracted
|
||||
# components is smaller than rank(X) - 1. Hence, if we want to extract
|
||||
# all the components (C.shape[1]), we have to use another one. Else,
|
||||
# let's use arpacks to compute only the interesting components.
|
||||
if self.n_components >= np.min(C.shape):
|
||||
U, s, V = svd(C, full_matrices=False)
|
||||
else:
|
||||
U, s, V = svds(C, k=self.n_components)
|
||||
# Deterministic output
|
||||
U, V = svd_flip(U, V)
|
||||
V = V.T
|
||||
self.x_scores_ = np.dot(X, U)
|
||||
self.y_scores_ = np.dot(Y, V)
|
||||
self.x_weights_ = U
|
||||
self.y_weights_ = V
|
||||
return self
|
||||
|
||||
def transform(self, X, Y=None):
|
||||
"""
|
||||
Apply the dimension reduction learned on the train data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : array-like of shape (n_samples, n_features)
|
||||
Training vectors, where n_samples is the number of samples and
|
||||
n_features is the number of predictors.
|
||||
|
||||
Y : array-like of shape (n_samples, n_targets)
|
||||
Target vectors, where n_samples is the number of samples and
|
||||
n_targets is the number of response variables.
|
||||
"""
|
||||
check_is_fitted(self)
|
||||
X = check_array(X, dtype=np.float64)
|
||||
Xr = (X - self.x_mean_) / self.x_std_
|
||||
x_scores = np.dot(Xr, self.x_weights_)
|
||||
if Y is not None:
|
||||
Y = check_array(Y, ensure_2d=False, dtype=np.float64)
|
||||
if Y.ndim == 1:
|
||||
Y = Y.reshape(-1, 1)
|
||||
Yr = (Y - self.y_mean_) / self.y_std_
|
||||
y_scores = np.dot(Yr, self.y_weights_)
|
||||
return x_scores, y_scores
|
||||
return x_scores
|
||||
|
||||
def fit_transform(self, X, y=None):
|
||||
"""Learn and apply the dimension reduction on the train data.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
X : array-like of shape (n_samples, n_features)
|
||||
Training vectors, where n_samples is the number of samples and
|
||||
n_features is the number of predictors.
|
||||
|
||||
y : array-like of shape (n_samples, n_targets)
|
||||
Target vectors, where n_samples is the number of samples and
|
||||
n_targets is the number of response variables.
|
||||
|
||||
Returns
|
||||
-------
|
||||
x_scores if Y is not given, (x_scores, y_scores) otherwise.
|
||||
"""
|
||||
return self.fit(X, y).transform(X, y)
|
18
venv/Lib/site-packages/sklearn/cross_decomposition/cca_.py
Normal file
18
venv/Lib/site-packages/sklearn/cross_decomposition/cca_.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED BY deprecated_modules.py
|
||||
import sys
|
||||
# mypy error: Module X has no attribute y (typically for C extensions)
|
||||
from . import _cca # type: ignore
|
||||
from ..externals._pep562 import Pep562
|
||||
from ..utils.deprecation import _raise_dep_warning_if_not_pytest
|
||||
|
||||
deprecated_path = 'sklearn.cross_decomposition.cca_'
|
||||
correct_import_path = 'sklearn.cross_decomposition'
|
||||
|
||||
_raise_dep_warning_if_not_pytest(deprecated_path, correct_import_path)
|
||||
|
||||
def __getattr__(name):
|
||||
return getattr(_cca, name)
|
||||
|
||||
if not sys.version_info >= (3, 7):
|
||||
Pep562(__name__)
|
18
venv/Lib/site-packages/sklearn/cross_decomposition/pls_.py
Normal file
18
venv/Lib/site-packages/sklearn/cross_decomposition/pls_.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
|
||||
# THIS FILE WAS AUTOMATICALLY GENERATED BY deprecated_modules.py
|
||||
import sys
|
||||
# mypy error: Module X has no attribute y (typically for C extensions)
|
||||
from . import _pls # type: ignore
|
||||
from ..externals._pep562 import Pep562
|
||||
from ..utils.deprecation import _raise_dep_warning_if_not_pytest
|
||||
|
||||
deprecated_path = 'sklearn.cross_decomposition.pls_'
|
||||
correct_import_path = 'sklearn.cross_decomposition'
|
||||
|
||||
_raise_dep_warning_if_not_pytest(deprecated_path, correct_import_path)
|
||||
|
||||
def __getattr__(name):
|
||||
return getattr(_pls, name)
|
||||
|
||||
if not sys.version_info >= (3, 7):
|
||||
Pep562(__name__)
|
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,450 @@
|
|||
import numpy as np
|
||||
from numpy.testing import assert_approx_equal
|
||||
|
||||
from sklearn.utils._testing import (assert_array_almost_equal,
|
||||
assert_array_equal, assert_raise_message,
|
||||
assert_warns)
|
||||
from sklearn.datasets import load_linnerud
|
||||
from sklearn.cross_decomposition import _pls as pls_
|
||||
from sklearn.cross_decomposition import CCA
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.utils import check_random_state
|
||||
from sklearn.exceptions import ConvergenceWarning
|
||||
|
||||
|
||||
def test_pls():
|
||||
d = load_linnerud()
|
||||
X = d.data
|
||||
Y = d.target
|
||||
# 1) Canonical (symmetric) PLS (PLS 2 blocks canonical mode A)
|
||||
# ===========================================================
|
||||
# Compare 2 algo.: nipals vs. svd
|
||||
# ------------------------------
|
||||
pls_bynipals = pls_.PLSCanonical(n_components=X.shape[1])
|
||||
pls_bynipals.fit(X, Y)
|
||||
pls_bysvd = pls_.PLSCanonical(algorithm="svd", n_components=X.shape[1])
|
||||
pls_bysvd.fit(X, Y)
|
||||
# check equalities of loading (up to the sign of the second column)
|
||||
assert_array_almost_equal(
|
||||
pls_bynipals.x_loadings_,
|
||||
pls_bysvd.x_loadings_, decimal=5,
|
||||
err_msg="nipals and svd implementations lead to different x loadings")
|
||||
|
||||
assert_array_almost_equal(
|
||||
pls_bynipals.y_loadings_,
|
||||
pls_bysvd.y_loadings_, decimal=5,
|
||||
err_msg="nipals and svd implementations lead to different y loadings")
|
||||
|
||||
# Check PLS properties (with n_components=X.shape[1])
|
||||
# ---------------------------------------------------
|
||||
plsca = pls_.PLSCanonical(n_components=X.shape[1])
|
||||
plsca.fit(X, Y)
|
||||
T = plsca.x_scores_
|
||||
P = plsca.x_loadings_
|
||||
Wx = plsca.x_weights_
|
||||
U = plsca.y_scores_
|
||||
Q = plsca.y_loadings_
|
||||
Wy = plsca.y_weights_
|
||||
|
||||
def check_ortho(M, err_msg):
|
||||
K = np.dot(M.T, M)
|
||||
assert_array_almost_equal(K, np.diag(np.diag(K)), err_msg=err_msg)
|
||||
|
||||
# Orthogonality of weights
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
check_ortho(Wx, "x weights are not orthogonal")
|
||||
check_ortho(Wy, "y weights are not orthogonal")
|
||||
|
||||
# Orthogonality of latent scores
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
check_ortho(T, "x scores are not orthogonal")
|
||||
check_ortho(U, "y scores are not orthogonal")
|
||||
|
||||
# Check X = TP' and Y = UQ' (with (p == q) components)
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
# center scale X, Y
|
||||
Xc, Yc, x_mean, y_mean, x_std, y_std =\
|
||||
pls_._center_scale_xy(X.copy(), Y.copy(), scale=True)
|
||||
assert_array_almost_equal(Xc, np.dot(T, P.T), err_msg="X != TP'")
|
||||
assert_array_almost_equal(Yc, np.dot(U, Q.T), err_msg="Y != UQ'")
|
||||
|
||||
# Check that rotations on training data lead to scores
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Xr = plsca.transform(X)
|
||||
assert_array_almost_equal(Xr, plsca.x_scores_,
|
||||
err_msg="rotation on X failed")
|
||||
Xr, Yr = plsca.transform(X, Y)
|
||||
assert_array_almost_equal(Xr, plsca.x_scores_,
|
||||
err_msg="rotation on X failed")
|
||||
assert_array_almost_equal(Yr, plsca.y_scores_,
|
||||
err_msg="rotation on Y failed")
|
||||
|
||||
# Check that inverse_transform works
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Xreconstructed = plsca.inverse_transform(Xr)
|
||||
assert_array_almost_equal(Xreconstructed, X,
|
||||
err_msg="inverse_transform failed")
|
||||
|
||||
# "Non regression test" on canonical PLS
|
||||
# --------------------------------------
|
||||
# The results were checked against the R-package plspm
|
||||
pls_ca = pls_.PLSCanonical(n_components=X.shape[1])
|
||||
pls_ca.fit(X, Y)
|
||||
|
||||
x_weights = np.array(
|
||||
[[-0.61330704, 0.25616119, -0.74715187],
|
||||
[-0.74697144, 0.11930791, 0.65406368],
|
||||
[-0.25668686, -0.95924297, -0.11817271]])
|
||||
# x_weights_sign_flip holds columns of 1 or -1, depending on sign flip
|
||||
# between R and python
|
||||
x_weights_sign_flip = pls_ca.x_weights_ / x_weights
|
||||
|
||||
x_rotations = np.array(
|
||||
[[-0.61330704, 0.41591889, -0.62297525],
|
||||
[-0.74697144, 0.31388326, 0.77368233],
|
||||
[-0.25668686, -0.89237972, -0.24121788]])
|
||||
x_rotations_sign_flip = pls_ca.x_rotations_ / x_rotations
|
||||
|
||||
y_weights = np.array(
|
||||
[[+0.58989127, 0.7890047, 0.1717553],
|
||||
[+0.77134053, -0.61351791, 0.16920272],
|
||||
[-0.23887670, -0.03267062, 0.97050016]])
|
||||
y_weights_sign_flip = pls_ca.y_weights_ / y_weights
|
||||
|
||||
y_rotations = np.array(
|
||||
[[+0.58989127, 0.7168115, 0.30665872],
|
||||
[+0.77134053, -0.70791757, 0.19786539],
|
||||
[-0.23887670, -0.00343595, 0.94162826]])
|
||||
y_rotations_sign_flip = pls_ca.y_rotations_ / y_rotations
|
||||
|
||||
# x_weights = X.dot(x_rotation)
|
||||
# Hence R/python sign flip should be the same in x_weight and x_rotation
|
||||
assert_array_almost_equal(x_rotations_sign_flip, x_weights_sign_flip)
|
||||
# This test that R / python give the same result up to column
|
||||
# sign indeterminacy
|
||||
assert_array_almost_equal(np.abs(x_rotations_sign_flip), 1, 4)
|
||||
assert_array_almost_equal(np.abs(x_weights_sign_flip), 1, 4)
|
||||
|
||||
|
||||
assert_array_almost_equal(y_rotations_sign_flip, y_weights_sign_flip)
|
||||
assert_array_almost_equal(np.abs(y_rotations_sign_flip), 1, 4)
|
||||
assert_array_almost_equal(np.abs(y_weights_sign_flip), 1, 4)
|
||||
|
||||
# 2) Regression PLS (PLS2): "Non regression test"
|
||||
# ===============================================
|
||||
# The results were checked against the R-packages plspm, misOmics and pls
|
||||
pls_2 = pls_.PLSRegression(n_components=X.shape[1])
|
||||
pls_2.fit(X, Y)
|
||||
|
||||
x_weights = np.array(
|
||||
[[-0.61330704, -0.00443647, 0.78983213],
|
||||
[-0.74697144, -0.32172099, -0.58183269],
|
||||
[-0.25668686, 0.94682413, -0.19399983]])
|
||||
x_weights_sign_flip = pls_2.x_weights_ / x_weights
|
||||
|
||||
x_loadings = np.array(
|
||||
[[-0.61470416, -0.24574278, 0.78983213],
|
||||
[-0.65625755, -0.14396183, -0.58183269],
|
||||
[-0.51733059, 1.00609417, -0.19399983]])
|
||||
x_loadings_sign_flip = pls_2.x_loadings_ / x_loadings
|
||||
|
||||
y_weights = np.array(
|
||||
[[+0.32456184, 0.29892183, 0.20316322],
|
||||
[+0.42439636, 0.61970543, 0.19320542],
|
||||
[-0.13143144, -0.26348971, -0.17092916]])
|
||||
y_weights_sign_flip = pls_2.y_weights_ / y_weights
|
||||
|
||||
y_loadings = np.array(
|
||||
[[+0.32456184, 0.29892183, 0.20316322],
|
||||
[+0.42439636, 0.61970543, 0.19320542],
|
||||
[-0.13143144, -0.26348971, -0.17092916]])
|
||||
y_loadings_sign_flip = pls_2.y_loadings_ / y_loadings
|
||||
|
||||
# x_loadings[:, i] = Xi.dot(x_weights[:, i]) \forall i
|
||||
assert_array_almost_equal(x_loadings_sign_flip, x_weights_sign_flip, 4)
|
||||
assert_array_almost_equal(np.abs(x_loadings_sign_flip), 1, 4)
|
||||
assert_array_almost_equal(np.abs(x_weights_sign_flip), 1, 4)
|
||||
|
||||
assert_array_almost_equal(y_loadings_sign_flip, y_weights_sign_flip, 4)
|
||||
assert_array_almost_equal(np.abs(y_loadings_sign_flip), 1, 4)
|
||||
assert_array_almost_equal(np.abs(y_weights_sign_flip), 1, 4)
|
||||
|
||||
# 3) Another non-regression test of Canonical PLS on random dataset
|
||||
# =================================================================
|
||||
# The results were checked against the R-package plspm
|
||||
n = 500
|
||||
p_noise = 10
|
||||
q_noise = 5
|
||||
# 2 latents vars:
|
||||
rng = check_random_state(11)
|
||||
l1 = rng.normal(size=n)
|
||||
l2 = rng.normal(size=n)
|
||||
latents = np.array([l1, l1, l2, l2]).T
|
||||
X = latents + rng.normal(size=4 * n).reshape((n, 4))
|
||||
Y = latents + rng.normal(size=4 * n).reshape((n, 4))
|
||||
X = np.concatenate(
|
||||
(X, rng.normal(size=p_noise * n).reshape(n, p_noise)), axis=1)
|
||||
Y = np.concatenate(
|
||||
(Y, rng.normal(size=q_noise * n).reshape(n, q_noise)), axis=1)
|
||||
|
||||
pls_ca = pls_.PLSCanonical(n_components=3)
|
||||
pls_ca.fit(X, Y)
|
||||
|
||||
x_weights = np.array(
|
||||
[[0.65803719, 0.19197924, 0.21769083],
|
||||
[0.7009113, 0.13303969, -0.15376699],
|
||||
[0.13528197, -0.68636408, 0.13856546],
|
||||
[0.16854574, -0.66788088, -0.12485304],
|
||||
[-0.03232333, -0.04189855, 0.40690153],
|
||||
[0.1148816, -0.09643158, 0.1613305],
|
||||
[0.04792138, -0.02384992, 0.17175319],
|
||||
[-0.06781, -0.01666137, -0.18556747],
|
||||
[-0.00266945, -0.00160224, 0.11893098],
|
||||
[-0.00849528, -0.07706095, 0.1570547],
|
||||
[-0.00949471, -0.02964127, 0.34657036],
|
||||
[-0.03572177, 0.0945091, 0.3414855],
|
||||
[0.05584937, -0.02028961, -0.57682568],
|
||||
[0.05744254, -0.01482333, -0.17431274]])
|
||||
x_weights_sign_flip = pls_ca.x_weights_ / x_weights
|
||||
|
||||
|
||||
x_loadings = np.array(
|
||||
[[0.65649254, 0.1847647, 0.15270699],
|
||||
[0.67554234, 0.15237508, -0.09182247],
|
||||
[0.19219925, -0.67750975, 0.08673128],
|
||||
[0.2133631, -0.67034809, -0.08835483],
|
||||
[-0.03178912, -0.06668336, 0.43395268],
|
||||
[0.15684588, -0.13350241, 0.20578984],
|
||||
[0.03337736, -0.03807306, 0.09871553],
|
||||
[-0.06199844, 0.01559854, -0.1881785],
|
||||
[0.00406146, -0.00587025, 0.16413253],
|
||||
[-0.00374239, -0.05848466, 0.19140336],
|
||||
[0.00139214, -0.01033161, 0.32239136],
|
||||
[-0.05292828, 0.0953533, 0.31916881],
|
||||
[0.04031924, -0.01961045, -0.65174036],
|
||||
[0.06172484, -0.06597366, -0.1244497]])
|
||||
x_loadings_sign_flip = pls_ca.x_loadings_ / x_loadings
|
||||
|
||||
y_weights = np.array(
|
||||
[[0.66101097, 0.18672553, 0.22826092],
|
||||
[0.69347861, 0.18463471, -0.23995597],
|
||||
[0.14462724, -0.66504085, 0.17082434],
|
||||
[0.22247955, -0.6932605, -0.09832993],
|
||||
[0.07035859, 0.00714283, 0.67810124],
|
||||
[0.07765351, -0.0105204, -0.44108074],
|
||||
[-0.00917056, 0.04322147, 0.10062478],
|
||||
[-0.01909512, 0.06182718, 0.28830475],
|
||||
[0.01756709, 0.04797666, 0.32225745]])
|
||||
y_weights_sign_flip = pls_ca.y_weights_ / y_weights
|
||||
|
||||
y_loadings = np.array(
|
||||
[[0.68568625, 0.1674376, 0.0969508],
|
||||
[0.68782064, 0.20375837, -0.1164448],
|
||||
[0.11712173, -0.68046903, 0.12001505],
|
||||
[0.17860457, -0.6798319, -0.05089681],
|
||||
[0.06265739, -0.0277703, 0.74729584],
|
||||
[0.0914178, 0.00403751, -0.5135078],
|
||||
[-0.02196918, -0.01377169, 0.09564505],
|
||||
[-0.03288952, 0.09039729, 0.31858973],
|
||||
[0.04287624, 0.05254676, 0.27836841]])
|
||||
y_loadings_sign_flip = pls_ca.y_loadings_ / y_loadings
|
||||
|
||||
assert_array_almost_equal(x_loadings_sign_flip, x_weights_sign_flip, 4)
|
||||
assert_array_almost_equal(np.abs(x_weights_sign_flip), 1, 4)
|
||||
assert_array_almost_equal(np.abs(x_loadings_sign_flip), 1, 4)
|
||||
|
||||
assert_array_almost_equal(y_loadings_sign_flip, y_weights_sign_flip, 4)
|
||||
assert_array_almost_equal(np.abs(y_weights_sign_flip), 1, 4)
|
||||
assert_array_almost_equal(np.abs(y_loadings_sign_flip), 1, 4)
|
||||
|
||||
# Orthogonality of weights
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
check_ortho(pls_ca.x_weights_, "x weights are not orthogonal")
|
||||
check_ortho(pls_ca.y_weights_, "y weights are not orthogonal")
|
||||
|
||||
# Orthogonality of latent scores
|
||||
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
check_ortho(pls_ca.x_scores_, "x scores are not orthogonal")
|
||||
check_ortho(pls_ca.y_scores_, "y scores are not orthogonal")
|
||||
|
||||
# 4) Another "Non regression test" of PLS Regression (PLS2):
|
||||
# Checking behavior when the first column of Y is constant
|
||||
# ===============================================
|
||||
# The results were compared against a modified version of plsreg2
|
||||
# from the R-package plsdepot
|
||||
X = d.data
|
||||
Y = d.target
|
||||
Y[:, 0] = 1
|
||||
pls_2 = pls_.PLSRegression(n_components=X.shape[1])
|
||||
pls_2.fit(X, Y)
|
||||
|
||||
x_weights = np.array(
|
||||
[[-0.6273573, 0.007081799, 0.7786994],
|
||||
[-0.7493417, -0.277612681, -0.6011807],
|
||||
[-0.2119194, 0.960666981, -0.1794690]])
|
||||
x_weights_sign_flip = pls_2.x_weights_ / x_weights
|
||||
|
||||
x_loadings = np.array(
|
||||
[[-0.6273512, -0.22464538, 0.7786994],
|
||||
[-0.6643156, -0.09871193, -0.6011807],
|
||||
[-0.5125877, 1.01407380, -0.1794690]])
|
||||
x_loadings_sign_flip = pls_2.x_loadings_ / x_loadings
|
||||
|
||||
y_loadings = np.array(
|
||||
[[0.0000000, 0.0000000, 0.0000000],
|
||||
[0.4357300, 0.5828479, 0.2174802],
|
||||
[-0.1353739, -0.2486423, -0.1810386]])
|
||||
|
||||
# R/python sign flip should be the same in x_weight and x_rotation
|
||||
assert_array_almost_equal(x_loadings_sign_flip, x_weights_sign_flip, 4)
|
||||
|
||||
# This test that R / python give the same result up to column
|
||||
# sign indeterminacy
|
||||
assert_array_almost_equal(np.abs(x_loadings_sign_flip), 1, 4)
|
||||
assert_array_almost_equal(np.abs(x_weights_sign_flip), 1, 4)
|
||||
|
||||
# For the PLSRegression with default parameters, it holds that
|
||||
# y_loadings==y_weights. In this case we only test that R/python
|
||||
# give the same result for the y_loadings irrespective of the sign
|
||||
assert_array_almost_equal(np.abs(pls_2.y_loadings_), np.abs(y_loadings), 4)
|
||||
|
||||
|
||||
def test_convergence_fail():
|
||||
d = load_linnerud()
|
||||
X = d.data
|
||||
Y = d.target
|
||||
pls_bynipals = pls_.PLSCanonical(n_components=X.shape[1],
|
||||
max_iter=2, tol=1e-10)
|
||||
assert_warns(ConvergenceWarning, pls_bynipals.fit, X, Y)
|
||||
|
||||
|
||||
def test_PLSSVD():
|
||||
# Let's check the PLSSVD doesn't return all possible component but just
|
||||
# the specified number
|
||||
d = load_linnerud()
|
||||
X = d.data
|
||||
Y = d.target
|
||||
n_components = 2
|
||||
for clf in [pls_.PLSSVD, pls_.PLSRegression, pls_.PLSCanonical]:
|
||||
pls = clf(n_components=n_components)
|
||||
pls.fit(X, Y)
|
||||
assert n_components == pls.y_scores_.shape[1]
|
||||
|
||||
|
||||
def test_univariate_pls_regression():
|
||||
# Ensure 1d Y is correctly interpreted
|
||||
d = load_linnerud()
|
||||
X = d.data
|
||||
Y = d.target
|
||||
|
||||
clf = pls_.PLSRegression()
|
||||
# Compare 1d to column vector
|
||||
model1 = clf.fit(X, Y[:, 0]).coef_
|
||||
model2 = clf.fit(X, Y[:, :1]).coef_
|
||||
assert_array_almost_equal(model1, model2)
|
||||
|
||||
|
||||
def test_predict_transform_copy():
|
||||
# check that the "copy" keyword works
|
||||
d = load_linnerud()
|
||||
X = d.data
|
||||
Y = d.target
|
||||
clf = pls_.PLSCanonical()
|
||||
X_copy = X.copy()
|
||||
Y_copy = Y.copy()
|
||||
clf.fit(X, Y)
|
||||
# check that results are identical with copy
|
||||
assert_array_almost_equal(clf.predict(X), clf.predict(X.copy(), copy=False))
|
||||
assert_array_almost_equal(clf.transform(X), clf.transform(X.copy(), copy=False))
|
||||
|
||||
# check also if passing Y
|
||||
assert_array_almost_equal(clf.transform(X, Y),
|
||||
clf.transform(X.copy(), Y.copy(), copy=False))
|
||||
# check that copy doesn't destroy
|
||||
# we do want to check exact equality here
|
||||
assert_array_equal(X_copy, X)
|
||||
assert_array_equal(Y_copy, Y)
|
||||
# also check that mean wasn't zero before (to make sure we didn't touch it)
|
||||
assert np.all(X.mean(axis=0) != 0)
|
||||
|
||||
|
||||
def test_scale_and_stability():
|
||||
# We test scale=True parameter
|
||||
# This allows to check numerical stability over platforms as well
|
||||
|
||||
d = load_linnerud()
|
||||
X1 = d.data
|
||||
Y1 = d.target
|
||||
# causes X[:, -1].std() to be zero
|
||||
X1[:, -1] = 1.0
|
||||
|
||||
# From bug #2821
|
||||
# Test with X2, T2 s.t. clf.x_score[:, 1] == 0, clf.y_score[:, 1] == 0
|
||||
# This test robustness of algorithm when dealing with value close to 0
|
||||
X2 = np.array([[0., 0., 1.],
|
||||
[1., 0., 0.],
|
||||
[2., 2., 2.],
|
||||
[3., 5., 4.]])
|
||||
Y2 = np.array([[0.1, -0.2],
|
||||
[0.9, 1.1],
|
||||
[6.2, 5.9],
|
||||
[11.9, 12.3]])
|
||||
|
||||
for (X, Y) in [(X1, Y1), (X2, Y2)]:
|
||||
X_std = X.std(axis=0, ddof=1)
|
||||
X_std[X_std == 0] = 1
|
||||
Y_std = Y.std(axis=0, ddof=1)
|
||||
Y_std[Y_std == 0] = 1
|
||||
|
||||
X_s = (X - X.mean(axis=0)) / X_std
|
||||
Y_s = (Y - Y.mean(axis=0)) / Y_std
|
||||
|
||||
for clf in [CCA(), pls_.PLSCanonical(), pls_.PLSRegression(),
|
||||
pls_.PLSSVD()]:
|
||||
clf.set_params(scale=True)
|
||||
X_score, Y_score = clf.fit_transform(X, Y)
|
||||
clf.set_params(scale=False)
|
||||
X_s_score, Y_s_score = clf.fit_transform(X_s, Y_s)
|
||||
assert_array_almost_equal(X_s_score, X_score)
|
||||
assert_array_almost_equal(Y_s_score, Y_score)
|
||||
# Scaling should be idempotent
|
||||
clf.set_params(scale=True)
|
||||
X_score, Y_score = clf.fit_transform(X_s, Y_s)
|
||||
assert_array_almost_equal(X_s_score, X_score)
|
||||
assert_array_almost_equal(Y_s_score, Y_score)
|
||||
|
||||
|
||||
def test_pls_errors():
|
||||
d = load_linnerud()
|
||||
X = d.data
|
||||
Y = d.target
|
||||
for clf in [pls_.PLSCanonical(), pls_.PLSRegression(),
|
||||
pls_.PLSSVD()]:
|
||||
clf.n_components = 4
|
||||
assert_raise_message(ValueError, "Invalid number of components",
|
||||
clf.fit, X, Y)
|
||||
|
||||
|
||||
def test_pls_scaling():
|
||||
# sanity check for scale=True
|
||||
n_samples = 1000
|
||||
n_targets = 5
|
||||
n_features = 10
|
||||
|
||||
rng = check_random_state(0)
|
||||
|
||||
Q = rng.randn(n_targets, n_features)
|
||||
Y = rng.randn(n_samples, n_targets)
|
||||
X = np.dot(Y, Q) + 2 * rng.randn(n_samples, n_features) + 1
|
||||
X *= 1000
|
||||
X_scaled = StandardScaler().fit_transform(X)
|
||||
|
||||
pls = pls_.PLSRegression(n_components=5, scale=True)
|
||||
|
||||
pls.fit(X, Y)
|
||||
score = pls.score(X, Y)
|
||||
|
||||
pls.fit(X_scaled, Y)
|
||||
score_scaled = pls.score(X_scaled, Y)
|
||||
|
||||
assert_approx_equal(score, score_scaled)
|
Loading…
Add table
Add a link
Reference in a new issue