CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
_truncated_svd.py323 linesDownload Raw Back to decomposition
1"""Truncated SVD for sparse matrices, aka latent semantic analysis (LSA)."""
2
3# Authors: The scikit-learn developers
4# SPDX-License-Identifier: BSD-3-Clause
5
6from numbers import Integral, Real
7
8import numpy as np
9import scipy.sparse as sp
10from scipy.sparse.linalg import svds
11
12from ..base import (
13    BaseEstimator,
14    ClassNamePrefixFeaturesOutMixin,
15    TransformerMixin,
16    _fit_context,
17)
18from ..utils import check_array, check_random_state
19from ..utils._arpack import _init_arpack_v0
20from ..utils._param_validation import Interval, StrOptions
21from ..utils.extmath import _randomized_svd, safe_sparse_dot, svd_flip
22from ..utils.sparsefuncs import mean_variance_axis
23from ..utils.validation import check_is_fitted, validate_data
24
25__all__ = ["TruncatedSVD"]
26
27
28class TruncatedSVD(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator):
29    """Dimensionality reduction using truncated SVD (aka LSA).
30
31    This transformer performs linear dimensionality reduction by means of
32    truncated singular value decomposition (SVD). Contrary to PCA, this
33    estimator does not center the data before computing the singular value
34    decomposition. This means it can work with sparse matrices
35    efficiently.
36
37    In particular, truncated SVD works on term count/tf-idf matrices as
38    returned by the vectorizers in :mod:`sklearn.feature_extraction.text`. In
39    that context, it is known as latent semantic analysis (LSA).
40
41    This estimator supports two algorithms: a fast randomized SVD solver, and
42    a "naive" algorithm that uses ARPACK as an eigensolver on `X * X.T` or
43    `X.T * X`, whichever is more efficient.
44
45    Read more in the :ref:`User Guide <LSA>`.
46
47    Parameters
48    ----------
49    n_components : int, default=2
50        Desired dimensionality of output data.
51        If algorithm='arpack', must be strictly less than the number of features.
52        If algorithm='randomized', must be less than or equal to the number of features.
53        The default value is useful for visualisation. For LSA, a value of
54        100 is recommended.
55
56    algorithm : {'arpack', 'randomized'}, default='randomized'
57        SVD solver to use. Either "arpack" for the ARPACK wrapper in SciPy
58        (scipy.sparse.linalg.svds), or "randomized" for the randomized
59        algorithm due to Halko (2009).
60
61    n_iter : int, default=5
62        Number of iterations for randomized SVD solver. Not used by ARPACK. The
63        default is larger than the default in
64        :func:`~sklearn.utils.extmath.randomized_svd` to handle sparse
65        matrices that may have large slowly decaying spectrum.
66
67    n_oversamples : int, default=10
68        Number of oversamples for randomized SVD solver. Not used by ARPACK.
69        See :func:`~sklearn.utils.extmath.randomized_svd` for a complete
70        description.
71
72        .. versionadded:: 1.1
73
74    power_iteration_normalizer : {'auto', 'QR', 'LU', 'none'}, default='auto'
75        Power iteration normalizer for randomized SVD solver.
76        Not used by ARPACK. See :func:`~sklearn.utils.extmath.randomized_svd`
77        for more details.
78
79        .. versionadded:: 1.1
80
81    random_state : int, RandomState instance or None, default=None
82        Used during randomized svd. Pass an int for reproducible results across
83        multiple function calls.
84        See :term:`Glossary <random_state>`.
85
86    tol : float, default=0.0
87        Tolerance for ARPACK. 0 means machine precision. Ignored by randomized
88        SVD solver.
89
90    Attributes
91    ----------
92    components_ : ndarray of shape (n_components, n_features)
93        The right singular vectors of the input data.
94
95    explained_variance_ : ndarray of shape (n_components,)
96        The variance of the training samples transformed by a projection to
97        each component.
98
99    explained_variance_ratio_ : ndarray of shape (n_components,)
100        Percentage of variance explained by each of the selected components.
101
102    singular_values_ : ndarray of shape (n_components,)
103        The singular values corresponding to each of the selected components.
104        The singular values are equal to the 2-norms of the ``n_components``
105        variables in the lower-dimensional space.
106
107    n_features_in_ : int
108        Number of features seen during :term:`fit`.
109
110        .. versionadded:: 0.24
111
112    feature_names_in_ : ndarray of shape (`n_features_in_`,)
113        Names of features seen during :term:`fit`. Defined only when `X`
114        has feature names that are all strings.
115
116        .. versionadded:: 1.0
117
118    See Also
119    --------
120    DictionaryLearning : Find a dictionary that sparsely encodes data.
121    FactorAnalysis : A simple linear generative model with
122        Gaussian latent variables.
123    IncrementalPCA : Incremental principal components analysis.
124    KernelPCA : Kernel Principal component analysis.
125    NMF : Non-Negative Matrix Factorization.
126    PCA : Principal component analysis.
127
128    Notes
129    -----
130    SVD suffers from a problem called "sign indeterminacy", which means the
131    sign of the ``components_`` and the output from transform depend on the
132    algorithm and random state. To work around this, fit instances of this
133    class to data once, then keep the instance around to do transformations.
134
135    References
136    ----------
137    :arxiv:`Halko, et al. (2009). "Finding structure with randomness:
138    Stochastic algorithms for constructing approximate matrix decompositions"
139    <0909.4061>`
140
141    Examples
142    --------
143    >>> from sklearn.decomposition import TruncatedSVD
144    >>> from scipy.sparse import csr_matrix
145    >>> import numpy as np
146    >>> np.random.seed(0)
147    >>> X_dense = np.random.rand(100, 100)
148    >>> X_dense[:, 2 * np.arange(50)] = 0
149    >>> X = csr_matrix(X_dense)
150    >>> svd = TruncatedSVD(n_components=5, n_iter=7, random_state=42)
151    >>> svd.fit(X)
152    TruncatedSVD(n_components=5, n_iter=7, random_state=42)
153    >>> print(svd.explained_variance_ratio_)
154    [0.0157 0.0512 0.0499 0.0479 0.0453]
155    >>> print(svd.explained_variance_ratio_.sum())
156    0.2102
157    >>> print(svd.singular_values_)
158    [35.2410  4.5981   4.5420  4.4486  4.3288]
159    """
160
161    _parameter_constraints: dict = {
162        "n_components": [Interval(Integral, 1, None, closed="left")],
163        "algorithm": [StrOptions({"arpack", "randomized"})],
164        "n_iter": [Interval(Integral, 0, None, closed="left")],
165        "n_oversamples": [Interval(Integral, 1, None, closed="left")],
166        "power_iteration_normalizer": [StrOptions({"auto", "OR", "LU", "none"})],
167        "random_state": ["random_state"],
168        "tol": [Interval(Real, 0, None, closed="left")],
169    }
170
171    def __init__(
172        self,
173        n_components=2,
174        *,
175        algorithm="randomized",
176        n_iter=5,
177        n_oversamples=10,
178        power_iteration_normalizer="auto",
179        random_state=None,
180        tol=0.0,
181    ):
182        self.algorithm = algorithm
183        self.n_components = n_components
184        self.n_iter = n_iter
185        self.n_oversamples = n_oversamples
186        self.power_iteration_normalizer = power_iteration_normalizer
187        self.random_state = random_state
188        self.tol = tol
189
190    def fit(self, X, y=None):
191        """Fit model on training data X.
192
193        Parameters
194        ----------
195        X : {array-like, sparse matrix} of shape (n_samples, n_features)
196            Training data.
197
198        y : Ignored
199            Not used, present here for API consistency by convention.
200
201        Returns
202        -------
203        self : object
204            Returns the transformer object.
205        """
206        self.fit_transform(X)
207        return self
208
209    @_fit_context(prefer_skip_nested_validation=True)
210    def fit_transform(self, X, y=None):
211        """Fit model to X and perform dimensionality reduction on X.
212
213        Parameters
214        ----------
215        X : {array-like, sparse matrix} of shape (n_samples, n_features)
216            Training data.
217
218        y : Ignored
219            Not used, present here for API consistency by convention.
220
221        Returns
222        -------
223        X_new : ndarray of shape (n_samples, n_components)
224            Reduced version of X. This will always be a dense array.
225        """
226        X = validate_data(self, X, accept_sparse=["csr", "csc"], ensure_min_features=2)
227        random_state = check_random_state(self.random_state)
228
229        if self.algorithm == "arpack":
230            v0 = _init_arpack_v0(min(X.shape), random_state)
231            U, Sigma, VT = svds(X, k=self.n_components, tol=self.tol, v0=v0)
232            # svds doesn't abide by scipy.linalg.svd/randomized_svd
233            # conventions, so reverse its outputs.
234            Sigma = Sigma[::-1]
235            # u_based_decision=False is needed to be consistent with PCA.
236            U, VT = svd_flip(U[:, ::-1], VT[::-1], u_based_decision=False)
237
238        elif self.algorithm == "randomized":
239            if self.n_components > X.shape[1]:
240                raise ValueError(
241                    f"n_components({self.n_components}) must be <="
242                    f" n_features({X.shape[1]})."
243                )
244            U, Sigma, VT = _randomized_svd(
245                X,
246                self.n_components,
247                n_iter=self.n_iter,
248                n_oversamples=self.n_oversamples,
249                power_iteration_normalizer=self.power_iteration_normalizer,
250                random_state=random_state,
251                flip_sign=False,
252            )
253            U, VT = svd_flip(U, VT, u_based_decision=False)
254
255        self.components_ = VT
256
257        # As a result of the SVD approximation error on X ~ U @ Sigma @ V.T,
258        # X @ V is not the same as U @ Sigma
259        if self.algorithm == "randomized" or (
260            self.algorithm == "arpack" and self.tol > 0
261        ):
262            X_transformed = safe_sparse_dot(X, self.components_.T)
263        else:
264            X_transformed = U * Sigma
265
266        # Calculate explained variance & explained variance ratio
267        self.explained_variance_ = exp_var = np.var(X_transformed, axis=0)
268        if sp.issparse(X):
269            _, full_var = mean_variance_axis(X, axis=0)
270            full_var = full_var.sum()
271        else:
272            full_var = np.var(X, axis=0).sum()
273        self.explained_variance_ratio_ = exp_var / full_var
274        self.singular_values_ = Sigma  # Store the singular values.
275
276        return X_transformed
277
278    def transform(self, X):
279        """Perform dimensionality reduction on X.
280
281        Parameters
282        ----------
283        X : {array-like, sparse matrix} of shape (n_samples, n_features)
284            New data.
285
286        Returns
287        -------
288        X_new : ndarray of shape (n_samples, n_components)
289            Reduced version of X. This will always be a dense array.
290        """
291        check_is_fitted(self)
292        X = validate_data(self, X, accept_sparse=["csr", "csc"], reset=False)
293        return safe_sparse_dot(X, self.components_.T)
294
295    def inverse_transform(self, X):
296        """Transform X back to its original space.
297
298        Returns an array X_original whose transform would be X.
299
300        Parameters
301        ----------
302        X : array-like of shape (n_samples, n_components)
303            New data.
304
305        Returns
306        -------
307        X_original : ndarray of shape (n_samples, n_features)
308            Note that this is always a dense array.
309        """
310        X = check_array(X)
311        return np.dot(X, self.components_)
312
313    def __sklearn_tags__(self):
314        tags = super().__sklearn_tags__()
315        tags.input_tags.sparse = True
316        tags.transformer_tags.preserves_dtype = ["float64", "float32"]
317        return tags
318
319    @property
320    def _n_features_out(self):
321        """Number of transformed output features."""
322        return self.components_.shape[0]
323 
Aluode/PerceptionLabPortable · CoolFace