From 896f9d97cfc9945f83ce89b4287c53a3d22a94d4 Mon Sep 17 00:00:00 2001 From: Andreas Mueller <t3kcit@gmail.com> Date: Fri, 30 Jun 2017 12:16:10 -0400 Subject: [PATCH] Fix handling of Nystroem params with a callable kernel (#9166) --- doc/whats_new.rst | 7 +++++ sklearn/kernel_approximation.py | 23 +++++++++----- sklearn/metrics/pairwise.py | 3 +- sklearn/tests/test_kernel_approximation.py | 36 ++++++++++++++++++++-- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/doc/whats_new.rst b/doc/whats_new.rst index d367c627c2..ecfc65de35 100644 --- a/doc/whats_new.rst +++ b/doc/whats_new.rst @@ -431,6 +431,13 @@ Bug fixes hence :func:`metrics.cohen_kappa_score`. :issue:`8354`, :issue:`7929` by `Joel Nothman`_ and :user:`Jon Crall <Erotemic>`. + - Made default kernel parameters kernel-dependent in :class:`kernel_approximation.Nystroem` + :issue:`5229` by :user:`mth4saurabh` and `Andreas Müller`_. + + - Fixed passing of ``gamma`` parameter to the ``chi2`` kernel in + :func:`metrics.pairwise_kernels` :issue:`5211` by :user:`nrhine1`, + :user:`mth4saurabh` and `Andreas Müller`_. + - Fixed a bug in :class:`gaussian_process.GaussianProcessRegressor` when the standard deviation and covariance predicted without fit would fail with a unmeaningful error by default. diff --git a/sklearn/kernel_approximation.py b/sklearn/kernel_approximation.py index 984b1a2a95..68b2e82772 100644 --- a/sklearn/kernel_approximation.py +++ b/sklearn/kernel_approximation.py @@ -18,7 +18,7 @@ from .base import TransformerMixin from .utils import check_array, check_random_state, as_float_array from .utils.extmath import safe_sparse_dot from .utils.validation import check_is_fitted -from .metrics.pairwise import pairwise_kernels +from .metrics.pairwise import pairwise_kernels, KERNEL_PARAMS class RBFSampler(BaseEstimator, TransformerMixin): @@ -389,10 +389,10 @@ class Nystroem(BaseEstimator, TransformerMixin): the kernel; see the documentation for sklearn.metrics.pairwise. Ignored by other kernels. - degree : float, default=3 + degree : float, default=None Degree of the polynomial kernel. Ignored by other kernels. - coef0 : float, default=1 + coef0 : float, default=None Zero coefficient for polynomial and sigmoid kernels. Ignored by other kernels. @@ -438,7 +438,7 @@ class Nystroem(BaseEstimator, TransformerMixin): sklearn.metrics.pairwise.kernel_metrics : List of built-in kernels. """ - def __init__(self, kernel="rbf", gamma=None, coef0=1, degree=3, + def __init__(self, kernel="rbf", gamma=None, coef0=None, degree=None, kernel_params=None, n_components=100, random_state=None): self.kernel = kernel self.gamma = gamma @@ -521,8 +521,17 @@ class Nystroem(BaseEstimator, TransformerMixin): if params is None: params = {} if not callable(self.kernel): - params['gamma'] = self.gamma - params['degree'] = self.degree - params['coef0'] = self.coef0 + for param in (KERNEL_PARAMS[self.kernel]): + if getattr(self, param) is not None: + params[param] = getattr(self, param) + else: + if (self.gamma is not None or + self.coef0 is not None or + self.degree is not None): + warnings.warn( + "Passing gamma, coef0 or degree to Nystroem when using a" + " callable kernel is deprecated in version 0.19 and will" + " raise an error in 0.21, as they are ignored. Use " + "kernel_params instead.", DeprecationWarning) return params diff --git a/sklearn/metrics/pairwise.py b/sklearn/metrics/pairwise.py index 9af3afd0c9..0b63653672 100644 --- a/sklearn/metrics/pairwise.py +++ b/sklearn/metrics/pairwise.py @@ -1298,9 +1298,8 @@ def kernel_metrics(): KERNEL_PARAMS = { "additive_chi2": (), - "chi2": (), + "chi2": frozenset(["gamma"]), "cosine": (), - "exp_chi2": frozenset(["gamma"]), "linear": (), "poly": frozenset(["gamma", "degree", "coef0"]), "polynomial": frozenset(["gamma", "degree", "coef0"]), diff --git a/sklearn/tests/test_kernel_approximation.py b/sklearn/tests/test_kernel_approximation.py index b2e826e262..8a2208b20a 100644 --- a/sklearn/tests/test_kernel_approximation.py +++ b/sklearn/tests/test_kernel_approximation.py @@ -5,13 +5,14 @@ from sklearn.utils.testing import assert_array_equal, assert_equal, assert_true from sklearn.utils.testing import assert_not_equal from sklearn.utils.testing import assert_array_almost_equal, assert_raises from sklearn.utils.testing import assert_less_equal +from sklearn.utils.testing import assert_warns_message from sklearn.metrics.pairwise import kernel_metrics from sklearn.kernel_approximation import RBFSampler from sklearn.kernel_approximation import AdditiveChi2Sampler from sklearn.kernel_approximation import SkewedChi2Sampler from sklearn.kernel_approximation import Nystroem -from sklearn.metrics.pairwise import polynomial_kernel, rbf_kernel +from sklearn.metrics.pairwise import polynomial_kernel, rbf_kernel, chi2_kernel # generate data rng = np.random.RandomState(0) @@ -165,7 +166,8 @@ def test_nystroem_approximation(): assert_equal(X_transformed.shape, (X.shape[0], 2)) # test callable kernel - linear_kernel = lambda X, Y: np.dot(X, Y.T) + def linear_kernel(X, Y): + return np.dot(X, Y.T) trans = Nystroem(n_components=2, kernel=linear_kernel, random_state=rnd) X_transformed = trans.fit(X).transform(X) assert_equal(X_transformed.shape, (X.shape[0], 2)) @@ -178,6 +180,26 @@ def test_nystroem_approximation(): assert_equal(X_transformed.shape, (X.shape[0], 2)) +def test_nystroem_default_parameters(): + rnd = np.random.RandomState(42) + X = rnd.uniform(size=(10, 4)) + + # rbf kernel should behave as gamma=None by default + # aka gamma = 1 / n_features + nystroem = Nystroem(n_components=10) + X_transformed = nystroem.fit_transform(X) + K = rbf_kernel(X, gamma=None) + K2 = np.dot(X_transformed, X_transformed.T) + assert_array_almost_equal(K, K2) + + # chi2 kernel should behave as gamma=1 by default + nystroem = Nystroem(kernel='chi2', n_components=10) + X_transformed = nystroem.fit_transform(X) + K = chi2_kernel(X, gamma=1) + K2 = np.dot(X_transformed, X_transformed.T) + assert_array_almost_equal(K, K2) + + def test_nystroem_singular_kernel(): # test that nystroem works with singular kernel matrix rng = np.random.RandomState(0) @@ -223,3 +245,13 @@ def test_nystroem_callable(): n_components=(n_samples - 1), kernel_params={'log': kernel_log}).fit(X) assert_equal(len(kernel_log), n_samples * (n_samples - 1) / 2) + + def linear_kernel(X, Y): + return np.dot(X, Y.T) + + # if degree, gamma or coef0 is passed, we raise a warning + msg = "Passing gamma, coef0 or degree to Nystroem" + params = ({'gamma': 1}, {'coef0': 1}, {'degree': 2}) + for param in params: + ny = Nystroem(kernel=linear_kernel, **param) + assert_warns_message(DeprecationWarning, msg, ny.fit, X) -- GitLab