Skip to content

Commit 544f6d2

Browse files
committed
Added MLKR algorithm
1 parent 9f602e6 commit 544f6d2

File tree

1 file changed

+124
-0
lines changed

1 file changed

+124
-0
lines changed

metric_learn/mlkr.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
"""
2+
Metric Learning for Kernel Regression (MLKR), Weinberger et al.,
3+
4+
MLKR is an algorithm for supervised metric learning, which learns a distance
5+
function by directly minimising the leave-one-out regression error. This
6+
algorithm can also be viewed as a supervised variation of PCA and can be used
7+
for dimensionality reduction and high dimensional data visualization.
8+
"""
9+
from __future__ import division
10+
import numpy as np
11+
from six.moves import xrange
12+
13+
from .base_metric import BaseMetricLearner
14+
15+
class MLKR(BaseMetricLearner):
16+
"""Metric Learning for Kernel Regression (MLKR)"""
17+
def __init__(self, A=None, epsilon=0.01):
18+
"""
19+
MLKR initialization
20+
21+
Parameters
22+
----------
23+
A: Initialization of matrix A. Defaults to the identity matrix.
24+
epsilon: Step size for gradient descent
25+
"""
26+
self.params = {
27+
"A": A,
28+
"epsilon": epsilon
29+
}
30+
31+
def _process_inputs(self, X, y):
32+
if X.ndim == 1:
33+
X = X[:, np.newaxis]
34+
if y.ndim == 1:
35+
y == y[:, np.newaxis]
36+
self.X = X
37+
n, d = X.shape
38+
assert y.shape[0] == n
39+
return y, n, d
40+
41+
def fit(self, X, y):
42+
"""
43+
Fit MLKR model
44+
45+
Parameters:
46+
----------
47+
X : (n x d) array of samples
48+
y : (n) data labels
49+
50+
Returns:
51+
-------
52+
self: Instance of self
53+
"""
54+
y, n, d = self._process_inputs(X, y)
55+
alpha = 0.0001 # Stopping criterion
56+
if self.params['A'] is None:
57+
A = np.identity(d) # Initialize A as eye matrix
58+
else:
59+
A = self.params['A']
60+
assert A.shape == (d, d)
61+
cost = np.Inf
62+
# Gradient descent procedure
63+
while cost > alpha:
64+
K = self._computeK(X, A, n)
65+
yhat = self._computeyhat(y, K)
66+
sum_i = 0
67+
for i in xrange(n):
68+
sum_j = 0
69+
for j in xrange(n):
70+
sum_j += (yhat[j] - y[j]) * K[i][j] * \
71+
(X[i, :] - X[j, :])[:, np.newaxis].dot \
72+
((X[i, :] - X[j, :])[:, np.newaxis].T)
73+
sum_i += (yhat[i] - y[i]) * sum_j
74+
gradient = 4 * A.dot(sum_i)
75+
A -= self.params['epsilon'] * gradient
76+
cost = np.sum(np.square(yhat - y))
77+
self._transformer = A
78+
return self
79+
80+
def _computeK(self, X, A, n):
81+
"""
82+
Internal helper function to compute K matrix.
83+
84+
Parameters:
85+
----------
86+
X: (n x d) array of samples
87+
A: (d x d) 'A' matrix
88+
n: number of rows in X
89+
90+
Returns:
91+
-------
92+
K: (n x n) K matrix where Kij = exp(-distance(x_i, x_j)) where
93+
distance is defined as squared L2 norm of (x_i - x_j)
94+
"""
95+
dist_mat = np.zeros(shape=(n, n))
96+
for i in xrange(n):
97+
for j in xrange(n):
98+
if i == j:
99+
dist = 0
100+
else:
101+
dist = np.sum(np.square(A.dot((X[i, :] - X[j, :]))))
102+
dist_mat[i, j] = dist
103+
return np.exp(-dist_mat)
104+
105+
def _computeyhat(self, y, K):
106+
"""
107+
Internal helper function to compute yhat matrix.
108+
109+
Parameters:
110+
----------
111+
y: (n) data labels
112+
K: (n x n) K matrix
113+
114+
Returns:
115+
-------
116+
yhat: (n x 1) yhat matrix
117+
"""
118+
numerator = K.dot(y)
119+
denominator = np.sum(K, 1)[:, np.newaxis]
120+
yhat = numerator / denominator
121+
return yhat
122+
123+
def transformer(self):
124+
return self._transformer

0 commit comments

Comments
 (0)