CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
kernel_approximation.py1107 linesDownload Raw Back to sklearn
1"""Approximate kernel feature maps based on Fourier transforms and count sketches."""
2
3# Authors: The scikit-learn developers
4# SPDX-License-Identifier: BSD-3-Clause
5
6import warnings
7from numbers import Integral, Real
8
9import numpy as np
10import scipy.sparse as sp
11from scipy.fft import fft, ifft
12from scipy.linalg import svd
13
14from .base import (
15    BaseEstimator,
16    ClassNamePrefixFeaturesOutMixin,
17    TransformerMixin,
18    _fit_context,
19)
20from .metrics.pairwise import KERNEL_PARAMS, PAIRWISE_KERNEL_FUNCTIONS, pairwise_kernels
21from .utils import check_random_state
22from .utils._param_validation import Interval, StrOptions
23from .utils.extmath import safe_sparse_dot
24from .utils.validation import (
25    _check_feature_names_in,
26    check_is_fitted,
27    validate_data,
28)
29
30
31class PolynomialCountSketch(
32    ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator
33):
34    """Polynomial kernel approximation via Tensor Sketch.
35
36    Implements Tensor Sketch, which approximates the feature map
37    of the polynomial kernel::
38
39        K(X, Y) = (gamma * <X, Y> + coef0)^degree
40
41    by efficiently computing a Count Sketch of the outer product of a
42    vector with itself using Fast Fourier Transforms (FFT). Read more in the
43    :ref:`User Guide <polynomial_kernel_approx>`.
44
45    .. versionadded:: 0.24
46
47    Parameters
48    ----------
49    gamma : float, default=1.0
50        Parameter of the polynomial kernel whose feature map
51        will be approximated.
52
53    degree : int, default=2
54        Degree of the polynomial kernel whose feature map
55        will be approximated.
56
57    coef0 : int, default=0
58        Constant term of the polynomial kernel whose feature map
59        will be approximated.
60
61    n_components : int, default=100
62        Dimensionality of the output feature space. Usually, `n_components`
63        should be greater than the number of features in input samples in
64        order to achieve good performance. The optimal score / run time
65        balance is typically achieved around `n_components` = 10 * `n_features`,
66        but this depends on the specific dataset being used.
67
68    random_state : int, RandomState instance, default=None
69        Determines random number generation for indexHash and bitHash
70        initialization. Pass an int for reproducible results across multiple
71        function calls. See :term:`Glossary <random_state>`.
72
73    Attributes
74    ----------
75    indexHash_ : ndarray of shape (degree, n_features), dtype=int64
76        Array of indexes in range [0, n_components) used to represent
77        the 2-wise independent hash functions for Count Sketch computation.
78
79    bitHash_ : ndarray of shape (degree, n_features), dtype=float32
80        Array with random entries in {+1, -1}, used to represent
81        the 2-wise independent hash functions for Count Sketch computation.
82
83    n_features_in_ : int
84        Number of features seen during :term:`fit`.
85
86        .. versionadded:: 0.24
87
88    feature_names_in_ : ndarray of shape (`n_features_in_`,)
89        Names of features seen during :term:`fit`. Defined only when `X`
90        has feature names that are all strings.
91
92        .. versionadded:: 1.0
93
94    See Also
95    --------
96    AdditiveChi2Sampler : Approximate feature map for additive chi2 kernel.
97    Nystroem : Approximate a kernel map using a subset of the training data.
98    RBFSampler : Approximate a RBF kernel feature map using random Fourier
99        features.
100    SkewedChi2Sampler : Approximate feature map for "skewed chi-squared" kernel.
101    sklearn.metrics.pairwise.kernel_metrics : List of built-in kernels.
102
103    Examples
104    --------
105    >>> from sklearn.kernel_approximation import PolynomialCountSketch
106    >>> from sklearn.linear_model import SGDClassifier
107    >>> X = [[0, 0], [1, 1], [1, 0], [0, 1]]
108    >>> y = [0, 0, 1, 1]
109    >>> ps = PolynomialCountSketch(degree=3, random_state=1)
110    >>> X_features = ps.fit_transform(X)
111    >>> clf = SGDClassifier(max_iter=10, tol=1e-3)
112    >>> clf.fit(X_features, y)
113    SGDClassifier(max_iter=10)
114    >>> clf.score(X_features, y)
115    1.0
116
117    For a more detailed example of usage, see
118    :ref:`sphx_glr_auto_examples_kernel_approximation_plot_scalable_poly_kernels.py`
119    """
120
121    _parameter_constraints: dict = {
122        "gamma": [Interval(Real, 0, None, closed="left")],
123        "degree": [Interval(Integral, 1, None, closed="left")],
124        "coef0": [Interval(Real, None, None, closed="neither")],
125        "n_components": [Interval(Integral, 1, None, closed="left")],
126        "random_state": ["random_state"],
127    }
128
129    def __init__(
130        self, *, gamma=1.0, degree=2, coef0=0, n_components=100, random_state=None
131    ):
132        self.gamma = gamma
133        self.degree = degree
134        self.coef0 = coef0
135        self.n_components = n_components
136        self.random_state = random_state
137
138    @_fit_context(prefer_skip_nested_validation=True)
139    def fit(self, X, y=None):
140        """Fit the model with X.
141
142        Initializes the internal variables. The method needs no information
143        about the distribution of data, so we only care about n_features in X.
144
145        Parameters
146        ----------
147        X : {array-like, sparse matrix} of shape (n_samples, n_features)
148            Training data, where `n_samples` is the number of samples
149            and `n_features` is the number of features.
150
151        y : array-like of shape (n_samples,) or (n_samples, n_outputs), \
152                default=None
153            Target values (None for unsupervised transformations).
154
155        Returns
156        -------
157        self : object
158            Returns the instance itself.
159        """
160        X = validate_data(self, X, accept_sparse="csc")
161        random_state = check_random_state(self.random_state)
162
163        n_features = X.shape[1]
164        if self.coef0 != 0:
165            n_features += 1
166
167        self.indexHash_ = random_state.randint(
168            0, high=self.n_components, size=(self.degree, n_features)
169        )
170
171        self.bitHash_ = random_state.choice(a=[-1, 1], size=(self.degree, n_features))
172        self._n_features_out = self.n_components
173        return self
174
175    def transform(self, X):
176        """Generate the feature map approximation for X.
177
178        Parameters
179        ----------
180        X : {array-like}, shape (n_samples, n_features)
181            New data, where `n_samples` is the number of samples
182            and `n_features` is the number of features.
183
184        Returns
185        -------
186        X_new : array-like, shape (n_samples, n_components)
187            Returns the instance itself.
188        """
189
190        check_is_fitted(self)
191        X = validate_data(self, X, accept_sparse="csc", reset=False)
192
193        X_gamma = np.sqrt(self.gamma) * X
194
195        if sp.issparse(X_gamma) and self.coef0 != 0:
196            X_gamma = sp.hstack(
197                [X_gamma, np.sqrt(self.coef0) * np.ones((X_gamma.shape[0], 1))],
198                format="csc",
199            )
200
201        elif not sp.issparse(X_gamma) and self.coef0 != 0:
202            X_gamma = np.hstack(
203                [X_gamma, np.sqrt(self.coef0) * np.ones((X_gamma.shape[0], 1))]
204            )
205
206        if X_gamma.shape[1] != self.indexHash_.shape[1]:
207            raise ValueError(
208                "Number of features of test samples does not"
209                " match that of training samples."
210            )
211
212        count_sketches = np.zeros((X_gamma.shape[0], self.degree, self.n_components))
213
214        if sp.issparse(X_gamma):
215            for j in range(X_gamma.shape[1]):
216                for d in range(self.degree):
217                    iHashIndex = self.indexHash_[d, j]
218                    iHashBit = self.bitHash_[d, j]
219                    count_sketches[:, d, iHashIndex] += (
220                        (iHashBit * X_gamma[:, [j]]).toarray().ravel()
221                    )
222
223        else:
224            for j in range(X_gamma.shape[1]):
225                for d in range(self.degree):
226                    iHashIndex = self.indexHash_[d, j]
227                    iHashBit = self.bitHash_[d, j]
228                    count_sketches[:, d, iHashIndex] += iHashBit * X_gamma[:, j]
229
230        # For each same, compute a count sketch of phi(x) using the polynomial
231        # multiplication (via FFT) of p count sketches of x.
232        count_sketches_fft = fft(count_sketches, axis=2, overwrite_x=True)
233        count_sketches_fft_prod = np.prod(count_sketches_fft, axis=1)
234        data_sketch = np.real(ifft(count_sketches_fft_prod, overwrite_x=True))
235
236        return data_sketch
237
238    def __sklearn_tags__(self):
239        tags = super().__sklearn_tags__()
240        tags.input_tags.sparse = True
241        return tags
242
243
244class RBFSampler(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator):
245    """Approximate a RBF kernel feature map using random Fourier features.
246
247    It implements a variant of Random Kitchen Sinks.[1]
248
249    Read more in the :ref:`User Guide <rbf_kernel_approx>`.
250
251    Parameters
252    ----------
253    gamma : 'scale' or float, default=1.0
254        Parameter of RBF kernel: exp(-gamma * x^2).
255        If ``gamma='scale'`` is passed then it uses
256        1 / (n_features * X.var()) as value of gamma.
257
258        .. versionadded:: 1.2
259           The option `"scale"` was added in 1.2.
260
261    n_components : int, default=100
262        Number of Monte Carlo samples per original feature.
263        Equals the dimensionality of the computed feature space.
264
265    random_state : int, RandomState instance or None, default=None
266        Pseudo-random number generator to control the generation of the random
267        weights and random offset when fitting the training data.
268        Pass an int for reproducible output across multiple function calls.
269        See :term:`Glossary <random_state>`.
270
271    Attributes
272    ----------
273    random_offset_ : ndarray of shape (n_components,), dtype={np.float64, np.float32}
274        Random offset used to compute the projection in the `n_components`
275        dimensions of the feature space.
276
277    random_weights_ : ndarray of shape (n_features, n_components),\
278        dtype={np.float64, np.float32}
279        Random projection directions drawn from the Fourier transform
280        of the RBF kernel.
281
282    n_features_in_ : int
283        Number of features seen during :term:`fit`.
284
285        .. versionadded:: 0.24
286
287    feature_names_in_ : ndarray of shape (`n_features_in_`,)
288        Names of features seen during :term:`fit`. Defined only when `X`
289        has feature names that are all strings.
290
291        .. versionadded:: 1.0
292
293    See Also
294    --------
295    AdditiveChi2Sampler : Approximate feature map for additive chi2 kernel.
296    Nystroem : Approximate a kernel map using a subset of the training data.
297    PolynomialCountSketch : Polynomial kernel approximation via Tensor Sketch.
298    SkewedChi2Sampler : Approximate feature map for
299        "skewed chi-squared" kernel.
300    sklearn.metrics.pairwise.kernel_metrics : List of built-in kernels.
301
302    Notes
303    -----
304    See "Random Features for Large-Scale Kernel Machines" by A. Rahimi and
305    Benjamin Recht.
306
307    [1] "Weighted Sums of Random Kitchen Sinks: Replacing
308    minimization with randomization in learning" by A. Rahimi and
309    Benjamin Recht.
310    (https://people.eecs.berkeley.edu/~brecht/papers/08.rah.rec.nips.pdf)
311
312    Examples
313    --------
314    >>> from sklearn.kernel_approximation import RBFSampler
315    >>> from sklearn.linear_model import SGDClassifier
316    >>> X = [[0, 0], [1, 1], [1, 0], [0, 1]]
317    >>> y = [0, 0, 1, 1]
318    >>> rbf_feature = RBFSampler(gamma=1, random_state=1)
319    >>> X_features = rbf_feature.fit_transform(X)
320    >>> clf = SGDClassifier(max_iter=5, tol=1e-3)
321    >>> clf.fit(X_features, y)
322    SGDClassifier(max_iter=5)
323    >>> clf.score(X_features, y)
324    1.0
325    """
326
327    _parameter_constraints: dict = {
328        "gamma": [
329            StrOptions({"scale"}),
330            Interval(Real, 0.0, None, closed="left"),
331        ],
332        "n_components": [Interval(Integral, 1, None, closed="left")],
333        "random_state": ["random_state"],
334    }
335
336    def __init__(self, *, gamma=1.0, n_components=100, random_state=None):
337        self.gamma = gamma
338        self.n_components = n_components
339        self.random_state = random_state
340
341    @_fit_context(prefer_skip_nested_validation=True)
342    def fit(self, X, y=None):
343        """Fit the model with X.
344
345        Samples random projection according to n_features.
346
347        Parameters
348        ----------
349        X : {array-like, sparse matrix}, shape (n_samples, n_features)
350            Training data, where `n_samples` is the number of samples
351            and `n_features` is the number of features.
352
353        y : array-like, shape (n_samples,) or (n_samples, n_outputs), \
354                default=None
355            Target values (None for unsupervised transformations).
356
357        Returns
358        -------
359        self : object
360            Returns the instance itself.
361        """
362        X = validate_data(self, X, accept_sparse="csr")
363        random_state = check_random_state(self.random_state)
364        n_features = X.shape[1]
365        sparse = sp.issparse(X)
366        if self.gamma == "scale":
367            # var = E[X^2] - E[X]^2 if sparse
368            X_var = (X.multiply(X)).mean() - (X.mean()) ** 2 if sparse else X.var()
369            self._gamma = 1.0 / (n_features * X_var) if X_var != 0 else 1.0
370        else:
371            self._gamma = self.gamma
372        self.random_weights_ = (2.0 * self._gamma) ** 0.5 * random_state.normal(
373            size=(n_features, self.n_components)
374        )
375
376        self.random_offset_ = random_state.uniform(0, 2 * np.pi, size=self.n_components)
377
378        if X.dtype == np.float32:
379            # Setting the data type of the fitted attribute will ensure the
380            # output data type during `transform`.
381            self.random_weights_ = self.random_weights_.astype(X.dtype, copy=False)
382            self.random_offset_ = self.random_offset_.astype(X.dtype, copy=False)
383
384        self._n_features_out = self.n_components
385        return self
386
387    def transform(self, X):
388        """Apply the approximate feature map to X.
389
390        Parameters
391        ----------
392        X : {array-like, sparse matrix}, shape (n_samples, n_features)
393            New data, where `n_samples` is the number of samples
394            and `n_features` is the number of features.
395
396        Returns
397        -------
398        X_new : array-like, shape (n_samples, n_components)
399            Returns the instance itself.
400        """
401        check_is_fitted(self)
402
403        X = validate_data(self, X, accept_sparse="csr", reset=False)
404        projection = safe_sparse_dot(X, self.random_weights_)
405        projection += self.random_offset_
406        np.cos(projection, projection)
407        projection *= (2.0 / self.n_components) ** 0.5
408        return projection
409
410    def __sklearn_tags__(self):
411        tags = super().__sklearn_tags__()
412        tags.input_tags.sparse = True
413        tags.transformer_tags.preserves_dtype = ["float64", "float32"]
414        return tags
415
416
417class SkewedChi2Sampler(
418    ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator
419):
420    """Approximate feature map for "skewed chi-squared" kernel.
421
422    Read more in the :ref:`User Guide <skewed_chi_kernel_approx>`.
423
424    Parameters
425    ----------
426    skewedness : float, default=1.0
427        "skewedness" parameter of the kernel. Needs to be cross-validated.
428
429    n_components : int, default=100
430        Number of Monte Carlo samples per original feature.
431        Equals the dimensionality of the computed feature space.
432
433    random_state : int, RandomState instance or None, default=None
434        Pseudo-random number generator to control the generation of the random
435        weights and random offset when fitting the training data.
436        Pass an int for reproducible output across multiple function calls.
437        See :term:`Glossary <random_state>`.
438
439    Attributes
440    ----------
441    random_weights_ : ndarray of shape (n_features, n_components)
442        Weight array, sampled from a secant hyperbolic distribution, which will
443        be used to linearly transform the log of the data.
444
445    random_offset_ : ndarray of shape (n_features, n_components)
446        Bias term, which will be added to the data. It is uniformly distributed
447        between 0 and 2*pi.
448
449    n_features_in_ : int
450        Number of features seen during :term:`fit`.
451
452        .. versionadded:: 0.24
453
454    feature_names_in_ : ndarray of shape (`n_features_in_`,)
455        Names of features seen during :term:`fit`. Defined only when `X`
456        has feature names that are all strings.
457
458        .. versionadded:: 1.0
459
460    See Also
461    --------
462    AdditiveChi2Sampler : Approximate feature map for additive chi2 kernel.
463    Nystroem : Approximate a kernel map using a subset of the training data.
464    RBFSampler : Approximate a RBF kernel feature map using random Fourier
465        features.
466    SkewedChi2Sampler : Approximate feature map for "skewed chi-squared" kernel.
467    sklearn.metrics.pairwise.chi2_kernel : The exact chi squared kernel.
468    sklearn.metrics.pairwise.kernel_metrics : List of built-in kernels.
469
470    References
471    ----------
472    See "Random Fourier Approximations for Skewed Multiplicative Histogram
473    Kernels" by Fuxin Li, Catalin Ionescu and Cristian Sminchisescu.
474
475    Examples
476    --------
477    >>> from sklearn.kernel_approximation import SkewedChi2Sampler
478    >>> from sklearn.linear_model import SGDClassifier
479    >>> X = [[0, 0], [1, 1], [1, 0], [0, 1]]
480    >>> y = [0, 0, 1, 1]
481    >>> chi2_feature = SkewedChi2Sampler(skewedness=.01,
482    ...                                  n_components=10,
483    ...                                  random_state=0)
484    >>> X_features = chi2_feature.fit_transform(X, y)
485    >>> clf = SGDClassifier(max_iter=10, tol=1e-3)
486    >>> clf.fit(X_features, y)
487    SGDClassifier(max_iter=10)
488    >>> clf.score(X_features, y)
489    1.0
490    """
491
492    _parameter_constraints: dict = {
493        "skewedness": [Interval(Real, None, None, closed="neither")],
494        "n_components": [Interval(Integral, 1, None, closed="left")],
495        "random_state": ["random_state"],
496    }
497
498    def __init__(self, *, skewedness=1.0, n_components=100, random_state=None):
499        self.skewedness = skewedness
500        self.n_components = n_components
501        self.random_state = random_state
502
503    @_fit_context(prefer_skip_nested_validation=True)
504    def fit(self, X, y=None):
505        """Fit the model with X.
506
507        Samples random projection according to n_features.
508
509        Parameters
510        ----------
511        X : array-like, shape (n_samples, n_features)
512            Training data, where `n_samples` is the number of samples
513            and `n_features` is the number of features.
514
515        y : array-like, shape (n_samples,) or (n_samples, n_outputs), \
516                default=None
517            Target values (None for unsupervised transformations).
518
519        Returns
520        -------
521        self : object
522            Returns the instance itself.
523        """
524        X = validate_data(self, X)
525        random_state = check_random_state(self.random_state)
526        n_features = X.shape[1]
527        uniform = random_state.uniform(size=(n_features, self.n_components))
528        # transform by inverse CDF of sech
529        self.random_weights_ = 1.0 / np.pi * np.log(np.tan(np.pi / 2.0 * uniform))
530        self.random_offset_ = random_state.uniform(0, 2 * np.pi, size=self.n_components)
531
532        if X.dtype == np.float32:
533            # Setting the data type of the fitted attribute will ensure the
534            # output data type during `transform`.
535            self.random_weights_ = self.random_weights_.astype(X.dtype, copy=False)
536            self.random_offset_ = self.random_offset_.astype(X.dtype, copy=False)
537
538        self._n_features_out = self.n_components
539        return self
540
541    def transform(self, X):
542        """Apply the approximate feature map to X.
543
544        Parameters
545        ----------
546        X : array-like, shape (n_samples, n_features)
547            New data, where `n_samples` is the number of samples
548            and `n_features` is the number of features. All values of X must be
549            strictly greater than "-skewedness".
550
551        Returns
552        -------
553        X_new : array-like, shape (n_samples, n_components)
554            Returns the instance itself.
555        """
556        check_is_fitted(self)
557        X = validate_data(
558            self, X, copy=True, dtype=[np.float64, np.float32], reset=False
559        )
560        if (X <= -self.skewedness).any():
561            raise ValueError("X may not contain entries smaller than -skewedness.")
562
563        X += self.skewedness
564        np.log(X, X)
565        projection = safe_sparse_dot(X, self.random_weights_)
566        projection += self.random_offset_
567        np.cos(projection, projection)
568        projection *= np.sqrt(2.0) / np.sqrt(self.n_components)
569        return projection
570
571    def __sklearn_tags__(self):
572        tags = super().__sklearn_tags__()
573        tags.transformer_tags.preserves_dtype = ["float64", "float32"]
574        return tags
575
576
577class AdditiveChi2Sampler(TransformerMixin, BaseEstimator):
578    """Approximate feature map for additive chi2 kernel.
579
580    Uses sampling the fourier transform of the kernel characteristic
581    at regular intervals.
582
583    Since the kernel that is to be approximated is additive, the components of
584    the input vectors can be treated separately.  Each entry in the original
585    space is transformed into 2*sample_steps-1 features, where sample_steps is
586    a parameter of the method. Typical values of sample_steps include 1, 2 and
587    3.
588
589    Optimal choices for the sampling interval for certain data ranges can be
590    computed (see the reference). The default values should be reasonable.
591
592    Read more in the :ref:`User Guide <additive_chi_kernel_approx>`.
593
594    Parameters
595    ----------
596    sample_steps : int, default=2
597        Gives the number of (complex) sampling points.
598
599    sample_interval : float, default=None
600        Sampling interval. Must be specified when sample_steps not in {1,2,3}.
601
602    Attributes
603    ----------
604    n_features_in_ : int
605        Number of features seen during :term:`fit`.
606
607        .. versionadded:: 0.24
608
609    feature_names_in_ : ndarray of shape (`n_features_in_`,)
610        Names of features seen during :term:`fit`. Defined only when `X`
611        has feature names that are all strings.
612
613        .. versionadded:: 1.0
614
615    See Also
616    --------
617    SkewedChi2Sampler : A Fourier-approximation to a non-additive variant of
618        the chi squared kernel.
619
620    sklearn.metrics.pairwise.chi2_kernel : The exact chi squared kernel.
621
622    sklearn.metrics.pairwise.additive_chi2_kernel : The exact additive chi
623        squared kernel.
624
625    Notes
626    -----
627    This estimator approximates a slightly different version of the additive
628    chi squared kernel then ``metric.additive_chi2`` computes.
629
630    This estimator is stateless and does not need to be fitted. However, we
631    recommend to call :meth:`fit_transform` instead of :meth:`transform`, as
632    parameter validation is only performed in :meth:`fit`.
633
634    References
635    ----------
636    See `"Efficient additive kernels via explicit feature maps"
637    <http://www.robots.ox.ac.uk/~vedaldi/assets/pubs/vedaldi11efficient.pdf>`_
638    A. Vedaldi and A. Zisserman, Pattern Analysis and Machine Intelligence,
639    2011
640
641    Examples
642    --------
643    >>> from sklearn.datasets import load_digits
644    >>> from sklearn.linear_model import SGDClassifier
645    >>> from sklearn.kernel_approximation import AdditiveChi2Sampler
646    >>> X, y = load_digits(return_X_y=True)
647    >>> chi2sampler = AdditiveChi2Sampler(sample_steps=2)
648    >>> X_transformed = chi2sampler.fit_transform(X, y)
649    >>> clf = SGDClassifier(max_iter=5, random_state=0, tol=1e-3)
650    >>> clf.fit(X_transformed, y)
651    SGDClassifier(max_iter=5, random_state=0)
652    >>> clf.score(X_transformed, y)
653    0.9499...
654    """
655
656    _parameter_constraints: dict = {
657        "sample_steps": [Interval(Integral, 1, None, closed="left")],
658        "sample_interval": [Interval(Real, 0, None, closed="left"), None],
659    }
660
661    def __init__(self, *, sample_steps=2, sample_interval=None):
662        self.sample_steps = sample_steps
663        self.sample_interval = sample_interval
664
665    @_fit_context(prefer_skip_nested_validation=True)
666    def fit(self, X, y=None):
667        """Only validates estimator's parameters.
668
669        This method allows to: (i) validate the estimator's parameters and
670        (ii) be consistent with the scikit-learn transformer API.
671
672        Parameters
673        ----------
674        X : array-like, shape (n_samples, n_features)
675            Training data, where `n_samples` is the number of samples
676            and `n_features` is the number of features.
677
678        y : array-like, shape (n_samples,) or (n_samples, n_outputs), \
679                default=None
680            Target values (None for unsupervised transformations).
681
682        Returns
683        -------
684        self : object
685            Returns the transformer.
686        """
687        X = validate_data(self, X, accept_sparse="csr", ensure_non_negative=True)
688
689        if self.sample_interval is None and self.sample_steps not in (1, 2, 3):
690            raise ValueError(
691                "If sample_steps is not in [1, 2, 3],"
692                " you need to provide sample_interval"
693            )
694
695        return self
696
697    def transform(self, X):
698        """Apply approximate feature map to X.
699
700        Parameters
701        ----------
702        X : {array-like, sparse matrix}, shape (n_samples, n_features)
703            Training data, where `n_samples` is the number of samples
704            and `n_features` is the number of features.
705
706        Returns
707        -------
708        X_new : {ndarray, sparse matrix}, \
709               shape = (n_samples, n_features * (2*sample_steps - 1))
710            Whether the return value is an array or sparse matrix depends on
711            the type of the input X.
712        """
713        X = validate_data(
714            self, X, accept_sparse="csr", reset=False, ensure_non_negative=True
715        )
716        sparse = sp.issparse(X)
717
718        if self.sample_interval is None:
719            # See figure 2 c) of "Efficient additive kernels via explicit feature maps"
720            # <http://www.robots.ox.ac.uk/~vedaldi/assets/pubs/vedaldi11efficient.pdf>
721            # A. Vedaldi and A. Zisserman, Pattern Analysis and Machine Intelligence,
722            # 2011
723            if self.sample_steps == 1:
724                sample_interval = 0.8
725            elif self.sample_steps == 2:
726                sample_interval = 0.5
727            elif self.sample_steps == 3:
728                sample_interval = 0.4
729            else:
730                raise ValueError(
731                    "If sample_steps is not in [1, 2, 3],"
732                    " you need to provide sample_interval"
733                )
734        else:
735            sample_interval = self.sample_interval
736
737        # zeroth component
738        # 1/cosh = sech
739        # cosh(0) = 1.0
740        transf = self._transform_sparse if sparse else self._transform_dense
741        return transf(X, self.sample_steps, sample_interval)
742
743    def get_feature_names_out(self, input_features=None):
744        """Get output feature names for transformation.
745
746        Parameters
747        ----------
748        input_features : array-like of str or None, default=None
749            Only used to validate feature names with the names seen in :meth:`fit`.
750
751        Returns
752        -------
753        feature_names_out : ndarray of str objects
754            Transformed feature names.
755        """
756        # Note that passing attributes="n_features_in_" forces check_is_fitted
757        # to check if the attribute is present. Otherwise it will pass on this
758        # stateless estimator (requires_fit=False)
759        check_is_fitted(self, attributes="n_features_in_")
760        input_features = _check_feature_names_in(
761            self, input_features, generate_names=True
762        )
763        est_name = self.__class__.__name__.lower()
764
765        names_list = [f"{est_name}_{name}_sqrt" for name in input_features]
766
767        for j in range(1, self.sample_steps):
768            cos_names = [f"{est_name}_{name}_cos{j}" for name in input_features]
769            sin_names = [f"{est_name}_{name}_sin{j}" for name in input_features]
770            names_list.extend(cos_names + sin_names)
771
772        return np.asarray(names_list, dtype=object)
773
774    @staticmethod
775    def _transform_dense(X, sample_steps, sample_interval):
776        non_zero = X != 0.0
777        X_nz = X[non_zero]
778
779        X_step = np.zeros_like(X)
780        X_step[non_zero] = np.sqrt(X_nz * sample_interval)
781
782        X_new = [X_step]
783
784        log_step_nz = sample_interval * np.log(X_nz)
785        step_nz = 2 * X_nz * sample_interval
786
787        for j in range(1, sample_steps):
788            factor_nz = np.sqrt(step_nz / np.cosh(np.pi * j * sample_interval))
789
790            X_step = np.zeros_like(X)
791            X_step[non_zero] = factor_nz * np.cos(j * log_step_nz)
792            X_new.append(X_step)
793
794            X_step = np.zeros_like(X)
795            X_step[non_zero] = factor_nz * np.sin(j * log_step_nz)
796            X_new.append(X_step)
797
798        return np.hstack(X_new)
799
800    @staticmethod
801    def _transform_sparse(X, sample_steps, sample_interval):
802        indices = X.indices.copy()
803        indptr = X.indptr.copy()
804
805        data_step = np.sqrt(X.data * sample_interval)
806        X_step = sp.csr_matrix(
807            (data_step, indices, indptr), shape=X.shape, dtype=X.dtype, copy=False
808        )
809        X_new = [X_step]
810
811        log_step_nz = sample_interval * np.log(X.data)
812        step_nz = 2 * X.data * sample_interval
813
814        for j in range(1, sample_steps):
815            factor_nz = np.sqrt(step_nz / np.cosh(np.pi * j * sample_interval))
816
817            data_step = factor_nz * np.cos(j * log_step_nz)
818            X_step = sp.csr_matrix(
819                (data_step, indices, indptr), shape=X.shape, dtype=X.dtype, copy=False
820            )
821            X_new.append(X_step)
822
823            data_step = factor_nz * np.sin(j * log_step_nz)
824            X_step = sp.csr_matrix(
825                (data_step, indices, indptr), shape=X.shape, dtype=X.dtype, copy=False
826            )
827            X_new.append(X_step)
828
829        return sp.hstack(X_new)
830
831    def __sklearn_tags__(self):
832        tags = super().__sklearn_tags__()
833        tags.requires_fit = False
834        tags.input_tags.positive_only = True
835        tags.input_tags.sparse = True
836        return tags
837
838
839class Nystroem(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator):
840    """Approximate a kernel map using a subset of the training data.
841
842    Constructs an approximate feature map for an arbitrary kernel
843    using a subset of the data as basis.
844
845    Read more in the :ref:`User Guide <nystroem_kernel_approx>`.
846
847    .. versionadded:: 0.13
848
849    Parameters
850    ----------
851    kernel : str or callable, default='rbf'
852        Kernel map to be approximated. A callable should accept two arguments
853        and the keyword arguments passed to this object as `kernel_params`, and
854        should return a floating point number.
855
856    gamma : float, default=None
857        Gamma parameter for the RBF, laplacian, polynomial, exponential chi2
858        and sigmoid kernels. Interpretation of the default value is left to
859        the kernel; see the documentation for sklearn.metrics.pairwise.
860        Ignored by other kernels.
861
862    coef0 : float, default=None
863        Zero coefficient for polynomial and sigmoid kernels.
864        Ignored by other kernels.
865
866    degree : float, default=None
867        Degree of the polynomial kernel. Ignored by other kernels.
868
869    kernel_params : dict, default=None
870        Additional parameters (keyword arguments) for kernel function passed
871        as callable object.
872
873    n_components : int, default=100
874        Number of features to construct.
875        How many data points will be used to construct the mapping.
876
877    random_state : int, RandomState instance or None, default=None
878        Pseudo-random number generator to control the uniform sampling without
879        replacement of `n_components` of the training data to construct the
880        basis kernel.
881        Pass an int for reproducible output across multiple function calls.
882        See :term:`Glossary <random_state>`.
883
884    n_jobs : int, default=None
885        The number of jobs to use for the computation. This works by breaking
886        down the kernel matrix into `n_jobs` even slices and computing them in
887        parallel.
888
889        ``None`` means 1 unless in a :obj:`joblib.parallel_backend` context.
890        ``-1`` means using all processors. See :term:`Glossary <n_jobs>`
891        for more details.
892
893        .. versionadded:: 0.24
894
895    Attributes
896    ----------
897    components_ : ndarray of shape (n_components, n_features)
898        Subset of training points used to construct the feature map.
899
900    component_indices_ : ndarray of shape (n_components)
901        Indices of ``components_`` in the training set.
902
903    normalization_ : ndarray of shape (n_components, n_components)
904        Normalization matrix needed for embedding.
905        Square root of the kernel matrix on ``components_``.
906
907    n_features_in_ : int
908        Number of features seen during :term:`fit`.
909
910        .. versionadded:: 0.24
911
912    feature_names_in_ : ndarray of shape (`n_features_in_`,)
913        Names of features seen during :term:`fit`. Defined only when `X`
914        has feature names that are all strings.
915
916        .. versionadded:: 1.0
917
918    See Also
919    --------
920    AdditiveChi2Sampler : Approximate feature map for additive chi2 kernel.
921    PolynomialCountSketch : Polynomial kernel approximation via Tensor Sketch.
922    RBFSampler : Approximate a RBF kernel feature map using random Fourier
923        features.
924    SkewedChi2Sampler : Approximate feature map for "skewed chi-squared" kernel.
925    sklearn.metrics.pairwise.kernel_metrics : List of built-in kernels.
926
927    References
928    ----------
929    * Williams, C.K.I. and Seeger, M.
930      "Using the Nystroem method to speed up kernel machines",
931      Advances in neural information processing systems 2001
932
933    * T. Yang, Y. Li, M. Mahdavi, R. Jin and Z. Zhou
934      "Nystroem Method vs Random Fourier Features: A Theoretical and Empirical
935      Comparison",
936      Advances in Neural Information Processing Systems 2012
937
938    Examples
939    --------
940    >>> from sklearn import datasets, svm
941    >>> from sklearn.kernel_approximation import Nystroem
942    >>> X, y = datasets.load_digits(n_class=9, return_X_y=True)
943    >>> data = X / 16.
944    >>> clf = svm.LinearSVC()
945    >>> feature_map_nystroem = Nystroem(gamma=.2,
946    ...                                 random_state=1,
947    ...                                 n_components=300)
948    >>> data_transformed = feature_map_nystroem.fit_transform(data)
949    >>> clf.fit(data_transformed, y)
950    LinearSVC()
951    >>> clf.score(data_transformed, y)
952    0.9987...
953    """
954
955    _parameter_constraints: dict = {
956        "kernel": [
957            StrOptions(set(PAIRWISE_KERNEL_FUNCTIONS.keys()) | {"precomputed"}),
958            callable,
959        ],
960        "gamma": [Interval(Real, 0, None, closed="left"), None],
961        "coef0": [Interval(Real, None, None, closed="neither"), None],
962        "degree": [Interval(Real, 1, None, closed="left"), None],
963        "kernel_params": [dict, None],
964        "n_components": [Interval(Integral, 1, None, closed="left")],
965        "random_state": ["random_state"],
966        "n_jobs": [Integral, None],
967    }
968
969    def __init__(
970        self,
971        kernel="rbf",
972        *,
973        gamma=None,
974        coef0=None,
975        degree=None,
976        kernel_params=None,
977        n_components=100,
978        random_state=None,
979        n_jobs=None,
980    ):
981        self.kernel = kernel
982        self.gamma = gamma
983        self.coef0 = coef0
984        self.degree = degree
985        self.kernel_params = kernel_params
986        self.n_components = n_components
987        self.random_state = random_state
988        self.n_jobs = n_jobs
989
990    @_fit_context(prefer_skip_nested_validation=True)
991    def fit(self, X, y=None):
992        """Fit estimator to data.
993
994        Samples a subset of training points, computes kernel
995        on these and computes normalization matrix.
996
997        Parameters
998        ----------
999        X : array-like, shape (n_samples, n_features)
1000            Training data, where `n_samples` is the number of samples
1001            and `n_features` is the number of features.
1002
1003        y : array-like, shape (n_samples,) or (n_samples, n_outputs), \
1004                default=None
1005            Target values (None for unsupervised transformations).
1006
1007        Returns
1008        -------
1009        self : object
1010            Returns the instance itself.
1011        """
1012        X = validate_data(self, X, accept_sparse="csr")
1013        rnd = check_random_state(self.random_state)
1014        n_samples = X.shape[0]
1015
1016        # get basis vectors
1017        if self.n_components > n_samples:
1018            # XXX should we just bail?
1019            n_components = n_samples
1020            warnings.warn(
1021                "n_components > n_samples. This is not possible.\n"
1022                "n_components was set to n_samples, which results"
1023                " in inefficient evaluation of the full kernel."
1024            )
1025
1026        else:
1027            n_components = self.n_components
1028        n_components = min(n_samples, n_components)
1029        inds = rnd.permutation(n_samples)
1030        basis_inds = inds[:n_components]
1031        basis = X[basis_inds]
1032
1033        basis_kernel = pairwise_kernels(
1034            basis,
1035            metric=self.kernel,
1036            filter_params=True,
1037            n_jobs=self.n_jobs,
1038            **self._get_kernel_params(),
1039        )
1040
1041        # sqrt of kernel matrix on basis vectors
1042        U, S, V = svd(basis_kernel)
1043        S = np.maximum(S, 1e-12)
1044        self.normalization_ = np.dot(U / np.sqrt(S), V)
1045        self.components_ = basis
1046        self.component_indices_ = basis_inds
1047        self._n_features_out = n_components
1048        return self
1049
1050    def transform(self, X):
1051        """Apply feature map to X.
1052
1053        Computes an approximate feature map using the kernel
1054        between some training points and X.
1055
1056        Parameters
1057        ----------
1058        X : array-like of shape (n_samples, n_features)
1059            Data to transform.
1060
1061        Returns
1062        -------
1063        X_transformed : ndarray of shape (n_samples, n_components)
1064            Transformed data.
1065        """
1066        check_is_fitted(self)
1067        X = validate_data(self, X, accept_sparse="csr", reset=False)
1068
1069        kernel_params = self._get_kernel_params()
1070        embedded = pairwise_kernels(
1071            X,
1072            self.components_,
1073            metric=self.kernel,
1074            filter_params=True,
1075            n_jobs=self.n_jobs,
1076            **kernel_params,
1077        )
1078        return np.dot(embedded, self.normalization_.T)
1079
1080    def _get_kernel_params(self):
1081        params = self.kernel_params
1082        if params is None:
1083            params = {}
1084        if not callable(self.kernel) and self.kernel != "precomputed":
1085            for param in KERNEL_PARAMS[self.kernel]:
1086                if getattr(self, param) is not None:
1087                    params[param] = getattr(self, param)
1088        else:
1089            if (
1090                self.gamma is not None
1091                or self.coef0 is not None
1092                or self.degree is not None
1093            ):
1094                raise ValueError(
1095                    "Don't pass gamma, coef0 or degree to "
1096                    "Nystroem if using a callable "
1097                    "or precomputed kernel"
1098                )
1099
1100        return params
1101
1102    def __sklearn_tags__(self):
1103        tags = super().__sklearn_tags__()
1104        tags.input_tags.sparse = True
1105        tags.transformer_tags.preserves_dtype = ["float64", "float32"]
1106        return tags
1107 
Aluode/PerceptionLabPortable · CoolFace