Skip to content

Define distance consistently as (x-y)^T*M*(x-y) #60

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,9 @@ default implementations for the methods ``metric``, ``transformer``, and
For an instance of a metric learner named ``foo`` learning from a set of
``d``-dimensional points, ``foo.metric()`` returns a ``d x d``
matrix ``M`` such that the distance between vectors ``x`` and ``y`` is
expressed ``sqrt((x-y).dot(inv(M)).dot(x-y))``.
expressed ``sqrt((x-y).dot(M).dot(x-y))``.
Using scipy's ``pdist`` function, this would look like
``pdist(X, metric='mahalanobis', VI=inv(foo.metric()))``.
``pdist(X, metric='mahalanobis', VI=foo.metric())``.

In the same scenario, ``foo.transformer()`` returns a ``d x d``
matrix ``L`` such that a vector ``x`` can be represented in the learned
Expand Down
6 changes: 3 additions & 3 deletions metric_learn/base_metric.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ def metric(self):
def transformer(self):
"""Computes the transformation matrix from the Mahalanobis matrix.

L = inv(cholesky(M))
L = cholesky(M).T

Returns
-------
L : (d x d) matrix
L : upper triangular (d x d) matrix
"""
return inv(cholesky(self.metric()))
return cholesky(self.metric()).T

def transform(self, X=None):
"""Applies the metric transformation.
Expand Down
6 changes: 5 additions & 1 deletion metric_learn/covariance.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,9 @@ def fit(self, X, y=None):
y : unused
"""
self.X_ = check_array(X, ensure_min_samples=2)
self.M_ = np.cov(self.X_.T)
self.M_ = np.cov(self.X_, rowvar = False)
if self.M_.ndim == 0:
self.M_ = 1./self.M_
else:
self.M_ = np.linalg.inv(self.M_)
return self
8 changes: 6 additions & 2 deletions metric_learn/lsml.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def __init__(self, tol=1e-3, max_iter=1000, prior=None, verbose=False):
tol : float, optional
max_iter : int, optional
prior : (d x d) matrix, optional
guess at a metric [default: covariance(X)]
guess at a metric [default: inv(covariance(X))]
verbose : bool, optional
if True, prints information while learning
"""
Expand All @@ -48,7 +48,11 @@ def _prepare_inputs(self, X, constraints, weights):
self.w_ = weights
self.w_ /= self.w_.sum() # weights must sum to 1
if self.prior is None:
self.M_ = np.cov(X.T)
self.M_ = np.cov(X, rowvar = False)
if self.M_.ndim == 0:
self.M_ = 1./self.M_
else:
self.M_ = np.linalg.inv(self.M_)
else:
self.M_ = self.prior

Expand Down
6 changes: 3 additions & 3 deletions metric_learn/sdml.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def _prepare_inputs(self, X, W):
W = check_array(W, accept_sparse=True)
# set up prior M
if self.use_cov:
self.M_ = np.cov(X.T)
self.M_ = pinvh(np.cov(X, rowvar = False))
else:
self.M_ = np.identity(X.shape[1])
L = laplacian(W, normed=False)
Expand All @@ -72,11 +72,11 @@ def fit(self, X, W):
Returns the instance.
"""
loss_matrix = self._prepare_inputs(X, W)
P = pinvh(self.M_) + self.balance_param * loss_matrix
P = self.M_ + self.balance_param * loss_matrix
emp_cov = pinvh(P)
# hack: ensure positive semidefinite
emp_cov = emp_cov.T.dot(emp_cov)
self.M_, _ = graph_lasso(emp_cov, self.sparsity_param, verbose=self.verbose)
_, self.M_ = graph_lasso(emp_cov, self.sparsity_param, verbose=self.verbose)
return self


Expand Down
2 changes: 1 addition & 1 deletion test/metric_learn_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ def test_iris(self):
itml.fit(self.iris_points, self.iris_labels)

csep = class_separation(itml.transform(), self.iris_labels)
self.assertLess(csep, 0.4) # it's not great
self.assertLess(csep, 0.2)


class TestLMNN(MetricTestCase):
Expand Down
80 changes: 80 additions & 0 deletions test/test_transformer_metric_conversion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import unittest
import numpy as np
from sklearn.datasets import load_iris
from numpy.testing import assert_array_almost_equal

from metric_learn import (
LMNN, NCA, LFDA, Covariance, MLKR,
LSML_Supervised, ITML_Supervised, SDML_Supervised, RCA_Supervised)


class TestTransformerMetricConversion(unittest.TestCase):
@classmethod
def setUpClass(self):
# runs once per test class
iris_data = load_iris()
self.X = iris_data['data']
self.y = iris_data['target']

def test_cov(self):
cov = Covariance()
cov.fit(self.X)
L = cov.transformer()
assert_array_almost_equal(L.T.dot(L), cov.metric())

def test_lsml_supervised(self):
seed = np.random.RandomState(1234)
lsml = LSML_Supervised(num_constraints=200)
lsml.fit(self.X, self.y, random_state=seed)
L = lsml.transformer()
assert_array_almost_equal(L.T.dot(L), lsml.metric())

def test_itml_supervised(self):
seed = np.random.RandomState(1234)
itml = ITML_Supervised(num_constraints=200)
itml.fit(self.X, self.y, random_state=seed)
L = itml.transformer()
assert_array_almost_equal(L.T.dot(L), itml.metric())

def test_lmnn(self):
lmnn = LMNN(k=5, learn_rate=1e-6, verbose=False)
lmnn.fit(self.X, self.y)
L = lmnn.transformer()
assert_array_almost_equal(L.T.dot(L), lmnn.metric())

def test_sdml_supervised(self):
seed = np.random.RandomState(1234)
sdml = SDML_Supervised(num_constraints=1500)
sdml.fit(self.X, self.y, random_state=seed)
L = sdml.transformer()
assert_array_almost_equal(L.T.dot(L), sdml.metric())

def test_nca(self):
n = self.X.shape[0]
nca = NCA(max_iter=(100000//n), learning_rate=0.01)
nca.fit(self.X, self.y)
L = nca.transformer()
assert_array_almost_equal(L.T.dot(L), nca.metric())

def test_lfda(self):
lfda = LFDA(k=2, num_dims=2)
lfda.fit(self.X, self.y)
L = lfda.transformer()
assert_array_almost_equal(L.T.dot(L), lfda.metric())

def test_rca_supervised(self):
seed = np.random.RandomState(1234)
rca = RCA_Supervised(num_dims=2, num_chunks=30, chunk_size=2)
rca.fit(self.X, self.y, random_state=seed)
L = rca.transformer()
assert_array_almost_equal(L.T.dot(L), rca.metric())

def test_mlkr(self):
mlkr = MLKR(num_dims=2)
mlkr.fit(self.X, self.y)
L = mlkr.transformer()
assert_array_almost_equal(L.T.dot(L), mlkr.metric())


if __name__ == '__main__':
unittest.main()