Skip to content

Commit 1d752f7

Browse files
author
mvargas33
committed
Add more tests. Fix 4 to 2 identation
1 parent 7ebc026 commit 1d752f7

File tree

2 files changed

+167
-111
lines changed

2 files changed

+167
-111
lines changed

metric_learn/base_metric.py

Lines changed: 63 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -178,82 +178,82 @@ class BilinearMixin(BaseMetricLearner, metaclass=ABCMeta):
178178
"""
179179

180180
def score_pairs(self, pairs):
181-
r"""Returns the learned Bilinear similarity between pairs.
181+
r"""Returns the learned Bilinear similarity between pairs.
182182
183-
This similarity is defined as: :math:`s_M(x, x') = x M x'`
184-
where ``M`` is the learned Bilinear matrix, for every pair of points
185-
``x`` and ``x'``.
183+
This similarity is defined as: :math:`s_M(x, x') = x M x'`
184+
where ``M`` is the learned Bilinear matrix, for every pair of points
185+
``x`` and ``x'``.
186186
187-
Parameters
188-
----------
189-
pairs : array-like, shape=(n_pairs, 2, n_features) or (n_pairs, 2)
190-
3D Array of pairs to score, with each row corresponding to two points,
191-
for 2D array of indices of pairs if the similarity learner uses a
192-
preprocessor.
187+
Parameters
188+
----------
189+
pairs : array-like, shape=(n_pairs, 2, n_features) or (n_pairs, 2)
190+
3D Array of pairs to score, with each row corresponding to two points,
191+
for 2D array of indices of pairs if the similarity learner uses a
192+
preprocessor.
193193
194-
Returns
195-
-------
196-
scores : `numpy.ndarray` of shape=(n_pairs,)
197-
The learned Bilinear similarity for every pair.
198-
199-
See Also
200-
--------
201-
get_metric : a method that returns a function to compute the similarity
202-
between two points. The difference with `score_pairs` is that it works
203-
on two 1D arrays and cannot use a preprocessor. Besides, the returned
204-
function is independent of the similarity learner and hence is not
205-
modified if the similarity learner is.
206-
207-
:ref:`Bilinear_similarity` : The section of the project documentation
208-
that describes Bilinear similarity.
209-
"""
210-
check_is_fitted(self, ['preprocessor_', 'components_'])
211-
pairs = check_input(pairs, type_of_inputs='tuples',
212-
preprocessor=self.preprocessor_,
213-
estimator=self, tuple_size=2)
214-
# Note: For bilinear order matters, dist(a,b) != dist(b,a)
215-
# We always choose first pair first, then second pair
216-
# (In contrast with Mahalanobis implementation)
217-
return np.sum(np.dot(pairs[:, 0, :], self.components_) * pairs[:, 1, :],
218-
axis=-1)
194+
Returns
195+
-------
196+
scores : `numpy.ndarray` of shape=(n_pairs,)
197+
The learned Bilinear similarity for every pair.
198+
199+
See Also
200+
--------
201+
get_metric : a method that returns a function to compute the similarity
202+
between two points. The difference with `score_pairs` is that it works
203+
on two 1D arrays and cannot use a preprocessor. Besides, the returned
204+
function is independent of the similarity learner and hence is not
205+
modified if the similarity learner is.
206+
207+
:ref:`Bilinear_similarity` : The section of the project documentation
208+
that describes Bilinear similarity.
209+
"""
210+
check_is_fitted(self, ['preprocessor_', 'components_'])
211+
pairs = check_input(pairs, type_of_inputs='tuples',
212+
preprocessor=self.preprocessor_,
213+
estimator=self, tuple_size=2)
214+
# Note: For bilinear order matters, dist(a,b) != dist(b,a)
215+
# We always choose first pair first, then second pair
216+
# (In contrast with Mahalanobis implementation)
217+
return np.sum(np.dot(pairs[:, 0, :], self.components_) * pairs[:, 1, :],
218+
axis=-1)
219219

220220
def get_metric(self):
221-
check_is_fitted(self, 'components_')
222-
components = self.components_.copy()
221+
check_is_fitted(self, 'components_')
222+
components = self.components_.copy()
223223

224-
def similarity_fun(u, v):
225-
"""This function computes the similarity between u and v, according to the
226-
previously learned similarity.
224+
def similarity_fun(u, v):
225+
"""This function computes the similarity between u and v, according to the
226+
previously learned similarity.
227227
228-
Parameters
229-
----------
230-
u : array-like, shape=(n_features,)
231-
The first point involved in the similarity computation.
228+
Parameters
229+
----------
230+
u : array-like, shape=(n_features,)
231+
The first point involved in the similarity computation.
232232
233-
v : array-like, shape=(n_features,)
234-
The second point involved in the similarity computation.
233+
v : array-like, shape=(n_features,)
234+
The second point involved in the similarity computation.
235235
236-
Returns
237-
-------
238-
similarity : float
239-
The similarity between u and v according to the new similarity.
240-
"""
241-
u = validate_vector(u)
242-
v = validate_vector(v)
243-
return np.dot(np.dot(u.T, components), v)
236+
Returns
237+
-------
238+
similarity : float
239+
The similarity between u and v according to the new similarity.
240+
"""
241+
u = validate_vector(u)
242+
v = validate_vector(v)
243+
return np.dot(np.dot(u.T, components), v)
244244

245-
return similarity_fun
245+
return similarity_fun
246246

247247
def get_bilinear_matrix(self):
248-
"""Returns a copy of the Bilinear matrix learned by the similarity learner.
248+
"""Returns a copy of the Bilinear matrix learned by the similarity learner.
249249
250-
Returns
251-
-------
252-
M : `numpy.ndarray`, shape=(n_features, n_features)
253-
The copy of the learned Bilinear matrix.
254-
"""
255-
check_is_fitted(self, 'components_')
256-
return self.components_
250+
Returns
251+
-------
252+
M : `numpy.ndarray`, shape=(n_features, n_features)
253+
The copy of the learned Bilinear matrix.
254+
"""
255+
check_is_fitted(self, 'components_')
256+
return self.components_
257257

258258

259259
class MahalanobisMixin(BaseMetricLearner, MetricTransformer,

test/test_bilinear_mixin.py

Lines changed: 104 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,125 @@
1+
from itertools import product
12
from metric_learn.base_metric import BilinearMixin
23
import numpy as np
34
from numpy.testing import assert_array_almost_equal
4-
5+
import pytest
6+
from metric_learn._util import make_context
7+
from sklearn import clone
8+
from sklearn.cluster import DBSCAN
59

610
class IdentityBilinearMixin(BilinearMixin):
7-
"""A simple Identity bilinear mixin that returns an identity matrix
8-
M as learned. Can change M for a random matrix calling random_M.
9-
Class for testing purposes.
10-
"""
11-
def __init__(self, preprocessor=None):
12-
super().__init__(preprocessor=preprocessor)
11+
"""A simple Identity bilinear mixin that returns an identity matrix
12+
M as learned. Can change M for a random matrix calling random_M.
13+
Class for testing purposes.
14+
"""
15+
def __init__(self, preprocessor=None):
16+
super().__init__(preprocessor=preprocessor)
1317

14-
def fit(self, X, y):
15-
X, y = self._prepare_inputs(X, y, ensure_min_samples=2)
16-
self.d = np.shape(X[0])[-1]
17-
self.components_ = np.identity(self.d)
18-
return self
18+
def fit(self, X, y):
19+
X, y = self._prepare_inputs(X, y, ensure_min_samples=2)
20+
self.d = np.shape(X[0])[-1]
21+
self.components_ = np.identity(self.d)
22+
return self
1923

20-
def random_M(self):
21-
self.components_ = np.random.rand(self.d, self.d)
24+
def random_M(self):
25+
self.components_ = np.random.rand(self.d, self.d)
2226

2327

2428
def test_same_similarity_with_two_methods():
25-
d = 100
26-
u = np.random.rand(d)
27-
v = np.random.rand(d)
28-
mixin = IdentityBilinearMixin()
29-
mixin.fit([u, v], [0, 0])
30-
mixin.random_M() # Dummy fit
29+
d = 100
30+
u = np.random.rand(d)
31+
v = np.random.rand(d)
32+
mixin = IdentityBilinearMixin()
33+
mixin.fit([u, v], [0, 0])
34+
mixin.random_M() # Dummy fit
3135

32-
# The distances must match, whether calc with get_metric() or score_pairs()
33-
dist1 = mixin.score_pairs([[u, v], [v, u]])
34-
dist2 = [mixin.get_metric()(u, v), mixin.get_metric()(v, u)]
36+
# The distances must match, whether calc with get_metric() or score_pairs()
37+
dist1 = mixin.score_pairs([[u, v], [v, u]])
38+
dist2 = [mixin.get_metric()(u, v), mixin.get_metric()(v, u)]
3539

36-
assert_array_almost_equal(dist1, dist2)
40+
assert_array_almost_equal(dist1, dist2)
3741

3842

3943
def test_check_correctness_similarity():
40-
d = 100
41-
u = np.random.rand(d)
42-
v = np.random.rand(d)
43-
mixin = IdentityBilinearMixin()
44-
mixin.fit([u, v], [0, 0]) # Identity fit
45-
dist1 = mixin.score_pairs([[u, v], [v, u]])
46-
u_v = np.dot(np.dot(u.T, np.identity(d)), v)
47-
v_u = np.dot(np.dot(v.T, np.identity(d)), u)
48-
desired = [u_v, v_u]
49-
assert_array_almost_equal(dist1, desired)
44+
d = 100
45+
u = np.random.rand(d)
46+
v = np.random.rand(d)
47+
mixin = IdentityBilinearMixin()
48+
mixin.fit([u, v], [0, 0]) # Identity fit
49+
dist1 = mixin.score_pairs([[u, v], [v, u]])
50+
dist2 = [mixin.get_metric()(u, v), mixin.get_metric()(v, u)]
5051

52+
u_v = np.dot(np.dot(u.T, np.identity(d)), v)
53+
v_u = np.dot(np.dot(v.T, np.identity(d)), u)
54+
desired = [u_v, v_u]
55+
assert_array_almost_equal(dist1, desired) # score_pairs
56+
assert_array_almost_equal(dist2, desired) # get_metric
5157

5258
def test_check_handmade_example():
53-
u = np.array([0, 1, 2])
54-
v = np.array([3, 4, 5])
55-
mixin = IdentityBilinearMixin()
56-
mixin.fit([u, v], [0, 0]) # Identity fit
57-
c = np.array([[2, 4, 6], [6, 4, 2], [1, 2, 3]])
58-
mixin.components_ = c # Force components_
59-
dists = mixin.score_pairs([[u, v], [v, u]])
60-
assert_array_almost_equal(dists, [96, 120])
59+
u = np.array([0, 1, 2])
60+
v = np.array([3, 4, 5])
61+
mixin = IdentityBilinearMixin()
62+
mixin.fit([u, v], [0, 0]) # Identity fit
63+
c = np.array([[2, 4, 6], [6, 4, 2], [1, 2, 3]])
64+
mixin.components_ = c # Force components_
65+
dists = mixin.score_pairs([[u, v], [v, u]])
66+
assert_array_almost_equal(dists, [96, 120])
6167

6268

6369
def test_check_handmade_symmetric_example():
64-
u = np.array([0, 1, 2])
65-
v = np.array([3, 4, 5])
66-
mixin = IdentityBilinearMixin()
67-
mixin.fit([u, v], [0, 0]) # Identity fit
68-
dists = mixin.score_pairs([[u, v], [v, u]])
69-
assert_array_almost_equal(dists, [14, 14])
70+
u = np.array([0, 1, 2])
71+
v = np.array([3, 4, 5])
72+
mixin = IdentityBilinearMixin()
73+
mixin.fit([u, v], [0, 0]) # Identity fit
74+
dists = mixin.score_pairs([[u, v], [v, u]])
75+
assert_array_almost_equal(dists, [14, 14])
76+
77+
78+
def test_score_pairs_finite():
79+
d = 100
80+
u = np.random.rand(d)
81+
v = np.random.rand(d)
82+
mixin = IdentityBilinearMixin()
83+
mixin.fit([u, v], [0, 0])
84+
mixin.random_M() # Dummy fit
85+
n = 100
86+
X = np.array([np.random.rand(d) for i in range(n)])
87+
pairs = np.array(list(product(X, X)))
88+
assert np.isfinite(mixin.score_pairs(pairs)).all()
89+
90+
91+
def test_score_pairs_dim():
92+
# scoring of 3D arrays should return 1D array (several tuples),
93+
# and scoring of 2D arrays (one tuple) should return an error (like
94+
# scikit-learn's error when scoring 1D arrays)
95+
d = 100
96+
u = np.random.rand(d)
97+
v = np.random.rand(d)
98+
mixin = IdentityBilinearMixin()
99+
mixin.fit([u, v], [0, 0])
100+
mixin.random_M() # Dummy fit
101+
n = 100
102+
X = np.array([np.random.rand(d) for i in range(n)])
103+
tuples = np.array(list(product(X, X)))
104+
assert mixin.score_pairs(tuples).shape == (tuples.shape[0],)
105+
context = make_context(mixin)
106+
msg = ("3D array of formed tuples expected{}. Found 2D array "
107+
"instead:\ninput={}. Reshape your data and/or use a preprocessor.\n"
108+
.format(context, tuples[1]))
109+
with pytest.raises(ValueError) as raised_error:
110+
mixin.score_pairs(tuples[1])
111+
assert str(raised_error.value) == msg
112+
113+
114+
def test_check_scikitlearn_compatibility():
115+
d = 100
116+
u = np.random.rand(d)
117+
v = np.random.rand(d)
118+
mixin = IdentityBilinearMixin()
119+
mixin.fit([u, v], [0, 0])
120+
mixin.random_M() # Dummy fit
121+
122+
n = 100
123+
X = np.array([np.random.rand(d) for i in range(n)])
124+
clustering = DBSCAN(metric=mixin.get_metric())
125+
clustering.fit(X)

0 commit comments

Comments
 (0)