From 5624cd3a47aab0ef1a071d4167f74892ae32dca8 Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Tue, 20 Feb 2024 16:29:24 +1100 Subject: [PATCH 01/15] deprecate n_iter --- doc/whats_new/v1.5.rst | 7 +++ sklearn/manifold/_t_sne.py | 62 +++++++++++++++----- sklearn/manifold/tests/test_t_sne.py | 84 ++++++++++++++++++---------- 3 files changed, 108 insertions(+), 45 deletions(-) diff --git a/doc/whats_new/v1.5.rst b/doc/whats_new/v1.5.rst index c0c96f14ae612..59bec0d3ec552 100644 --- a/doc/whats_new/v1.5.rst +++ b/doc/whats_new/v1.5.rst @@ -110,6 +110,13 @@ Changelog by passing a function in place of a strategy name. :pr:`28053` by :user:`Mark Elliot `. +:mod:`sklearn.manifold` +....................... + +- |API| Deprecates `n_iter` in favor of `max_iter` in :class:`manifold.TSNE`. + `n_iter` will be removed in version 1.7. This makes :class:`manifold.TSNE` + consistent with the rest of the estimators. + :mod:`sklearn.metrics` ...................... diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index 2233bea3a7681..e8da22ce6909c 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -10,6 +10,7 @@ from numbers import Integral, Real from time import time +import warnings import numpy as np from scipy import linalg @@ -27,7 +28,7 @@ from ..neighbors import NearestNeighbors from ..utils import check_random_state from ..utils._openmp_helpers import _openmp_effective_n_threads -from ..utils._param_validation import Interval, StrOptions, validate_params +from ..utils._param_validation import Hidden, Interval, StrOptions, validate_params from ..utils.validation import _num_samples, check_non_negative # mypy error: Module 'sklearn.manifold' has no attribute '_utils' @@ -304,7 +305,7 @@ def _gradient_descent( objective, p0, it, - n_iter, + max_iter, n_iter_check=1, n_iter_without_progress=300, momentum=0.8, @@ -332,7 +333,7 @@ def _gradient_descent( Current number of iterations (this function will be called more than once during the optimization). - n_iter : int + max_iter : int Maximum number of gradient descent iterations. n_iter_check : int, default=1 @@ -394,10 +395,10 @@ def _gradient_descent( best_iter = i = it tic = time() - for i in range(it, n_iter): + for i in range(it, max_iter): check_convergence = (i + 1) % n_iter_check == 0 # only compute the error when needed - kwargs["compute_error"] = check_convergence or i == n_iter - 1 + kwargs["compute_error"] = check_convergence or i == max_iter - 1 error, grad = objective(p, *args, **kwargs) @@ -617,10 +618,22 @@ class TSNE(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator): .. versionchanged:: 1.2 The default value changed to `"auto"`. - n_iter : int, default=1000 + max_iter : int, default=None + Maximum number of iterations for the optimization. Should be at + least 250. Default `None` corresponds to `max_iter=1000`. + + .. versionchanged:: 1.5 + Parameter name changed from `n_iter` to `max_iter`. + + + n_iter : int Maximum number of iterations for the optimization. Should be at least 250. + .. deprecated:: 1.5 + `n_iter` was deprecated in version 1.5 and will be removed in 1.7. + Please use `max_iter` instead. + n_iter_without_progress : int, default=300 Maximum number of iterations without progress before we abort the optimization, used after 250 initial iterations with early @@ -784,7 +797,7 @@ class TSNE(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator): StrOptions({"auto"}), Interval(Real, 0, None, closed="neither"), ], - "n_iter": [Interval(Integral, 250, None, closed="left")], + "max_iter": [Interval(Integral, 250, None, closed="left"), None], "n_iter_without_progress": [Interval(Integral, -1, None, closed="left")], "min_grad_norm": [Interval(Real, 0, None, closed="left")], "metric": [StrOptions(set(_VALID_METRICS) | {"precomputed"}), callable], @@ -798,10 +811,14 @@ class TSNE(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator): "method": [StrOptions({"barnes_hut", "exact"})], "angle": [Interval(Real, 0, 1, closed="both")], "n_jobs": [None, Integral], + "n_iter": [ + Interval(Integral, 1, None, closed="left"), + Hidden(StrOptions({"deprecated"})), + ], } # Control the number of exploration iterations with early_exaggeration on - _EXPLORATION_N_ITER = 250 + _EXPLORATION_MAX_ITER = 250 # Control the number of iterations between progress checks _N_ITER_CHECK = 50 @@ -813,7 +830,8 @@ def __init__( perplexity=30.0, early_exaggeration=12.0, learning_rate="auto", - n_iter=1000, + n_iter="deprecated", + max_iter=None, # TODO(1.7): set to 1000 n_iter_without_progress=300, min_grad_norm=1e-7, metric="euclidean", @@ -830,6 +848,7 @@ def __init__( self.early_exaggeration = early_exaggeration self.learning_rate = learning_rate self.n_iter = n_iter + self.max_iter = max_iter self.n_iter_without_progress = n_iter_without_progress self.min_grad_norm = min_grad_norm self.metric = metric @@ -1057,8 +1076,8 @@ def _tsne( "verbose": self.verbose, "kwargs": dict(skip_num_points=skip_num_points), "args": [P, degrees_of_freedom, n_samples, self.n_components], - "n_iter_without_progress": self._EXPLORATION_N_ITER, - "n_iter": self._EXPLORATION_N_ITER, + "n_iter_without_progress": self._EXPLORATION_MAX_ITER, + "max_iter": self._EXPLORATION_MAX_ITER, "momentum": 0.5, } if self.method == "barnes_hut": @@ -1085,9 +1104,9 @@ def _tsne( # Learning schedule (part 2): disable early exaggeration and finish # optimization with a higher momentum at 0.8 P /= self.early_exaggeration - remaining = self.n_iter - self._EXPLORATION_N_ITER - if it < self._EXPLORATION_N_ITER or remaining > 0: - opt_args["n_iter"] = self.n_iter + remaining = self.max_iter - self._EXPLORATION_MAX_ITER + if it < self._EXPLORATION_MAX_ITER or remaining > 0: + opt_args["max_iter"] = self.max_iter opt_args["it"] = it + 1 opt_args["momentum"] = 0.8 opt_args["n_iter_without_progress"] = self.n_iter_without_progress @@ -1132,6 +1151,21 @@ def fit_transform(self, X, y=None): X_new : ndarray of shape (n_samples, n_components) Embedding of the training data in low-dimensional space. """ + # TODO(1.7): remove + if self.n_iter != 'deprecated': + if self.max_iter is not None: + raise ValueError( + "Both 'n_iter' and 'max_iter' attributes were set. Attribute" + " 'n_iter' was deprecated in version 1.5 and will be removed in" + " 1.7. To avoid this error, only set the 'max_iter' attribute." + ) + warnings.warn("'n_iter' was renamed to 'max_iter' in version 1.5 and " + "will be removed in 1.7.", + FutureWarning) + self.max_iter = self.n_iter + elif self.max_iter is None: + self.max_iter = 1000 + self._check_params_vs_input(X) embedding = self._fit(X) self.embedding_ = embedding diff --git a/sklearn/manifold/tests/test_t_sne.py b/sklearn/manifold/tests/test_t_sne.py index ea037fa5f8391..487c0ebf00862 100644 --- a/sklearn/manifold/tests/test_t_sne.py +++ b/sklearn/manifold/tests/test_t_sne.py @@ -73,7 +73,7 @@ def flat_function(_, compute_error=True): ObjectiveSmallGradient(), np.zeros(1), 0, - n_iter=100, + max_iter=100, n_iter_without_progress=100, momentum=0.0, learning_rate=0.0, @@ -97,7 +97,7 @@ def flat_function(_, compute_error=True): flat_function, np.zeros(1), 0, - n_iter=100, + max_iter=100, n_iter_without_progress=10, momentum=0.0, learning_rate=0.0, @@ -121,7 +121,7 @@ def flat_function(_, compute_error=True): ObjectiveSmallGradient(), np.zeros(1), 0, - n_iter=11, + max_iter=11, n_iter_without_progress=100, momentum=0.0, learning_rate=0.0, @@ -308,7 +308,7 @@ def test_preserve_trustworthiness_approximately(method, init): init=init, random_state=0, method=method, - n_iter=700, + max_iter=700, learning_rate="auto", ) X_embedded = tsne.fit_transform(X) @@ -321,13 +321,13 @@ def test_optimization_minimizes_kl_divergence(): random_state = check_random_state(0) X, _ = make_blobs(n_features=3, random_state=random_state) kl_divergences = [] - for n_iter in [250, 300, 350]: + for max_iter in [250, 300, 350]: tsne = TSNE( n_components=2, init="random", perplexity=10, learning_rate=100.0, - n_iter=n_iter, + max_iter=max_iter, random_state=0, ) tsne.fit_transform(X) @@ -353,7 +353,7 @@ def test_fit_transform_csr_matrix(method, csr_container): learning_rate=100.0, random_state=0, method=method, - n_iter=750, + max_iter=750, ) X_embedded = tsne.fit_transform(X_csr) assert_allclose(trustworthiness(X_csr, X_embedded, n_neighbors=1), 1.0, rtol=1.1e-1) @@ -373,7 +373,7 @@ def test_preserve_trustworthiness_approximately_with_precomputed_distances(): metric="precomputed", random_state=i, verbose=0, - n_iter=500, + max_iter=500, init="random", ) X_embedded = tsne.fit_transform(D) @@ -533,7 +533,7 @@ def test_early_exaggeration_used(): random_state=0, method=method, early_exaggeration=1.0, - n_iter=250, + max_iter=250, ) X_embedded1 = tsne.fit_transform(X) tsne = TSNE( @@ -544,21 +544,21 @@ def test_early_exaggeration_used(): random_state=0, method=method, early_exaggeration=10.0, - n_iter=250, + max_iter=250, ) X_embedded2 = tsne.fit_transform(X) assert not np.allclose(X_embedded1, X_embedded2) -def test_n_iter_used(): - # check that the ``n_iter`` parameter has an effect +def test_max_iter_used(): + # check that the ``max_iter`` parameter has an effect random_state = check_random_state(0) n_components = 2 methods = ["exact", "barnes_hut"] X = random_state.randn(25, n_components).astype(np.float32) for method in methods: - for n_iter in [251, 500]: + for max_iter in [251, 500]: tsne = TSNE( n_components=n_components, perplexity=1, @@ -567,11 +567,11 @@ def test_n_iter_used(): random_state=0, method=method, early_exaggeration=1.0, - n_iter=n_iter, + max_iter=max_iter, ) tsne.fit_transform(X) - assert tsne.n_iter_ == n_iter - 1 + assert tsne.n_iter_ == max_iter - 1 @pytest.mark.parametrize("csr_container", CSR_CONTAINERS) @@ -732,7 +732,7 @@ def test_64bit(method, dt): random_state=0, method=method, verbose=0, - n_iter=300, + max_iter=300, init="random", ) X_embedded = tsne.fit_transform(X) @@ -746,7 +746,7 @@ def test_64bit(method, dt): @pytest.mark.parametrize("method", ["barnes_hut", "exact"]) def test_kl_divergence_not_nan(method): # Ensure kl_divergence_ is computed at last iteration - # even though n_iter % n_iter_check != 0, i.e. 1003 % 50 != 0 + # even though max_iter % n_iter_check != 0, i.e. 1003 % 50 != 0 random_state = check_random_state(0) X = random_state.randn(50, 2) @@ -757,7 +757,7 @@ def test_kl_divergence_not_nan(method): random_state=0, method=method, verbose=0, - n_iter=503, + max_iter=503, init="random", ) tsne.fit_transform(X) @@ -819,11 +819,11 @@ def test_n_iter_without_progress(): learning_rate=1e8, random_state=0, method=method, - n_iter=351, + max_iter=351, init="random", ) tsne._N_ITER_CHECK = 1 - tsne._EXPLORATION_N_ITER = 0 + tsne._EXPLORATION_MAX_ITER = 0 old_stdout = sys.stdout sys.stdout = StringIO() @@ -886,7 +886,7 @@ def test_accessible_kl_divergence(): random_state = check_random_state(0) X = random_state.randn(50, 2) tsne = TSNE( - n_iter_without_progress=2, verbose=2, random_state=0, method="exact", n_iter=500 + n_iter_without_progress=2, verbose=2, random_state=0, method="exact", max_iter=500 ) old_stdout = sys.stdout @@ -923,14 +923,14 @@ def test_uniform_grid(method): enough. """ seeds = range(3) - n_iter = 500 + max_iter = 500 for seed in seeds: tsne = TSNE( n_components=2, init="random", random_state=seed, perplexity=50, - n_iter=n_iter, + max_iter=max_iter, method=method, learning_rate="auto", ) @@ -971,7 +971,7 @@ def test_bh_match_exact(): n_features = 10 X = random_state.randn(30, n_features).astype(np.float32) X_embeddeds = {} - n_iter = {} + max_iter = {} for method in ["exact", "barnes_hut"]: tsne = TSNE( n_components=2, @@ -979,16 +979,16 @@ def test_bh_match_exact(): learning_rate=1.0, init="random", random_state=0, - n_iter=251, + max_iter=251, perplexity=29.5, angle=0, ) # Kill the early_exaggeration - tsne._EXPLORATION_N_ITER = 0 + tsne._EXPLORATION_MAX_ITER = 0 X_embeddeds[method] = tsne.fit_transform(X) - n_iter[method] = tsne.n_iter_ + max_iter[method] = tsne.n_iter_ - assert n_iter["exact"] == n_iter["barnes_hut"] + assert max_iter["exact"] == max_iter["barnes_hut"] assert_allclose(X_embeddeds["exact"], X_embeddeds["barnes_hut"], rtol=1e-4) @@ -1077,7 +1077,7 @@ def test_tsne_with_different_distance_metrics(metric, dist_func, method): method=method, n_components=n_components_embedding, random_state=0, - n_iter=300, + max_iter=300, init="random", learning_rate="auto", ).fit_transform(X) @@ -1086,7 +1086,7 @@ def test_tsne_with_different_distance_metrics(metric, dist_func, method): method=method, n_components=n_components_embedding, random_state=0, - n_iter=300, + max_iter=300, init="random", learning_rate="auto", ).fit_transform(dist_func(X)) @@ -1130,7 +1130,7 @@ def test_tsne_with_mahalanobis_distance(): X = random_state.randn(n_samples, n_features) default_params = { "perplexity": 40, - "n_iter": 250, + "max_iter": 250, "learning_rate": "auto", "init": "random", "n_components": 3, @@ -1179,3 +1179,25 @@ def test_tsne_works_with_pandas_output(): with config_context(transform_output="pandas"): arr = np.arange(35 * 4).reshape(35, 4) TSNE(n_components=2).fit_transform(arr) + + +# TODO(1.7): remove +def test_tnse_n_iter_deprecated(): + """Check `n_iter` parameter deprecated.""" + random_state = check_random_state(0) + X = random_state.randn(40, 100) + tsne = TSNE(n_iter=250) + msg = "'n_iter' was renamed to 'max_iter'" + with pytest.warns(FutureWarning, match=msg): + tsne.fit_transform(X) + + +# TODO(1.7): remove +def test_tnse_n_iter_max_iter_both_set(): + """Check error raised when `n_iter` and `max_iter` both set.""" + random_state = check_random_state(0) + X = random_state.randn(40, 100) + tsne = TSNE(n_iter=250, max_iter=500) + msg = "Both 'n_iter' and 'max_iter' attributes were set" + with pytest.raises(ValueError, match=msg): + tsne.fit_transform(X) From d0e5e5e7f5af9910022676dc1eb528ebec85b1bd Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Tue, 20 Feb 2024 20:28:39 +1100 Subject: [PATCH 02/15] lint --- sklearn/manifold/_t_sne.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index e8da22ce6909c..c14a166e9ec7e 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -8,9 +8,9 @@ # * Fast Optimization for t-SNE: # https://cseweb.ucsd.edu/~lvdmaaten/workshops/nips2010/papers/vandermaaten.pdf +import warnings from numbers import Integral, Real from time import time -import warnings import numpy as np from scipy import linalg @@ -1152,16 +1152,20 @@ def fit_transform(self, X, y=None): Embedding of the training data in low-dimensional space. """ # TODO(1.7): remove - if self.n_iter != 'deprecated': + if self.n_iter != "deprecated": if self.max_iter is not None: raise ValueError( "Both 'n_iter' and 'max_iter' attributes were set. Attribute" " 'n_iter' was deprecated in version 1.5 and will be removed in" " 1.7. To avoid this error, only set the 'max_iter' attribute." ) - warnings.warn("'n_iter' was renamed to 'max_iter' in version 1.5 and " - "will be removed in 1.7.", - FutureWarning) + warnings.warn( + ( + "'n_iter' was renamed to 'max_iter' in version 1.5 and " + "will be removed in 1.7." + ), + FutureWarning, + ) self.max_iter = self.n_iter elif self.max_iter is None: self.max_iter = 1000 From b003c5b7b4090bd34673aec7ff70c8572a213ba4 Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Tue, 20 Feb 2024 20:35:32 +1100 Subject: [PATCH 03/15] update whats new --- doc/whats_new/v1.5.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/whats_new/v1.5.rst b/doc/whats_new/v1.5.rst index 59bec0d3ec552..c24e4c11920ca 100644 --- a/doc/whats_new/v1.5.rst +++ b/doc/whats_new/v1.5.rst @@ -115,7 +115,8 @@ Changelog - |API| Deprecates `n_iter` in favor of `max_iter` in :class:`manifold.TSNE`. `n_iter` will be removed in version 1.7. This makes :class:`manifold.TSNE` - consistent with the rest of the estimators. + consistent with the rest of the estimators. :pr:`28471` by + :user:`Lucy Liu ` :mod:`sklearn.metrics` ...................... From b51731b9e6f0e2a96eda240aad886bafbb5ba40a Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Tue, 20 Feb 2024 20:36:43 +1100 Subject: [PATCH 04/15] review --- sklearn/manifold/_t_sne.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index c14a166e9ec7e..37f3ff69a2482 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -618,9 +618,9 @@ class TSNE(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator): .. versionchanged:: 1.2 The default value changed to `"auto"`. - max_iter : int, default=None + max_iter : int, default=1000 Maximum number of iterations for the optimization. Should be at - least 250. Default `None` corresponds to `max_iter=1000`. + least 250. .. versionchanged:: 1.5 Parameter name changed from `n_iter` to `max_iter`. From 4878fe4cba751c17aa7930bbcd18c5818811a90b Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Tue, 20 Feb 2024 20:38:49 +1100 Subject: [PATCH 05/15] mv dep param to end --- sklearn/manifold/_t_sne.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index 37f3ff69a2482..e67d36ff66234 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -625,15 +625,6 @@ class TSNE(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator): .. versionchanged:: 1.5 Parameter name changed from `n_iter` to `max_iter`. - - n_iter : int - Maximum number of iterations for the optimization. Should be at - least 250. - - .. deprecated:: 1.5 - `n_iter` was deprecated in version 1.5 and will be removed in 1.7. - Please use `max_iter` instead. - n_iter_without_progress : int, default=300 Maximum number of iterations without progress before we abort the optimization, used after 250 initial iterations with early @@ -713,6 +704,14 @@ class TSNE(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator): .. versionadded:: 0.22 + n_iter : int + Maximum number of iterations for the optimization. Should be at + least 250. + + .. deprecated:: 1.5 + `n_iter` was deprecated in version 1.5 and will be removed in 1.7. + Please use `max_iter` instead. + Attributes ---------- embedding_ : array-like of shape (n_samples, n_components) From 0d1498867e583e080b15e8d0a25f9319df2e8699 Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Tue, 20 Feb 2024 20:40:41 +1100 Subject: [PATCH 06/15] add note --- sklearn/manifold/_t_sne.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index e67d36ff66234..ef94bfd793f66 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -1151,6 +1151,7 @@ def fit_transform(self, X, y=None): Embedding of the training data in low-dimensional space. """ # TODO(1.7): remove + # Also make sure to change `max_iter` default back to 1 and deprecate None if self.n_iter != "deprecated": if self.max_iter is not None: raise ValueError( From 0220de0f08502164d796c7d0d2e7015c163b9c55 Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Tue, 20 Feb 2024 20:47:29 +1100 Subject: [PATCH 07/15] lint --- sklearn/manifold/tests/test_t_sne.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/sklearn/manifold/tests/test_t_sne.py b/sklearn/manifold/tests/test_t_sne.py index 487c0ebf00862..f0189405d365b 100644 --- a/sklearn/manifold/tests/test_t_sne.py +++ b/sklearn/manifold/tests/test_t_sne.py @@ -886,7 +886,11 @@ def test_accessible_kl_divergence(): random_state = check_random_state(0) X = random_state.randn(50, 2) tsne = TSNE( - n_iter_without_progress=2, verbose=2, random_state=0, method="exact", max_iter=500 + n_iter_without_progress=2, + verbose=2, + random_state=0, + method="exact", + max_iter=500, ) old_stdout = sys.stdout From e2cea4ff16df345672c6e52edfae838652a78db0 Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Tue, 20 Feb 2024 22:02:09 +1100 Subject: [PATCH 08/15] xfail --- sklearn/manifold/_t_sne.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index ef94bfd793f66..181fa4804462a 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -1209,4 +1209,17 @@ def _n_features_out(self): return self.embedding_.shape[1] def _more_tags(self): - return {"pairwise": self.metric == "precomputed"} + return { + "pairwise": self.metric == "precomputed", + # TODO(1.7): remove + "_xfail_checks": { + "check_estimators_overwrite_params": ( + "'max_iter' updated during fit due to parameter name update, " + "to allow early parameter check and for easy removal in 1.7." + ), + "check_dont_overwrite_parameters": ( + "'max_iter' updated during fit due to parameter name update, " + "to allow early parameter check and for easy removal in 1.7." + ), + } + } From 61ba89ddb408a0792e8be7516ce2ed571be43261 Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Tue, 20 Feb 2024 22:08:57 +1100 Subject: [PATCH 09/15] lint --- sklearn/manifold/_t_sne.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index 181fa4804462a..7b4b3fd7068d3 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -1221,5 +1221,5 @@ def _more_tags(self): "'max_iter' updated during fit due to parameter name update, " "to allow early parameter check and for easy removal in 1.7." ), - } + }, } From 5e0d0368b219b0d48a9b8a4382b8509df333152d Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Wed, 21 Feb 2024 16:51:34 +1100 Subject: [PATCH 10/15] use max_iter_ --- sklearn/manifold/_t_sne.py | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index 7b4b3fd7068d3..73631ad6462ba 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -1103,9 +1103,9 @@ def _tsne( # Learning schedule (part 2): disable early exaggeration and finish # optimization with a higher momentum at 0.8 P /= self.early_exaggeration - remaining = self.max_iter - self._EXPLORATION_MAX_ITER + remaining = self.max_iter_ - self._EXPLORATION_MAX_ITER if it < self._EXPLORATION_MAX_ITER or remaining > 0: - opt_args["max_iter"] = self.max_iter + opt_args["max_iter"] = self.max_iter_ opt_args["it"] = it + 1 opt_args["momentum"] = 0.8 opt_args["n_iter_without_progress"] = self.n_iter_without_progress @@ -1166,9 +1166,11 @@ def fit_transform(self, X, y=None): ), FutureWarning, ) - self.max_iter = self.n_iter + self.max_iter_ = self.n_iter elif self.max_iter is None: - self.max_iter = 1000 + self.max_iter_ = 1000 + else: + self.max_iter_ = self.max_iter self._check_params_vs_input(X) embedding = self._fit(X) @@ -1209,17 +1211,4 @@ def _n_features_out(self): return self.embedding_.shape[1] def _more_tags(self): - return { - "pairwise": self.metric == "precomputed", - # TODO(1.7): remove - "_xfail_checks": { - "check_estimators_overwrite_params": ( - "'max_iter' updated during fit due to parameter name update, " - "to allow early parameter check and for easy removal in 1.7." - ), - "check_dont_overwrite_parameters": ( - "'max_iter' updated during fit due to parameter name update, " - "to allow early parameter check and for easy removal in 1.7." - ), - }, - } + return {"pairwise": self.metric == "precomputed"} From 75547fa0268419ca7c0c79774a5612e656509399 Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Wed, 21 Feb 2024 19:14:46 +1100 Subject: [PATCH 11/15] fix tests --- sklearn/manifold/_t_sne.py | 4 ++-- sklearn/neighbors/tests/test_neighbors_pipeline.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index 73631ad6462ba..678f70e076923 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -829,7 +829,6 @@ def __init__( perplexity=30.0, early_exaggeration=12.0, learning_rate="auto", - n_iter="deprecated", max_iter=None, # TODO(1.7): set to 1000 n_iter_without_progress=300, min_grad_norm=1e-7, @@ -841,12 +840,12 @@ def __init__( method="barnes_hut", angle=0.5, n_jobs=None, + n_iter="deprecated", ): self.n_components = n_components self.perplexity = perplexity self.early_exaggeration = early_exaggeration self.learning_rate = learning_rate - self.n_iter = n_iter self.max_iter = max_iter self.n_iter_without_progress = n_iter_without_progress self.min_grad_norm = min_grad_norm @@ -858,6 +857,7 @@ def __init__( self.method = method self.angle = angle self.n_jobs = n_jobs + self.n_iter = n_iter def _check_params_vs_input(self, X): if self.perplexity >= X.shape[0]: diff --git a/sklearn/neighbors/tests/test_neighbors_pipeline.py b/sklearn/neighbors/tests/test_neighbors_pipeline.py index 1d01a0d0a60a8..6ad78824489ca 100644 --- a/sklearn/neighbors/tests/test_neighbors_pipeline.py +++ b/sklearn/neighbors/tests/test_neighbors_pipeline.py @@ -121,7 +121,7 @@ def test_isomap(): def test_tsne(): # Test chaining KNeighborsTransformer and TSNE - n_iter = 250 + max_iter = 250 perplexity = 5 n_neighbors = int(3.0 * perplexity + 1) @@ -140,14 +140,14 @@ def test_tsne(): perplexity=perplexity, method="barnes_hut", random_state=42, - n_iter=n_iter, + max_iter=max_iter, ), ) est_compact = TSNE( init="random", metric=metric, perplexity=perplexity, - n_iter=n_iter, + max_iter=max_iter, method="barnes_hut", random_state=42, ) From 096f0830e048fd612d59bcdf71f3db1ee01eaeb5 Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Wed, 21 Feb 2024 19:27:34 +1100 Subject: [PATCH 12/15] fix param constraints --- sklearn/manifold/_t_sne.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index 678f70e076923..f77481868d210 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -811,7 +811,7 @@ class TSNE(ClassNamePrefixFeaturesOutMixin, TransformerMixin, BaseEstimator): "angle": [Interval(Real, 0, 1, closed="both")], "n_jobs": [None, Integral], "n_iter": [ - Interval(Integral, 1, None, closed="left"), + Interval(Integral, 250, None, closed="left"), Hidden(StrOptions({"deprecated"})), ], } From e013b190b8195d1a78892e22f0ffb0a6a5fe0666 Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Wed, 21 Feb 2024 21:48:48 +1100 Subject: [PATCH 13/15] amend docstring test --- sklearn/tests/test_docstring_parameters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/sklearn/tests/test_docstring_parameters.py b/sklearn/tests/test_docstring_parameters.py index 52a383e4ca602..bdea9f8fd35f7 100644 --- a/sklearn/tests/test_docstring_parameters.py +++ b/sklearn/tests/test_docstring_parameters.py @@ -240,6 +240,9 @@ def test_fit_docstring_attributes(name, Estimator): # Low max iter to speed up tests: we are only interested in checking the existence # of fitted attributes. This should be invariant to whether it has converged or not. if "max_iter" in est.get_params(): + # min value for `TSNE` is 250 + if Estimator.__name__ == "TSNE": + est.set_params(max_iter=250) est.set_params(max_iter=2) if "random_state" in est.get_params(): From e6f4f910fb1d7f87f0c11c74c2a5c205b3b93d56 Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Thu, 22 Feb 2024 12:30:06 +1100 Subject: [PATCH 14/15] make private attr --- sklearn/manifold/_t_sne.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sklearn/manifold/_t_sne.py b/sklearn/manifold/_t_sne.py index f77481868d210..348f26e83592c 100644 --- a/sklearn/manifold/_t_sne.py +++ b/sklearn/manifold/_t_sne.py @@ -1103,9 +1103,9 @@ def _tsne( # Learning schedule (part 2): disable early exaggeration and finish # optimization with a higher momentum at 0.8 P /= self.early_exaggeration - remaining = self.max_iter_ - self._EXPLORATION_MAX_ITER + remaining = self._max_iter - self._EXPLORATION_MAX_ITER if it < self._EXPLORATION_MAX_ITER or remaining > 0: - opt_args["max_iter"] = self.max_iter_ + opt_args["max_iter"] = self._max_iter opt_args["it"] = it + 1 opt_args["momentum"] = 0.8 opt_args["n_iter_without_progress"] = self.n_iter_without_progress @@ -1166,11 +1166,11 @@ def fit_transform(self, X, y=None): ), FutureWarning, ) - self.max_iter_ = self.n_iter + self._max_iter = self.n_iter elif self.max_iter is None: - self.max_iter_ = 1000 + self._max_iter = 1000 else: - self.max_iter_ = self.max_iter + self._max_iter = self.max_iter self._check_params_vs_input(X) embedding = self._fit(X) From c9f635186a3caba67e3aa1a6045a842f458ae31c Mon Sep 17 00:00:00 2001 From: Lucy Liu Date: Thu, 22 Feb 2024 12:30:55 +1100 Subject: [PATCH 15/15] review --- sklearn/tests/test_docstring_parameters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sklearn/tests/test_docstring_parameters.py b/sklearn/tests/test_docstring_parameters.py index bdea9f8fd35f7..11ef852a6bbf3 100644 --- a/sklearn/tests/test_docstring_parameters.py +++ b/sklearn/tests/test_docstring_parameters.py @@ -240,10 +240,10 @@ def test_fit_docstring_attributes(name, Estimator): # Low max iter to speed up tests: we are only interested in checking the existence # of fitted attributes. This should be invariant to whether it has converged or not. if "max_iter" in est.get_params(): + est.set_params(max_iter=2) # min value for `TSNE` is 250 if Estimator.__name__ == "TSNE": est.set_params(max_iter=250) - est.set_params(max_iter=2) if "random_state" in est.get_params(): est.set_params(random_state=0)