From c7a4c2e43d3e31e6928468cadd8f322070885eda Mon Sep 17 00:00:00 2001 From: William de Vazelhes Date: Wed, 2 Jan 2019 17:18:02 +0100 Subject: [PATCH 1/3] FIX put back nca's tests --- test/metric_learn_test.py | 106 +++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/test/metric_learn_test.py b/test/metric_learn_test.py index 74bc25de..5c44cc8f 100644 --- a/test/metric_learn_test.py +++ b/test/metric_learn_test.py @@ -6,7 +6,7 @@ from six.moves import xrange from sklearn.metrics import pairwise_distances from sklearn.datasets import load_iris, make_classification, make_regression -from numpy.testing import assert_array_almost_equal +from numpy.testing import assert_array_almost_equal, assert_array_equal from sklearn.utils.testing import assert_warns_message from sklearn.exceptions import ConvergenceWarning from sklearn.utils.validation import check_X_y @@ -138,6 +138,110 @@ def test_iris(self): csep = class_separation(nca.transform(self.iris_points), self.iris_labels) self.assertLess(csep, 0.20) + def test_finite_differences(self): + """Test gradient of loss function + + Assert that the gradient is almost equal to its finite differences + approximation. + """ + # Initialize the transformation `M`, as well as `X` and `y` and `NCA` + X, y = make_classification() + M = np.random.randn(np.random.randint(1, X.shape[1] + 1), X.shape[1]) + mask = y[:, np.newaxis] == y[np.newaxis, :] + nca = NCA() + nca.n_iter_ = 0 + + def fun(M): + return nca._loss_grad_lbfgs(M, X, mask)[0] + + def grad(M): + return nca._loss_grad_lbfgs(M, X, mask)[1].ravel() + + # compute relative error + rel_diff = check_grad(fun, grad, M.ravel()) / np.linalg.norm(grad(M)) + np.testing.assert_almost_equal(rel_diff, 0., decimal=6) + + def test_simple_example(self): + """Test on a simple example. + + Puts four points in the input space where the opposite labels points are + next to each other. After transform the same labels points should be next + to each other. + + """ + X = np.array([[0, 0], [0, 1], [2, 0], [2, 1]]) + y = np.array([1, 0, 1, 0]) + nca = NCA(num_dims=2,) + nca.fit(X, y) + Xansformed = nca.transform(X) + np.testing.assert_equal(pairwise_distances(Xansformed).argsort()[:, 1], + np.array([2, 3, 0, 1])) + + def test_deprecation(self): + # test that the right deprecation message is thrown. + # TODO: remove in v.0.5 + X = np.array([[0, 0], [0, 1], [2, 0], [2, 1]]) + y = np.array([1, 0, 1, 0]) + nca = NCA(num_dims=2, learning_rate=0.01) + msg = ('"learning_rate" parameter is not used.' + ' It has been deprecated in version 0.4 and will be' + 'removed in 0.5') + assert_warns_message(DeprecationWarning, msg, nca.fit, X, y) + + def test_singleton_class(self): + X = self.iris_points + y = self.iris_labels + + # one singleton class: test fitting works + singleton_class = 1 + ind_singleton, = np.where(y == singleton_class) + y[ind_singleton] = 2 + y[ind_singleton[0]] = singleton_class + + nca = NCA(max_iter=30) + nca.fit(X, y) + + # One non-singleton class: test fitting works + ind_1, = np.where(y == 1) + ind_2, = np.where(y == 2) + y[ind_1] = 0 + y[ind_1[0]] = 1 + y[ind_2] = 0 + y[ind_2[0]] = 2 + + nca = NCA(max_iter=30) + nca.fit(X, y) + + # Only singleton classes: test fitting does nothing (the gradient + # must be null in this case, so the final matrix must stay like + # the initialization) + ind_0, = np.where(y == 0) + ind_1, = np.where(y == 1) + ind_2, = np.where(y == 2) + X = X[[ind_0[0], ind_1[0], ind_2[0]]] + y = y[[ind_0[0], ind_1[0], ind_2[0]]] + + EPS = np.finfo(float).eps + A = np.zeros((X.shape[1], X.shape[1])) + np.fill_diagonal(A, + 1. / (np.maximum(X.max(axis=0) - X.min(axis=0), EPS))) + nca = NCA(max_iter=30, num_dims=X.shape[1]) + nca.fit(X, y) + assert_array_equal(nca.A_, A) + + def test_one_class(self): + # if there is only one class the gradient is null, so the final matrix + # must stay like the initialization + X = self.iris_points[self.iris_labels == 0] + y = self.iris_labels[self.iris_labels == 0] + EPS = np.finfo(float).eps + A = np.zeros((X.shape[1], X.shape[1])) + np.fill_diagonal(A, + 1. / (np.maximum(X.max(axis=0) - X.min(axis=0), EPS))) + nca = NCA(max_iter=30, num_dims=X.shape[1]) + nca.fit(X, y) + assert_array_equal(nca.A_, A) + class TestLFDA(MetricTestCase): def test_iris(self): From a926d6b040ddf835628b03b05bd9e1f2780f7eff Mon Sep 17 00:00:00 2001 From: William de Vazelhes Date: Wed, 2 Jan 2019 17:56:35 +0100 Subject: [PATCH 2/3] Remove deprecation test for learning rate because there is no more learning rate --- test/metric_learn_test.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/test/metric_learn_test.py b/test/metric_learn_test.py index 5c44cc8f..0c9d2bd4 100644 --- a/test/metric_learn_test.py +++ b/test/metric_learn_test.py @@ -177,17 +177,6 @@ def test_simple_example(self): np.testing.assert_equal(pairwise_distances(Xansformed).argsort()[:, 1], np.array([2, 3, 0, 1])) - def test_deprecation(self): - # test that the right deprecation message is thrown. - # TODO: remove in v.0.5 - X = np.array([[0, 0], [0, 1], [2, 0], [2, 1]]) - y = np.array([1, 0, 1, 0]) - nca = NCA(num_dims=2, learning_rate=0.01) - msg = ('"learning_rate" parameter is not used.' - ' It has been deprecated in version 0.4 and will be' - 'removed in 0.5') - assert_warns_message(DeprecationWarning, msg, nca.fit, X, y) - def test_singleton_class(self): X = self.iris_points y = self.iris_labels From 3ed8c116e2c74a229409320c6981120c84dea0eb Mon Sep 17 00:00:00 2001 From: William de Vazelhes Date: Wed, 2 Jan 2019 17:58:49 +0100 Subject: [PATCH 3/3] FIX: update tests with new terminology ('transformer_') --- test/metric_learn_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/metric_learn_test.py b/test/metric_learn_test.py index 0c9d2bd4..b6ed89d9 100644 --- a/test/metric_learn_test.py +++ b/test/metric_learn_test.py @@ -216,7 +216,7 @@ def test_singleton_class(self): 1. / (np.maximum(X.max(axis=0) - X.min(axis=0), EPS))) nca = NCA(max_iter=30, num_dims=X.shape[1]) nca.fit(X, y) - assert_array_equal(nca.A_, A) + assert_array_equal(nca.transformer_, A) def test_one_class(self): # if there is only one class the gradient is null, so the final matrix @@ -229,7 +229,7 @@ def test_one_class(self): 1. / (np.maximum(X.max(axis=0) - X.min(axis=0), EPS))) nca = NCA(max_iter=30, num_dims=X.shape[1]) nca.fit(X, y) - assert_array_equal(nca.A_, A) + assert_array_equal(nca.transformer_, A) class TestLFDA(MetricTestCase):