Skip to content

Commit fc82e36

Browse files
committed
Rename TSNE n_iter to max_iter
In scikit-learn 1.5 they renamed the TSNE `n_iter` parameter to `max_iter`. In the interest of maximizing sklearn compatibility, here we do the same. This PR deprecates `n_iter` in favor of `max_iter`, in 26.02 we'll remove `n_iter` completely.
1 parent ab72076 commit fc82e36

2 files changed

Lines changed: 74 additions & 44 deletions

File tree

python/cuml/cuml/manifold/t_sne.pyx

Lines changed: 61 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -104,9 +104,9 @@ cdef extern from "cuml/manifold/tsne.h" namespace "ML" nogil:
104104

105105
# Changed in scikit-learn version 1.5: Parameter name changed from n_iter to max_iter.
106106
if Version(sklearn.__version__) >= Version("1.5.0"):
107-
_SKLEARN_N_ITER_PARAM = "max_iter"
107+
_SKLEARN_MAX_ITER_PARAM = "max_iter"
108108
else:
109-
_SKLEARN_N_ITER_PARAM = "n_iter"
109+
_SKLEARN_MAX_ITER_PARAM = "n_iter"
110110

111111
_SUPPORTED_METRICS = {
112112
"l2": DistanceType.L2SqrtExpanded,
@@ -173,7 +173,6 @@ cdef _init_params(self, int n_samples, TSNEParams &params):
173173
adaptive_learning = _check_mapping(
174174
self, "learning_rate_method", {"adaptive": True, "none": False, None: False}
175175
)
176-
n_iter = _check_numeric(self, "n_iter", gt=0)
177176
min_grad_norm = _check_numeric(self, "min_grad_norm", ge=0)
178177
angle = _check_numeric(self, "angle", ge=0, le=1)
179178
n_neighbors = _check_numeric(self, "n_neighbors", gt=0)
@@ -188,7 +187,19 @@ cdef _init_params(self, int n_samples, TSNEParams &params):
188187
if n_samples < 2:
189188
raise ValueError("TSNE requires >= 2 samples")
190189

191-
exaggeration_iter = min(exaggeration_iter, self.n_iter)
190+
if self.n_iter != "deprecated":
191+
warnings.warn(
192+
(
193+
"`n_iter` was deprecated in 25.12 and will be removed in 26.02. "
194+
"Please use `max_iter` instead."
195+
),
196+
FutureWarning,
197+
)
198+
max_iter = _check_numeric(self, "n_iter", gt=0)
199+
else:
200+
max_iter = _check_numeric(self, "max_iter", gt=0)
201+
202+
exaggeration_iter = min(exaggeration_iter, max_iter)
192203
if n_neighbors > 1023:
193204
warnings.warn(
194205
f"n_neighbors ({n_neighbors}) should be < 1024, "
@@ -237,7 +248,7 @@ cdef _init_params(self, int n_samples, TSNEParams &params):
237248
params.min_gain = 0.01
238249
params.pre_learning_rate = pre_learning_rate
239250
params.post_learning_rate = post_learning_rate
240-
params.max_iter = n_iter
251+
params.max_iter = max_iter
241252
params.min_grad_norm = min_grad_norm
242253
params.pre_momentum = pre_momentum
243254
params.post_momentum = post_momentum
@@ -283,11 +294,18 @@ class TSNE(Base,
283294
learning_rate : float (default 200.0)
284295
The learning rate usually between (10, 1000). If this is too high,
285296
t-SNE could look like a cloud / ball of points.
286-
n_iter : int (default 1000)
297+
max_iter : int (default 1000)
287298
The more epochs, the more stable/accurate the final embedding.
288299
n_iter_without_progress : int (default 300)
289300
Currently unused. When the KL Divergence becomes too small after some
290301
iterations, terminate t-SNE early.
302+
n_iter : int (default "deprecated")
303+
304+
.. deprecated:: 25.12
305+
``n_iter`` has been renamed to ``max_iter`` to better match the
306+
API of ``sklearn.manifold.TSNE``. ``n_iter`` is deprecated in favor
307+
of ``max_iter`` and will be removed in 26.02.
308+
291309
min_grad_norm : float (default 1e-07)
292310
The minimum gradient norm for when t-SNE will terminate early.
293311
Used in the 'exact' and 'fft' algorithms. Consider reducing if
@@ -417,8 +435,9 @@ class TSNE(Base,
417435
"early_exaggeration",
418436
"late_exaggeration",
419437
"learning_rate",
420-
"n_iter",
438+
"max_iter",
421439
"n_iter_without_progress",
440+
"n_iter",
422441
"min_grad_norm",
423442
"metric",
424443
"metric_params",
@@ -464,14 +483,12 @@ class TSNE(Base,
464483
"init": model.init,
465484
"random_state": model.random_state,
466485
"method": method,
486+
"max_iter": getattr(model, _SKLEARN_MAX_ITER_PARAM),
467487
}
468488
if model.learning_rate != "auto":
469489
# For now have `learning_rate="auto"` just use cuml's default
470490
params["learning_rate"]: model.learning_rate
471491

472-
if (max_iter := getattr(model, _SKLEARN_N_ITER_PARAM, None)) is not None:
473-
params["n_iter"] = max_iter
474-
475492
return params
476493

477494
def _params_to_cpu(self):
@@ -489,7 +506,7 @@ class TSNE(Base,
489506
"init": self.init,
490507
"random_state": self.random_state,
491508
"method": method,
492-
_SKLEARN_N_ITER_PARAM: self.n_iter,
509+
_SKLEARN_MAX_ITER_PARAM: self.max_iter,
493510
}
494511
return params
495512

@@ -511,44 +528,45 @@ class TSNE(Base,
511528
**super()._attrs_to_cpu(model)
512529
}
513530

514-
def __init__(self, *,
515-
n_components=2,
516-
perplexity=30.0,
517-
early_exaggeration=12.0,
518-
late_exaggeration=1.0,
519-
learning_rate=200.0,
520-
n_iter=1000,
521-
n_iter_without_progress=300,
522-
min_grad_norm=1e-07,
523-
metric='euclidean',
524-
metric_params=None,
525-
init='random',
526-
random_state=None,
527-
method='fft',
528-
angle=0.5,
529-
n_neighbors=90,
530-
perplexity_max_iter=100,
531-
exaggeration_iter=250,
532-
pre_momentum=0.5,
533-
post_momentum=0.8,
534-
learning_rate_method='adaptive',
535-
square_distances=True,
536-
precomputed_knn=None,
537-
verbose=False,
538-
handle=None,
539-
output_type=None):
540-
541-
super().__init__(handle=handle,
542-
verbose=verbose,
543-
output_type=output_type)
544-
531+
def __init__(
532+
self,
533+
*,
534+
n_components=2,
535+
perplexity=30.0,
536+
early_exaggeration=12.0,
537+
late_exaggeration=1.0,
538+
learning_rate=200.0,
539+
max_iter=1000,
540+
n_iter_without_progress=300,
541+
n_iter="deprecated",
542+
min_grad_norm=1e-07,
543+
metric='euclidean',
544+
metric_params=None,
545+
init='random',
546+
random_state=None,
547+
method='fft',
548+
angle=0.5,
549+
n_neighbors=90,
550+
perplexity_max_iter=100,
551+
exaggeration_iter=250,
552+
pre_momentum=0.5,
553+
post_momentum=0.8,
554+
learning_rate_method='adaptive',
555+
square_distances=True,
556+
precomputed_knn=None,
557+
verbose=False,
558+
handle=None,
559+
output_type=None,
560+
):
561+
super().__init__(handle=handle, verbose=verbose, output_type=output_type)
545562
self.n_components = n_components
546563
self.perplexity = perplexity
547564
self.early_exaggeration = early_exaggeration
548565
self.late_exaggeration = late_exaggeration
549566
self.learning_rate = learning_rate
550-
self.n_iter = n_iter
567+
self.max_iter = max_iter
551568
self.n_iter_without_progress = n_iter_without_progress
569+
self.n_iter = n_iter
552570
self.min_grad_norm = min_grad_norm
553571
self.metric = metric
554572
self.metric_params = metric_params

python/cuml/tests/test_tsne.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ def test_tsne_large(nrows, ncols, method):
219219
tsne = TSNE(
220220
random_state=1,
221221
exaggeration_iter=1,
222-
n_iter=2,
222+
max_iter=2,
223223
method=method,
224224
min_grad_norm=1e-12,
225225
)
@@ -430,5 +430,17 @@ def test_tsne_n_iter(algorithm):
430430
n_samples=1000, n_features=64, centers=5, random_state=42
431431
)
432432
model = TSNE(n_components=2, random_state=42).fit(X)
433+
assert model.n_iter_ > 0
434+
assert model.n_iter_ <= model.max_iter
435+
436+
437+
def test_tsne_n_iter_deprecated():
438+
X, _ = make_blobs(
439+
n_samples=1000, n_features=64, centers=5, random_state=42
440+
)
441+
model = TSNE(n_components=2, random_state=42, n_iter=2)
442+
with pytest.warns(FutureWarning, match="n_iter"):
443+
model.fit(X)
444+
433445
assert model.n_iter_ > 0
434446
assert model.n_iter_ <= model.n_iter

0 commit comments

Comments
 (0)