Skip to content

Commit 2221387

Browse files
Sebastian Saegerjnothman
authored andcommitted
FIX n_iter_without_progress and min_grad_norm in TSNE
Adds tests for n_iter_without_progress and min_grad_norm
1 parent 2b0439c commit 2221387

File tree

2 files changed

+74
-5
lines changed

2 files changed

+74
-5
lines changed

sklearn/manifold/t_sne.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -546,15 +546,19 @@ class TSNE(BaseEstimator):
546546
least 200.
547547
548548
n_iter_without_progress : int, optional (default: 30)
549+
Only used if method='exact'
549550
Maximum number of iterations without progress before we abort the
550-
optimization.
551+
optimization. If method='barnes_hut' this parameter is fixed to
552+
a value of 30 and cannot be changed.
551553
552554
.. versionadded:: 0.17
553555
parameter *n_iter_without_progress* to control stopping criteria.
554556
555-
min_grad_norm : float, optional (default: 1E-7)
557+
min_grad_norm : float, optional (default: 1e-7)
558+
Only used if method='exact'
556559
If the gradient norm is below this threshold, the optimization will
557-
be aborted.
560+
be aborted. If method='barnes_hut' this parameter is fixed to a value
561+
of 1e-3 and cannot be changed.
558562
559563
metric : string or callable, optional
560564
The metric to use when calculating distance between instances in a
@@ -802,9 +806,9 @@ def _tsne(self, P, degrees_of_freedom, n_samples, random_state,
802806
self.n_components)
803807
params = X_embedded.ravel()
804808

805-
opt_args = {}
806809
opt_args = {"n_iter": 50, "momentum": 0.5, "it": 0,
807810
"learning_rate": self.learning_rate,
811+
"n_iter_without_progress": self.n_iter_without_progress,
808812
"verbose": self.verbose, "n_iter_check": 25,
809813
"kwargs": dict(skip_num_points=skip_num_points)}
810814
if self.method == 'barnes_hut':
@@ -829,7 +833,7 @@ def _tsne(self, P, degrees_of_freedom, n_samples, random_state,
829833
opt_args['args'] = [P, degrees_of_freedom, n_samples,
830834
self.n_components]
831835
opt_args['min_error_diff'] = 0.0
832-
opt_args['min_grad_norm'] = 0.0
836+
opt_args['min_grad_norm'] = self.min_grad_norm
833837

834838
# Early exaggeration
835839
P *= self.early_exaggeration

sklearn/manifold/tests/test_t_sne.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from sklearn.utils.testing import assert_array_almost_equal
1212
from sklearn.utils.testing import assert_less
1313
from sklearn.utils.testing import assert_raises_regexp
14+
from sklearn.utils.testing import assert_in
1415
from sklearn.utils import check_random_state
1516
from sklearn.manifold.t_sne import _joint_probabilities
1617
from sklearn.manifold.t_sne import _joint_probabilities_nn
@@ -560,3 +561,67 @@ def test_index_offset():
560561
# Make sure translating between 1D and N-D indices are preserved
561562
assert_equal(_barnes_hut_tsne.test_index2offset(), 1)
562563
assert_equal(_barnes_hut_tsne.test_index_offset(), 1)
564+
565+
566+
def test_n_iter_without_progress():
567+
# Make sure that the parameter n_iter_without_progress is used correctly
568+
random_state = check_random_state(0)
569+
X = random_state.randn(100, 2)
570+
tsne = TSNE(n_iter_without_progress=2, verbose=2,
571+
random_state=0, method='exact')
572+
573+
old_stdout = sys.stdout
574+
sys.stdout = StringIO()
575+
try:
576+
tsne.fit_transform(X)
577+
finally:
578+
out = sys.stdout.getvalue()
579+
sys.stdout.close()
580+
sys.stdout = old_stdout
581+
582+
# The output needs to contain the value of n_iter_without_progress
583+
assert_in("did not make any progress during the "
584+
"last 2 episodes. Finished.", out)
585+
586+
587+
def test_min_grad_norm():
588+
# Make sure that the parameter min_grad_norm is used correctly
589+
random_state = check_random_state(0)
590+
X = random_state.randn(100, 2)
591+
min_grad_norm = 0.002
592+
tsne = TSNE(min_grad_norm=min_grad_norm, verbose=2,
593+
random_state=0, method='exact')
594+
595+
old_stdout = sys.stdout
596+
sys.stdout = StringIO()
597+
try:
598+
tsne.fit_transform(X)
599+
finally:
600+
out = sys.stdout.getvalue()
601+
sys.stdout.close()
602+
sys.stdout = old_stdout
603+
604+
lines_out = out.split('\n')
605+
606+
# extract the gradient norm from the verbose output
607+
gradient_norm_values = []
608+
for line in lines_out:
609+
# When the computation is Finished just an old gradient norm value
610+
# is repeated that we do not need to store
611+
if 'Finished' in line:
612+
break
613+
614+
start_grad_norm = line.find('gradient norm')
615+
if start_grad_norm >= 0:
616+
line = line[start_grad_norm:]
617+
line = line.replace('gradient norm = ', '')
618+
gradient_norm_values.append(float(line))
619+
620+
# Compute how often the gradient norm is smaller than min_grad_norm
621+
gradient_norm_values = np.array(gradient_norm_values)
622+
n_smaller_gradient_norms = \
623+
len(gradient_norm_values[gradient_norm_values <= min_grad_norm])
624+
625+
# The gradient norm can be smaller than min_grad_norm at most once,
626+
# because in the moment it becomes smaller the optimization stops
627+
assert_less_equal(n_smaller_gradient_norms, 1)

0 commit comments

Comments
 (0)