diff --git a/.travis.yml b/.travis.yml index 321d195a..d294c294 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,23 +7,12 @@ env: - SKGGM_VERSION=a0ed406586c4364ea3297a658f415e13b5cbdaf8 matrix: include: - - name: "Pytest python 2.7 with skggm" - python: "2.7" + - name: "Pytest python 3.6 without skggm" + python: "3.6" before_install: - sudo apt-get install liblapack-dev - pip install --upgrade pip pytest - pip install wheel cython numpy scipy codecov pytest-cov scikit-learn - - pip install git+https://github.com/skggm/skggm.git@${SKGGM_VERSION}; - script: - - pytest test --cov; - after_success: - - bash <(curl -s https://codecov.io/bash) - - name: "Pytest python 3.4 without skggm" - python: "3.4" - before_install: - - sudo apt-get install liblapack-dev - - pip install --upgrade pip "pytest<5" - - pip install wheel cython numpy scipy codecov pytest-cov scikit-learn script: - pytest test --cov; after_success: diff --git a/README.rst b/README.rst index 5afe556c..ceb2eb33 100644 --- a/README.rst +++ b/README.rst @@ -19,7 +19,7 @@ metric-learn contains efficient Python implementations of several popular superv **Dependencies** -- Python 2.7+, 3.4+ +- Python 3.6+ - numpy, scipy, scikit-learn>=0.20.3 **Optional dependencies** diff --git a/metric_learn/__init__.py b/metric_learn/__init__.py index b036ccfa..c9d53883 100644 --- a/metric_learn/__init__.py +++ b/metric_learn/__init__.py @@ -1,5 +1,3 @@ -from __future__ import absolute_import - from .constraints import Constraints from .covariance import Covariance from .itml import ITML, ITML_Supervised diff --git a/metric_learn/_util.py b/metric_learn/_util.py index 77e8d9fa..764a34c8 100644 --- a/metric_learn/_util.py +++ b/metric_learn/_util.py @@ -1,5 +1,4 @@ import numpy as np -import six from numpy.linalg import LinAlgError from sklearn.datasets import make_spd_matrix from sklearn.decomposition import PCA @@ -283,7 +282,7 @@ def make_name(estimator): if a string is given """ if estimator is not None: - if isinstance(estimator, six.string_types): + if isinstance(estimator, str): estimator_name = estimator else: estimator_name = estimator.__class__.__name__ diff --git a/metric_learn/base_metric.py b/metric_learn/base_metric.py index 65692aed..d1af0821 100644 --- a/metric_learn/base_metric.py +++ b/metric_learn/base_metric.py @@ -8,12 +8,11 @@ from sklearn.metrics import roc_auc_score, roc_curve, precision_recall_curve import numpy as np from abc import ABCMeta, abstractmethod -import six from ._util import ArrayIndexer, check_input, validate_vector import warnings -class BaseMetricLearner(six.with_metaclass(ABCMeta, BaseEstimator)): +class BaseMetricLearner(BaseEstimator, metaclass=ABCMeta): """ Base class for all metric-learners. @@ -145,7 +144,7 @@ def get_metric(self): """ -class MetricTransformer(six.with_metaclass(ABCMeta)): +class MetricTransformer(metaclass=ABCMeta): @abstractmethod def transform(self, X): @@ -163,8 +162,8 @@ def transform(self, X): """ -class MahalanobisMixin(six.with_metaclass(ABCMeta, BaseMetricLearner, - MetricTransformer)): +class MahalanobisMixin(BaseMetricLearner, MetricTransformer, + metaclass=ABCMeta): r"""Mahalanobis metric learning algorithms. Algorithm that learns a Mahalanobis (pseudo) distance :math:`d_M(x, x')`, diff --git a/metric_learn/constraints.py b/metric_learn/constraints.py index b15d0277..2d86b819 100644 --- a/metric_learn/constraints.py +++ b/metric_learn/constraints.py @@ -4,7 +4,6 @@ """ import numpy as np import warnings -from six.moves import xrange from sklearn.utils import check_random_state from sklearn.neighbors import NearestNeighbors @@ -245,7 +244,7 @@ def chunks(self, num_chunks=100, chunk_size=2, random_state=None): chunks = -np.ones_like(self.partial_labels, dtype=int) uniq, lookup = np.unique(self.partial_labels, return_inverse=True) unknown_uniq = np.where(uniq < 0)[0] - all_inds = [set(np.where(lookup == c)[0]) for c in xrange(len(uniq)) + all_inds = [set(np.where(lookup == c)[0]) for c in range(len(uniq)) if c not in unknown_uniq] max_chunks = int(np.sum([len(s) // chunk_size for s in all_inds])) if max_chunks < num_chunks: diff --git a/metric_learn/covariance.py b/metric_learn/covariance.py index 7214dd62..3b218e6d 100644 --- a/metric_learn/covariance.py +++ b/metric_learn/covariance.py @@ -2,7 +2,6 @@ Covariance metric (baseline method) """ -from __future__ import absolute_import import numpy as np import scipy from sklearn.base import TransformerMixin diff --git a/metric_learn/itml.py b/metric_learn/itml.py index 5db438d8..48d5a222 100644 --- a/metric_learn/itml.py +++ b/metric_learn/itml.py @@ -2,10 +2,8 @@ Information Theoretic Metric Learning (ITML) """ -from __future__ import print_function, absolute_import import warnings import numpy as np -from six.moves import xrange from sklearn.exceptions import ChangedBehaviorWarning from sklearn.metrics import pairwise_distances from sklearn.utils.validation import check_array @@ -69,7 +67,7 @@ def _fit(self, pairs, y, bounds=None): pos_vv = pos_pairs[:, 0, :] - pos_pairs[:, 1, :] neg_vv = neg_pairs[:, 0, :] - neg_pairs[:, 1, :] - for it in xrange(self.max_iter): + for it in range(self.max_iter): # update positives for i, v in enumerate(pos_vv): wtw = v.dot(A).dot(v) # scalar diff --git a/metric_learn/lfda.py b/metric_learn/lfda.py index a970e789..2feed169 100644 --- a/metric_learn/lfda.py +++ b/metric_learn/lfda.py @@ -1,11 +1,9 @@ """ Local Fisher Discriminant Analysis (LFDA) """ -from __future__ import division, absolute_import import numpy as np import scipy import warnings -from six.moves import xrange from sklearn.metrics import pairwise_distances from sklearn.base import TransformerMixin @@ -127,7 +125,7 @@ def fit(self, X, y): tSb = np.zeros((d, d)) tSw = np.zeros((d, d)) - for c in xrange(num_classes): + for c in range(num_classes): Xc = X[y == c] nc = Xc.shape[0] diff --git a/metric_learn/lmnn.py b/metric_learn/lmnn.py index a026a8f6..12eb5ab1 100644 --- a/metric_learn/lmnn.py +++ b/metric_learn/lmnn.py @@ -1,11 +1,9 @@ """ Large Margin Nearest Neighbor Metric learning (LMNN) """ -from __future__ import print_function, absolute_import import numpy as np import warnings from collections import Counter -from six.moves import xrange from sklearn.exceptions import ChangedBehaviorWarning from sklearn.metrics import euclidean_distances from sklearn.base import TransformerMixin @@ -229,7 +227,7 @@ def fit(self, X, y): "| learning rate") # main loop - for it in xrange(2, self.max_iter): + for it in range(2, self.max_iter): # then at each iteration, we try to find a value of L that has better # objective than the previous L, following the gradient: while True: @@ -293,7 +291,7 @@ def _loss_grad(self, X, L, dfG, k, reg, target_neighbors, label_inds): # compute the gradient total_active = 0 df = np.zeros((X.shape[1], X.shape[1])) - for nn_idx in reversed(xrange(k)): # note: reverse not useful here + for nn_idx in reversed(range(k)): # note: reverse not useful here act1 = g0 < g1[:, nn_idx] act2 = g0 < g2[:, nn_idx] total_active += act1.sum() + act2.sum() diff --git a/metric_learn/lsml.py b/metric_learn/lsml.py index 5e84bf86..0cf9dc22 100644 --- a/metric_learn/lsml.py +++ b/metric_learn/lsml.py @@ -2,11 +2,9 @@ Metric Learning from Relative Comparisons by Minimizing Squared Residual (LSML) """ -from __future__ import print_function, absolute_import, division import warnings import numpy as np import scipy.linalg -from six.moves import xrange from sklearn.base import TransformerMixin from sklearn.exceptions import ChangedBehaviorWarning @@ -66,7 +64,7 @@ def _fit(self, quadruplets, weights=None): s_best = self._total_loss(M, vab, vcd, prior_inv) if self.verbose: print('initial loss', s_best) - for it in xrange(1, self.max_iter + 1): + for it in range(1, self.max_iter + 1): grad = self._gradient(M, vab, vcd, prior_inv) grad_norm = scipy.linalg.norm(grad) if grad_norm < self.tol: diff --git a/metric_learn/mlkr.py b/metric_learn/mlkr.py index c65341be..9b84dba8 100644 --- a/metric_learn/mlkr.py +++ b/metric_learn/mlkr.py @@ -1,7 +1,6 @@ """ Metric Learning for Kernel Regression (MLKR) """ -from __future__ import division, print_function import time import sys import warnings diff --git a/metric_learn/mmc.py b/metric_learn/mmc.py index 3ef9c534..330e2113 100644 --- a/metric_learn/mmc.py +++ b/metric_learn/mmc.py @@ -1,8 +1,6 @@ """Mahalanobis Metric for Clustering (MMC)""" -from __future__ import print_function, absolute_import, division import warnings import numpy as np -from six.moves import xrange from sklearn.base import TransformerMixin from sklearn.utils.validation import assert_all_finite from sklearn.exceptions import ChangedBehaviorWarning @@ -110,12 +108,12 @@ def _fit_full(self, pairs, y): A_old = A.copy() - for cycle in xrange(self.max_iter): + for cycle in range(self.max_iter): # projection of constraints C1 and C2 satisfy = False - for it in xrange(self.max_proj): + for it in range(self.max_proj): # First constraint: # f(A) = \sum_{i,j \in S} d_ij' A d_ij <= t (1) diff --git a/metric_learn/nca.py b/metric_learn/nca.py index d09e7282..217d7d28 100644 --- a/metric_learn/nca.py +++ b/metric_learn/nca.py @@ -2,7 +2,6 @@ Neighborhood Components Analysis (NCA) """ -from __future__ import absolute_import import warnings import time import sys diff --git a/metric_learn/rca.py b/metric_learn/rca.py index 32024a43..2004b9d4 100644 --- a/metric_learn/rca.py +++ b/metric_learn/rca.py @@ -2,10 +2,8 @@ Relative Components Analysis (RCA) """ -from __future__ import absolute_import import numpy as np import warnings -from six.moves import xrange from sklearn.base import TransformerMixin from sklearn.exceptions import ChangedBehaviorWarning @@ -22,7 +20,7 @@ def _chunk_mean_centering(data, chunks): # mean on it chunk_data = data[chunk_mask].astype(float, copy=False) chunk_labels = chunks[chunk_mask] - for c in xrange(num_chunks): + for c in range(num_chunks): mask = chunk_labels == c chunk_data[mask] -= chunk_data[mask].mean(axis=0) diff --git a/metric_learn/sdml.py b/metric_learn/sdml.py index 38c50955..f7c801e8 100644 --- a/metric_learn/sdml.py +++ b/metric_learn/sdml.py @@ -2,7 +2,6 @@ Sparse High-Dimensional Metric Learning (SDML) """ -from __future__ import absolute_import import warnings import numpy as np from sklearn.base import TransformerMixin diff --git a/setup.py b/setup.py index c8b38b7c..1e6f0002 100755 --- a/setup.py +++ b/setup.py @@ -29,7 +29,7 @@ classifiers=[ 'Development Status :: 4 - Beta', 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', + 'Programming Language :: Python :: 3', 'Operating System :: OS Independent', 'Intended Audience :: Science/Research', 'Topic :: Scientific/Engineering' @@ -39,7 +39,6 @@ 'numpy', 'scipy', 'scikit-learn', - 'six' ], extras_require=dict( docs=['sphinx', 'shinx_rtd_theme', 'numpydoc'], diff --git a/test/metric_learn_test.py b/test/metric_learn_test.py index 5a271890..a97f6437 100644 --- a/test/metric_learn_test.py +++ b/test/metric_learn_test.py @@ -4,7 +4,6 @@ import numpy as np import scipy from scipy.optimize import check_grad, approx_fprime -from six.moves import xrange from sklearn.metrics import pairwise_distances, euclidean_distances from sklearn.datasets import (load_iris, make_classification, make_regression, make_spd_matrix) @@ -32,7 +31,7 @@ def class_separation(X, labels): unique_labels, label_inds = np.unique(labels, return_inverse=True) ratio = 0 - for li in xrange(len(unique_labels)): + for li in range(len(unique_labels)): Xc = X[label_inds == li] Xnc = X[label_inds != li] ratio += pairwise_distances(Xc).mean() / pairwise_distances(Xc, Xnc).mean() @@ -385,15 +384,15 @@ def loss_fn(L, X, y, target_neighbors, reg): for j in target_neighbors[i]: loss += (1 - reg) * np.sum((Lx[i] - Lx[j]) ** 2) grad += (1 - reg) * np.outer(Lx[i] - Lx[j], X[i] - X[j]) - for l in range(X.shape[0]): - if y[i] != y[l]: + for k in range(X.shape[0]): + if y[i] != y[k]: hin, active = hinge(1 + np.sum((Lx[i] - Lx[j])**2) - - np.sum((Lx[i] - Lx[l])**2)) + np.sum((Lx[i] - Lx[k])**2)) total_active += active if active: loss += reg * hin grad += (reg * (np.outer(Lx[i] - Lx[j], X[i] - X[j]) - - np.outer(Lx[i] - Lx[l], X[i] - X[l]))) + np.outer(Lx[i] - Lx[k], X[i] - X[k]))) grad = 2 * grad return grad, loss, total_active @@ -521,7 +520,7 @@ def test_toy_ex_lmnn(X, y, loss): # storage a1 = [None] * k a2 = [None] * k - for nn_idx in xrange(k): + for nn_idx in range(k): a1[nn_idx] = np.array([]) a2[nn_idx] = np.array([]) diff --git a/test/test_base_metric.py b/test/test_base_metric.py index b2b1d339..0b1fbd22 100644 --- a/test/test_base_metric.py +++ b/test/test_base_metric.py @@ -16,111 +16,59 @@ class TestStringRepr(unittest.TestCase): def test_covariance(self): self.assertEqual(remove_spaces(str(metric_learn.Covariance())), - remove_spaces("Covariance(preprocessor=None)")) + remove_spaces("Covariance()")) def test_lmnn(self): self.assertEqual( - remove_spaces(str(metric_learn.LMNN())), - remove_spaces( - "LMNN(convergence_tol=0.001, init=None, k=3, " - "learn_rate=1e-07, " - "max_iter=1000, min_iter=50, n_components=None, " - "num_dims='deprecated', preprocessor=None, random_state=None, " - "regularization=0.5, use_pca='deprecated', verbose=False)")) + remove_spaces(str(metric_learn.LMNN(convergence_tol=0.01, k=6))), + remove_spaces("LMNN(convergence_tol=0.01, k=6)")) def test_nca(self): - self.assertEqual(remove_spaces(str(metric_learn.NCA())), - remove_spaces("NCA(init=None, max_iter=100," - "n_components=None, " - "num_dims='deprecated', " - "preprocessor=None, random_state=None, " - "tol=None, verbose=False)")) + self.assertEqual(remove_spaces(str(metric_learn.NCA(max_iter=42))), + remove_spaces("NCA(max_iter=42)")) def test_lfda(self): - self.assertEqual(remove_spaces(str(metric_learn.LFDA())), - remove_spaces( - "LFDA(embedding_type='weighted', k=None, " - "n_components=None, num_dims='deprecated'," - "preprocessor=None)")) + self.assertEqual(remove_spaces(str(metric_learn.LFDA(k=2))), + remove_spaces("LFDA(k=2)")) def test_itml(self): - self.assertEqual(remove_spaces(str(metric_learn.ITML())), - remove_spaces(""" -ITML(A0='deprecated', convergence_threshold=0.001, gamma=1.0, - max_iter=1000, preprocessor=None, prior='identity', random_state=None, - verbose=False) -""")) - self.assertEqual(remove_spaces(str(metric_learn.ITML_Supervised())), - remove_spaces(""" -ITML_Supervised(A0='deprecated', bounds='deprecated', - convergence_threshold=0.001, gamma=1.0, - max_iter=1000, num_constraints=None, num_labeled='deprecated', - preprocessor=None, prior='identity', random_state=None, verbose=False) -""")) + self.assertEqual(remove_spaces(str(metric_learn.ITML(gamma=0.5))), + remove_spaces("ITML(gamma=0.5)")) + self.assertEqual( + remove_spaces(str(metric_learn.ITML_Supervised(num_constraints=7))), + remove_spaces("ITML_Supervised(num_constraints=7)")) def test_lsml(self): - self.assertEqual(remove_spaces(str(metric_learn.LSML())), - remove_spaces(""" -LSML(max_iter=1000, preprocessor=None, prior=None, - random_state=None, tol=0.001, verbose=False) -""")) - self.assertEqual(remove_spaces(str(metric_learn.LSML_Supervised())), - remove_spaces(""" -LSML_Supervised(max_iter=1000, num_constraints=None, - num_labeled='deprecated', preprocessor=None, prior=None, - random_state=None, tol=0.001, verbose=False, weights=None) -""")) + self.assertEqual(remove_spaces(str(metric_learn.LSML(tol=0.1))), + remove_spaces("LSML(tol=0.1)")) + self.assertEqual( + remove_spaces(str(metric_learn.LSML_Supervised(verbose=True))), + remove_spaces("LSML_Supervised(verbose=True)")) def test_sdml(self): - self.assertEqual(remove_spaces(str(metric_learn.SDML())), - remove_spaces(""" -SDML(balance_param=0.5, preprocessor=None, prior=None, random_state=None, - sparsity_param=0.01, use_cov='deprecated', verbose=False) -""")) - self.assertEqual(remove_spaces(str(metric_learn.SDML_Supervised())), - remove_spaces(""" -SDML_Supervised(balance_param=0.5, num_constraints=None, - num_labeled='deprecated', preprocessor=None, prior=None, - random_state=None, sparsity_param=0.01, use_cov='deprecated', - verbose=False) -""")) + self.assertEqual(remove_spaces(str(metric_learn.SDML(verbose=True))), + remove_spaces("SDML(verbose=True)")) + self.assertEqual( + remove_spaces(str(metric_learn.SDML_Supervised(sparsity_param=0.5))), + remove_spaces("SDML_Supervised(sparsity_param=0.5)")) def test_rca(self): - self.assertEqual(remove_spaces(str(metric_learn.RCA())), - remove_spaces("RCA(n_components=None, " - "num_dims='deprecated', " - "pca_comps='deprecated', " - "preprocessor=None)")) - self.assertEqual(remove_spaces(str(metric_learn.RCA_Supervised())), - remove_spaces( - "RCA_Supervised(chunk_size=2, " - "n_components=None, num_chunks=100, " - "num_dims='deprecated', pca_comps='deprecated', " - "preprocessor=None, random_state=None)")) + self.assertEqual(remove_spaces(str(metric_learn.RCA(n_components=3))), + remove_spaces("RCA(n_components=3)")) + self.assertEqual( + remove_spaces(str(metric_learn.RCA_Supervised(num_chunks=5))), + remove_spaces("RCA_Supervised(num_chunks=5)")) def test_mlkr(self): - self.assertEqual(remove_spaces(str(metric_learn.MLKR())), - remove_spaces("MLKR(A0='deprecated', init=None," - "max_iter=1000, n_components=None," - "num_dims='deprecated', preprocessor=None," - "random_state=None, tol=None, " - "verbose=False)" - )) + self.assertEqual(remove_spaces(str(metric_learn.MLKR(max_iter=777))), + remove_spaces("MLKR(max_iter=777)")) def test_mmc(self): - self.assertEqual(remove_spaces(str(metric_learn.MMC())), - remove_spaces(""" -MMC(A0='deprecated', convergence_threshold=0.001, diagonal=False, - diagonal_c=1.0, init=None, max_iter=100, max_proj=10000, - preprocessor=None, random_state=None, verbose=False) -""")) - self.assertEqual(remove_spaces(str(metric_learn.MMC_Supervised())), - remove_spaces(""" -MMC_Supervised(A0='deprecated', convergence_threshold=1e-06, diagonal=False, - diagonal_c=1.0, init=None, max_iter=100, max_proj=10000, - num_constraints=None, num_labeled='deprecated', preprocessor=None, - random_state=None, verbose=False) -""")) + self.assertEqual(remove_spaces(str(metric_learn.MMC(diagonal=True))), + remove_spaces("MMC(diagonal=True)")) + self.assertEqual( + remove_spaces(str(metric_learn.MMC_Supervised(max_iter=1))), + remove_spaces("MMC_Supervised(max_iter=1)")) @pytest.mark.parametrize('estimator, build_dataset', metric_learners, diff --git a/test/test_pairs_classifiers.py b/test/test_pairs_classifiers.py index 6c71abcd..c5ca27f4 100644 --- a/test/test_pairs_classifiers.py +++ b/test/test_pairs_classifiers.py @@ -1,5 +1,3 @@ -from __future__ import division - from functools import partial import pytest