nikcleju@2
|
1 """Orthogonal matching pursuit algorithms
|
nikcleju@2
|
2 """
|
nikcleju@2
|
3
|
nikcleju@2
|
4 # Author: Vlad Niculae
|
nikcleju@2
|
5 #
|
nikcleju@2
|
6 # License: BSD Style.
|
nikcleju@2
|
7
|
nikcleju@2
|
8 from warnings import warn
|
nikcleju@2
|
9
|
nikcleju@2
|
10 import numpy as np
|
nikcleju@2
|
11 from scipy import linalg
|
nikcleju@2
|
12 from scipy.linalg.lapack import get_lapack_funcs
|
nikcleju@2
|
13
|
nikcleju@2
|
14 #from .base import LinearModel
|
nikcleju@2
|
15 #from ..utils.arrayfuncs import solve_triangular
|
nikcleju@2
|
16 from sklearn.linear_model.base import LinearModel
|
nikcleju@2
|
17 from sklearn.utils.arrayfuncs import solve_triangular
|
nikcleju@2
|
18
|
nikcleju@2
|
19 premature = """ Orthogonal matching pursuit ended prematurely due to linear
|
nikcleju@2
|
20 dependence in the dictionary. The requested precision might not have been met.
|
nikcleju@2
|
21 """
|
nikcleju@2
|
22
|
nikcleju@2
|
23
|
nikcleju@2
|
24 def _cholesky_omp(X, y, n_nonzero_coefs, tol=None, copy_X=True):
|
nikcleju@2
|
25 """Orthogonal Matching Pursuit step using the Cholesky decomposition.
|
nikcleju@2
|
26
|
nikcleju@2
|
27 Parameters:
|
nikcleju@2
|
28 -----------
|
nikcleju@2
|
29 X: array, shape = (n_samples, n_features)
|
nikcleju@2
|
30 Input dictionary. Columns are assumed to have unit norm.
|
nikcleju@2
|
31
|
nikcleju@2
|
32 y: array, shape = (n_samples,)
|
nikcleju@2
|
33 Input targets
|
nikcleju@2
|
34
|
nikcleju@2
|
35 n_nonzero_coefs: int
|
nikcleju@2
|
36 Targeted number of non-zero elements
|
nikcleju@2
|
37
|
nikcleju@2
|
38 tol: float
|
nikcleju@2
|
39 Targeted squared error, if not None overrides n_nonzero_coefs.
|
nikcleju@2
|
40
|
nikcleju@2
|
41 copy_X: bool, optional
|
nikcleju@2
|
42 Whether the design matrix X must be copied by the algorithm. A false
|
nikcleju@2
|
43 value is only helpful if X is already Fortran-ordered, otherwise a
|
nikcleju@2
|
44 copy is made anyway.
|
nikcleju@2
|
45
|
nikcleju@2
|
46 Returns:
|
nikcleju@2
|
47 --------
|
nikcleju@2
|
48 gamma: array, shape = (n_nonzero_coefs,)
|
nikcleju@2
|
49 Non-zero elements of the solution
|
nikcleju@2
|
50
|
nikcleju@2
|
51 idx: array, shape = (n_nonzero_coefs,)
|
nikcleju@2
|
52 Indices of the positions of the elements in gamma within the solution
|
nikcleju@2
|
53 vector
|
nikcleju@2
|
54
|
nikcleju@2
|
55 """
|
nikcleju@2
|
56 if copy_X:
|
nikcleju@2
|
57 X = X.copy('F')
|
nikcleju@2
|
58 else: # even if we are allowed to overwrite, still copy it if bad order
|
nikcleju@2
|
59 X = np.asfortranarray(X)
|
nikcleju@2
|
60
|
nikcleju@2
|
61 min_float = np.finfo(X.dtype).eps
|
nikcleju@2
|
62 nrm2, swap = linalg.get_blas_funcs(('nrm2', 'swap'), (X,))
|
nikcleju@2
|
63 potrs, = get_lapack_funcs(('potrs',), (X,))
|
nikcleju@2
|
64
|
nikcleju@2
|
65 alpha = np.dot(X.T, y)
|
nikcleju@2
|
66 residual = y
|
nikcleju@2
|
67 n_active = 0
|
nikcleju@2
|
68 indices = range(X.shape[1]) # keeping track of swapping
|
nikcleju@2
|
69
|
nikcleju@2
|
70 #max_features = X.shape[1] if tol is not None else n_nonzero_coefs
|
nikcleju@2
|
71 # Nic: tol not None should not overide n_nonzero_coefs, but act together
|
nikcleju@2
|
72 max_features = n_nonzero_coefs
|
nikcleju@2
|
73
|
nikcleju@2
|
74 L = np.empty((max_features, max_features), dtype=X.dtype)
|
nikcleju@2
|
75 L[0, 0] = 1.
|
nikcleju@2
|
76
|
nikcleju@2
|
77 while True:
|
nikcleju@2
|
78 lam = np.argmax(np.abs(np.dot(X.T, residual)))
|
nikcleju@2
|
79 if lam < n_active or alpha[lam] ** 2 < min_float:
|
nikcleju@2
|
80 # atom already selected or inner product too small
|
nikcleju@2
|
81 warn(premature)
|
nikcleju@2
|
82 break
|
nikcleju@2
|
83 if n_active > 0:
|
nikcleju@2
|
84 # Updates the Cholesky decomposition of X' X
|
nikcleju@2
|
85 L[n_active, :n_active] = np.dot(X[:, :n_active].T, X[:, lam])
|
nikcleju@2
|
86 solve_triangular(L[:n_active, :n_active], L[n_active, :n_active])
|
nikcleju@2
|
87 v = nrm2(L[n_active, :n_active]) ** 2
|
nikcleju@2
|
88 if 1 - v <= min_float: # selected atoms are dependent
|
nikcleju@2
|
89 warn(premature)
|
nikcleju@2
|
90 break
|
nikcleju@2
|
91 L[n_active, n_active] = np.sqrt(1 - v)
|
nikcleju@2
|
92 X.T[n_active], X.T[lam] = swap(X.T[n_active], X.T[lam])
|
nikcleju@2
|
93 alpha[n_active], alpha[lam] = alpha[lam], alpha[n_active]
|
nikcleju@2
|
94 indices[n_active], indices[lam] = indices[lam], indices[n_active]
|
nikcleju@2
|
95 n_active += 1
|
nikcleju@2
|
96 # solves LL'x = y as a composition of two triangular systems
|
nikcleju@2
|
97 gamma, _ = potrs(L[:n_active, :n_active], alpha[:n_active], lower=True,
|
nikcleju@2
|
98 overwrite_b=False)
|
nikcleju@2
|
99
|
nikcleju@2
|
100 residual = y - np.dot(X[:, :n_active], gamma)
|
nikcleju@2
|
101 if tol is not None and nrm2(residual) ** 2 <= tol:
|
nikcleju@2
|
102 break
|
nikcleju@2
|
103 #elif n_active == max_features:
|
nikcleju@2
|
104 # Nic: tol not None should not overide n_nonzero_coefs, but act together
|
nikcleju@2
|
105 if n_active == max_features:
|
nikcleju@2
|
106 break
|
nikcleju@2
|
107
|
nikcleju@2
|
108 return gamma, indices[:n_active]
|
nikcleju@2
|
109
|
nikcleju@2
|
110
|
nikcleju@2
|
111 def _gram_omp(Gram, Xy, n_nonzero_coefs, tol_0=None, tol=None,
|
nikcleju@2
|
112 copy_Gram=True, copy_Xy=True):
|
nikcleju@2
|
113 """Orthogonal Matching Pursuit step on a precomputed Gram matrix.
|
nikcleju@2
|
114
|
nikcleju@2
|
115 This function uses the the Cholesky decomposition method.
|
nikcleju@2
|
116
|
nikcleju@2
|
117 Parameters:
|
nikcleju@2
|
118 -----------
|
nikcleju@2
|
119 Gram: array, shape = (n_features, n_features)
|
nikcleju@2
|
120 Gram matrix of the input data matrix
|
nikcleju@2
|
121
|
nikcleju@2
|
122 Xy: array, shape = (n_features,)
|
nikcleju@2
|
123 Input targets
|
nikcleju@2
|
124
|
nikcleju@2
|
125 n_nonzero_coefs: int
|
nikcleju@2
|
126 Targeted number of non-zero elements
|
nikcleju@2
|
127
|
nikcleju@2
|
128 tol_0: float
|
nikcleju@2
|
129 Squared norm of y, required if tol is not None.
|
nikcleju@2
|
130
|
nikcleju@2
|
131 tol: float
|
nikcleju@2
|
132 Targeted squared error, if not None overrides n_nonzero_coefs.
|
nikcleju@2
|
133
|
nikcleju@2
|
134 copy_Gram: bool, optional
|
nikcleju@2
|
135 Whether the gram matrix must be copied by the algorithm. A false
|
nikcleju@2
|
136 value is only helpful if it is already Fortran-ordered, otherwise a
|
nikcleju@2
|
137 copy is made anyway.
|
nikcleju@2
|
138
|
nikcleju@2
|
139 copy_Xy: bool, optional
|
nikcleju@2
|
140 Whether the covariance vector Xy must be copied by the algorithm.
|
nikcleju@2
|
141 If False, it may be overwritten.
|
nikcleju@2
|
142
|
nikcleju@2
|
143 Returns:
|
nikcleju@2
|
144 --------
|
nikcleju@2
|
145 gamma: array, shape = (n_nonzero_coefs,)
|
nikcleju@2
|
146 Non-zero elements of the solution
|
nikcleju@2
|
147
|
nikcleju@2
|
148 idx: array, shape = (n_nonzero_coefs,)
|
nikcleju@2
|
149 Indices of the positions of the elements in gamma within the solution
|
nikcleju@2
|
150 vector
|
nikcleju@2
|
151
|
nikcleju@2
|
152 """
|
nikcleju@2
|
153 Gram = Gram.copy('F') if copy_Gram else np.asfortranarray(Gram)
|
nikcleju@2
|
154
|
nikcleju@2
|
155 if copy_Xy:
|
nikcleju@2
|
156 Xy = Xy.copy()
|
nikcleju@2
|
157
|
nikcleju@2
|
158 min_float = np.finfo(Gram.dtype).eps
|
nikcleju@2
|
159 nrm2, swap = linalg.get_blas_funcs(('nrm2', 'swap'), (Gram,))
|
nikcleju@2
|
160 potrs, = get_lapack_funcs(('potrs',), (Gram,))
|
nikcleju@2
|
161
|
nikcleju@2
|
162 indices = range(len(Gram)) # keeping track of swapping
|
nikcleju@2
|
163 alpha = Xy
|
nikcleju@2
|
164 tol_curr = tol_0
|
nikcleju@2
|
165 delta = 0
|
nikcleju@2
|
166 n_active = 0
|
nikcleju@2
|
167
|
nikcleju@2
|
168 max_features = len(Gram) if tol is not None else n_nonzero_coefs
|
nikcleju@2
|
169 L = np.empty((max_features, max_features), dtype=Gram.dtype)
|
nikcleju@2
|
170 L[0, 0] = 1.
|
nikcleju@2
|
171
|
nikcleju@2
|
172 while True:
|
nikcleju@2
|
173 lam = np.argmax(np.abs(alpha))
|
nikcleju@2
|
174 if lam < n_active or alpha[lam] ** 2 < min_float:
|
nikcleju@2
|
175 # selected same atom twice, or inner product too small
|
nikcleju@2
|
176 warn(premature)
|
nikcleju@2
|
177 break
|
nikcleju@2
|
178 if n_active > 0:
|
nikcleju@2
|
179 L[n_active, :n_active] = Gram[lam, :n_active]
|
nikcleju@2
|
180 solve_triangular(L[:n_active, :n_active], L[n_active, :n_active])
|
nikcleju@2
|
181 v = nrm2(L[n_active, :n_active]) ** 2
|
nikcleju@2
|
182 if 1 - v <= min_float: # selected atoms are dependent
|
nikcleju@2
|
183 warn(premature)
|
nikcleju@2
|
184 break
|
nikcleju@2
|
185 L[n_active, n_active] = np.sqrt(1 - v)
|
nikcleju@2
|
186 Gram[n_active], Gram[lam] = swap(Gram[n_active], Gram[lam])
|
nikcleju@2
|
187 Gram.T[n_active], Gram.T[lam] = swap(Gram.T[n_active], Gram.T[lam])
|
nikcleju@2
|
188 indices[n_active], indices[lam] = indices[lam], indices[n_active]
|
nikcleju@2
|
189 Xy[n_active], Xy[lam] = Xy[lam], Xy[n_active]
|
nikcleju@2
|
190 n_active += 1
|
nikcleju@2
|
191 # solves LL'x = y as a composition of two triangular systems
|
nikcleju@2
|
192 gamma, _ = potrs(L[:n_active, :n_active], Xy[:n_active], lower=True,
|
nikcleju@2
|
193 overwrite_b=False)
|
nikcleju@2
|
194
|
nikcleju@2
|
195 beta = np.dot(Gram[:, :n_active], gamma)
|
nikcleju@2
|
196 alpha = Xy - beta
|
nikcleju@2
|
197 if tol is not None:
|
nikcleju@2
|
198 tol_curr += delta
|
nikcleju@2
|
199 delta = np.inner(gamma, beta[:n_active])
|
nikcleju@2
|
200 tol_curr -= delta
|
nikcleju@2
|
201 if tol_curr <= tol:
|
nikcleju@2
|
202 break
|
nikcleju@2
|
203 elif n_active == max_features:
|
nikcleju@2
|
204 break
|
nikcleju@2
|
205
|
nikcleju@2
|
206 return gamma, indices[:n_active]
|
nikcleju@2
|
207
|
nikcleju@2
|
208
|
nikcleju@2
|
209 def orthogonal_mp(X, y, n_nonzero_coefs=None, tol=None, precompute_gram=False,
|
nikcleju@2
|
210 copy_X=True):
|
nikcleju@2
|
211 """Orthogonal Matching Pursuit (OMP)
|
nikcleju@2
|
212
|
nikcleju@2
|
213 Solves n_targets Orthogonal Matching Pursuit problems.
|
nikcleju@2
|
214 An instance of the problem has the form:
|
nikcleju@2
|
215
|
nikcleju@2
|
216 When parametrized by the number of non-zero coefficients using
|
nikcleju@2
|
217 `n_nonzero_coefs`:
|
nikcleju@2
|
218 argmin ||y - X\gamma||^2 subject to ||\gamma||_0 <= n_{nonzero coefs}
|
nikcleju@2
|
219
|
nikcleju@2
|
220 When parametrized by error using the parameter `tol`:
|
nikcleju@2
|
221 argmin ||\gamma||_0 subject to ||y - X\gamma||^2 <= tol
|
nikcleju@2
|
222
|
nikcleju@2
|
223 Parameters
|
nikcleju@2
|
224 ----------
|
nikcleju@2
|
225 X: array, shape = (n_samples, n_features)
|
nikcleju@2
|
226 Input data. Columns are assumed to have unit norm.
|
nikcleju@2
|
227
|
nikcleju@2
|
228 y: array, shape = (n_samples,) or (n_samples, n_targets)
|
nikcleju@2
|
229 Input targets
|
nikcleju@2
|
230
|
nikcleju@2
|
231 n_nonzero_coefs: int
|
nikcleju@2
|
232 Desired number of non-zero entries in the solution. If None (by
|
nikcleju@2
|
233 default) this value is set to 10% of n_features.
|
nikcleju@2
|
234
|
nikcleju@2
|
235 tol: float
|
nikcleju@2
|
236 Maximum norm of the residual. If not None, overrides n_nonzero_coefs.
|
nikcleju@2
|
237
|
nikcleju@2
|
238 Nic: tol does not oveeride n_nonzero_coefs, the wto conditions act jointly!
|
nikcleju@2
|
239
|
nikcleju@2
|
240 precompute_gram: {True, False, 'auto'},
|
nikcleju@2
|
241 Whether to perform precomputations. Improves performance when n_targets
|
nikcleju@2
|
242 or n_samples is very large.
|
nikcleju@2
|
243
|
nikcleju@2
|
244 copy_X: bool, optional
|
nikcleju@2
|
245 Whether the design matrix X must be copied by the algorithm. A false
|
nikcleju@2
|
246 value is only helpful if X is already Fortran-ordered, otherwise a
|
nikcleju@2
|
247 copy is made anyway.
|
nikcleju@2
|
248
|
nikcleju@2
|
249 Returns
|
nikcleju@2
|
250 -------
|
nikcleju@2
|
251 coef: array, shape = (n_features,) or (n_features, n_targets)
|
nikcleju@2
|
252 Coefficients of the OMP solution
|
nikcleju@2
|
253
|
nikcleju@2
|
254 See also
|
nikcleju@2
|
255 --------
|
nikcleju@2
|
256 OrthogonalMatchingPursuit
|
nikcleju@2
|
257 orthogonal_mp_gram
|
nikcleju@2
|
258 lars_path
|
nikcleju@2
|
259 decomposition.sparse_encode
|
nikcleju@2
|
260 decomposition.sparse_encode_parallel
|
nikcleju@2
|
261
|
nikcleju@2
|
262 Notes
|
nikcleju@2
|
263 -----
|
nikcleju@2
|
264 Orthogonal matching pursuit was introduced in G. Mallat, Z. Zhang,
|
nikcleju@2
|
265 Matching pursuits with time-frequency dictionaries, IEEE Transactions on
|
nikcleju@2
|
266 Signal Processing, Vol. 41, No. 12. (December 1993), pp. 3397-3415.
|
nikcleju@2
|
267 (http://blanche.polytechnique.fr/~mallat/papiers/MallatPursuit93.pdf)
|
nikcleju@2
|
268
|
nikcleju@2
|
269 This implementation is based on Rubinstein, R., Zibulevsky, M. and Elad,
|
nikcleju@2
|
270 M., Efficient Implementation of the K-SVD Algorithm using Batch Orthogonal
|
nikcleju@2
|
271 Matching Pursuit Technical Report - CS Technion, April 2008.
|
nikcleju@2
|
272 http://www.cs.technion.ac.il/~ronrubin/Publications/KSVX-OMP-v2.pdf
|
nikcleju@2
|
273
|
nikcleju@2
|
274 """
|
nikcleju@2
|
275 X, y = map(np.asanyarray, (X, y))
|
nikcleju@2
|
276 if y.ndim == 1:
|
nikcleju@2
|
277 y = y[:, np.newaxis]
|
nikcleju@2
|
278 if copy_X:
|
nikcleju@2
|
279 X = X.copy('F')
|
nikcleju@2
|
280 copy_X = False
|
nikcleju@2
|
281 else:
|
nikcleju@2
|
282 X = np.asfortranarray(X)
|
nikcleju@2
|
283 if y.shape[1] > 1: # subsequent targets will be affected
|
nikcleju@2
|
284 copy_X = True
|
nikcleju@2
|
285 if n_nonzero_coefs == None and tol == None:
|
nikcleju@2
|
286 n_nonzero_coefs = int(0.1 * X.shape[1])
|
nikcleju@2
|
287 if tol is not None and tol < 0:
|
nikcleju@2
|
288 raise ValueError("Epsilon cannot be negative")
|
nikcleju@2
|
289 if tol is None and n_nonzero_coefs <= 0:
|
nikcleju@2
|
290 raise ValueError("The number of atoms must be positive")
|
nikcleju@2
|
291 if tol is None and n_nonzero_coefs > X.shape[1]:
|
nikcleju@2
|
292 raise ValueError("The number of atoms cannot be more than the number \
|
nikcleju@2
|
293 of features")
|
nikcleju@2
|
294 if precompute_gram == 'auto':
|
nikcleju@2
|
295 precompute_gram = X.shape[0] > X.shape[1]
|
nikcleju@2
|
296 if precompute_gram:
|
nikcleju@2
|
297 G = np.dot(X.T, X)
|
nikcleju@2
|
298 G = np.asfortranarray(G)
|
nikcleju@2
|
299 Xy = np.dot(X.T, y)
|
nikcleju@2
|
300 if tol is not None:
|
nikcleju@2
|
301 norms_squared = np.sum((y ** 2), axis=0)
|
nikcleju@2
|
302 else:
|
nikcleju@2
|
303 norms_squared = None
|
nikcleju@2
|
304 return orthogonal_mp_gram(G, Xy, n_nonzero_coefs, tol, norms_squared,
|
nikcleju@2
|
305 copy_Gram=copy_X, copy_Xy=False)
|
nikcleju@2
|
306
|
nikcleju@2
|
307 coef = np.zeros((X.shape[1], y.shape[1]))
|
nikcleju@2
|
308 for k in xrange(y.shape[1]):
|
nikcleju@2
|
309 x, idx = _cholesky_omp(X, y[:, k], n_nonzero_coefs, tol,
|
nikcleju@2
|
310 copy_X=copy_X)
|
nikcleju@2
|
311 coef[idx, k] = x
|
nikcleju@2
|
312 return np.squeeze(coef)
|
nikcleju@2
|
313
|
nikcleju@2
|
314
|
nikcleju@2
|
315 def orthogonal_mp_gram(Gram, Xy, n_nonzero_coefs=None, tol=None,
|
nikcleju@2
|
316 norms_squared=None, copy_Gram=True,
|
nikcleju@2
|
317 copy_Xy=True):
|
nikcleju@2
|
318 """Gram Orthogonal Matching Pursuit (OMP)
|
nikcleju@2
|
319
|
nikcleju@2
|
320 Solves n_targets Orthogonal Matching Pursuit problems using only
|
nikcleju@2
|
321 the Gram matrix X.T * X and the product X.T * y.
|
nikcleju@2
|
322
|
nikcleju@2
|
323 Parameters
|
nikcleju@2
|
324 ----------
|
nikcleju@2
|
325 Gram: array, shape = (n_features, n_features)
|
nikcleju@2
|
326 Gram matrix of the input data: X.T * X
|
nikcleju@2
|
327
|
nikcleju@2
|
328 Xy: array, shape = (n_features,) or (n_features, n_targets)
|
nikcleju@2
|
329 Input targets multiplied by X: X.T * y
|
nikcleju@2
|
330
|
nikcleju@2
|
331 n_nonzero_coefs: int
|
nikcleju@2
|
332 Desired number of non-zero entries in the solution. If None (by
|
nikcleju@2
|
333 default) this value is set to 10% of n_features.
|
nikcleju@2
|
334
|
nikcleju@2
|
335 tol: float
|
nikcleju@2
|
336 Maximum norm of the residual. If not None, overrides n_nonzero_coefs.
|
nikcleju@2
|
337
|
nikcleju@2
|
338 norms_squared: array-like, shape = (n_targets,)
|
nikcleju@2
|
339 Squared L2 norms of the lines of y. Required if tol is not None.
|
nikcleju@2
|
340
|
nikcleju@2
|
341 copy_Gram: bool, optional
|
nikcleju@2
|
342 Whether the gram matrix must be copied by the algorithm. A false
|
nikcleju@2
|
343 value is only helpful if it is already Fortran-ordered, otherwise a
|
nikcleju@2
|
344 copy is made anyway.
|
nikcleju@2
|
345
|
nikcleju@2
|
346 copy_Xy: bool, optional
|
nikcleju@2
|
347 Whether the covariance vector Xy must be copied by the algorithm.
|
nikcleju@2
|
348 If False, it may be overwritten.
|
nikcleju@2
|
349
|
nikcleju@2
|
350 Returns
|
nikcleju@2
|
351 -------
|
nikcleju@2
|
352 coef: array, shape = (n_features,) or (n_features, n_targets)
|
nikcleju@2
|
353 Coefficients of the OMP solution
|
nikcleju@2
|
354
|
nikcleju@2
|
355 See also
|
nikcleju@2
|
356 --------
|
nikcleju@2
|
357 OrthogonalMatchingPursuit
|
nikcleju@2
|
358 orthogonal_mp
|
nikcleju@2
|
359 lars_path
|
nikcleju@2
|
360 decomposition.sparse_encode
|
nikcleju@2
|
361 decomposition.sparse_encode_parallel
|
nikcleju@2
|
362
|
nikcleju@2
|
363 Notes
|
nikcleju@2
|
364 -----
|
nikcleju@2
|
365 Orthogonal matching pursuit was introduced in G. Mallat, Z. Zhang,
|
nikcleju@2
|
366 Matching pursuits with time-frequency dictionaries, IEEE Transactions on
|
nikcleju@2
|
367 Signal Processing, Vol. 41, No. 12. (December 1993), pp. 3397-3415.
|
nikcleju@2
|
368 (http://blanche.polytechnique.fr/~mallat/papiers/MallatPursuit93.pdf)
|
nikcleju@2
|
369
|
nikcleju@2
|
370 This implementation is based on Rubinstein, R., Zibulevsky, M. and Elad,
|
nikcleju@2
|
371 M., Efficient Implementation of the K-SVD Algorithm using Batch Orthogonal
|
nikcleju@2
|
372 Matching Pursuit Technical Report - CS Technion, April 2008.
|
nikcleju@2
|
373 http://www.cs.technion.ac.il/~ronrubin/Publications/KSVX-OMP-v2.pdf
|
nikcleju@2
|
374
|
nikcleju@2
|
375 """
|
nikcleju@2
|
376 Gram, Xy = map(np.asanyarray, (Gram, Xy))
|
nikcleju@2
|
377 if Xy.ndim == 1:
|
nikcleju@2
|
378 Xy = Xy[:, np.newaxis]
|
nikcleju@2
|
379 if tol is not None:
|
nikcleju@2
|
380 norms_squared = [norms_squared]
|
nikcleju@2
|
381
|
nikcleju@2
|
382 if n_nonzero_coefs == None and tol is None:
|
nikcleju@2
|
383 n_nonzero_coefs = int(0.1 * len(Gram))
|
nikcleju@2
|
384 if tol is not None and norms_squared == None:
|
nikcleju@2
|
385 raise ValueError('Gram OMP needs the precomputed norms in order \
|
nikcleju@2
|
386 to evaluate the error sum of squares.')
|
nikcleju@2
|
387 if tol is not None and tol < 0:
|
nikcleju@2
|
388 raise ValueError("Epsilon cennot be negative")
|
nikcleju@2
|
389 if tol is None and n_nonzero_coefs <= 0:
|
nikcleju@2
|
390 raise ValueError("The number of atoms must be positive")
|
nikcleju@2
|
391 if tol is None and n_nonzero_coefs > len(Gram):
|
nikcleju@2
|
392 raise ValueError("The number of atoms cannot be more than the number \
|
nikcleju@2
|
393 of features")
|
nikcleju@2
|
394 coef = np.zeros((len(Gram), Xy.shape[1]))
|
nikcleju@2
|
395 for k in range(Xy.shape[1]):
|
nikcleju@2
|
396 x, idx = _gram_omp(Gram, Xy[:, k], n_nonzero_coefs,
|
nikcleju@2
|
397 norms_squared[k] if tol is not None else None, tol,
|
nikcleju@2
|
398 copy_Gram=copy_Gram, copy_Xy=copy_Xy)
|
nikcleju@2
|
399 coef[idx, k] = x
|
nikcleju@2
|
400 return np.squeeze(coef)
|
nikcleju@2
|
401
|
nikcleju@2
|
402
|
nikcleju@2
|
403 class OrthogonalMatchingPursuit(LinearModel):
|
nikcleju@2
|
404 """Orthogonal Mathching Pursuit model (OMP)
|
nikcleju@2
|
405
|
nikcleju@2
|
406 Parameters
|
nikcleju@2
|
407 ----------
|
nikcleju@2
|
408 n_nonzero_coefs: int, optional
|
nikcleju@2
|
409 Desired number of non-zero entries in the solution. If None (by
|
nikcleju@2
|
410 default) this value is set to 10% of n_features.
|
nikcleju@2
|
411
|
nikcleju@2
|
412 tol: float, optional
|
nikcleju@2
|
413 Maximum norm of the residual. If not None, overrides n_nonzero_coefs.
|
nikcleju@2
|
414
|
nikcleju@2
|
415 fit_intercept: boolean, optional
|
nikcleju@2
|
416 whether to calculate the intercept for this model. If set
|
nikcleju@2
|
417 to false, no intercept will be used in calculations
|
nikcleju@2
|
418 (e.g. data is expected to be already centered).
|
nikcleju@2
|
419
|
nikcleju@2
|
420 normalize: boolean, optional
|
nikcleju@2
|
421 If False, the regressors X are assumed to be already normalized.
|
nikcleju@2
|
422
|
nikcleju@2
|
423 precompute_gram: {True, False, 'auto'},
|
nikcleju@2
|
424 Whether to use a precomputed Gram and Xy matrix to speed up
|
nikcleju@2
|
425 calculations. Improves performance when `n_targets` or `n_samples` is
|
nikcleju@2
|
426 very large. Note that if you already have such matrices, you can pass
|
nikcleju@2
|
427 them directly to the fit method.
|
nikcleju@2
|
428
|
nikcleju@2
|
429 copy_X: bool, optional
|
nikcleju@2
|
430 Whether the design matrix X must be copied by the algorithm. A false
|
nikcleju@2
|
431 value is only helpful if X is already Fortran-ordered, otherwise a
|
nikcleju@2
|
432 copy is made anyway.
|
nikcleju@2
|
433
|
nikcleju@2
|
434 copy_Gram: bool, optional
|
nikcleju@2
|
435 Whether the gram matrix must be copied by the algorithm. A false
|
nikcleju@2
|
436 value is only helpful if X is already Fortran-ordered, otherwise a
|
nikcleju@2
|
437 copy is made anyway.
|
nikcleju@2
|
438
|
nikcleju@2
|
439 copy_Xy: bool, optional
|
nikcleju@2
|
440 Whether the covariance vector Xy must be copied by the algorithm.
|
nikcleju@2
|
441 If False, it may be overwritten.
|
nikcleju@2
|
442
|
nikcleju@2
|
443
|
nikcleju@2
|
444 Attributes
|
nikcleju@2
|
445 ----------
|
nikcleju@2
|
446 coef_: array, shape = (n_features,) or (n_features, n_targets)
|
nikcleju@2
|
447 parameter vector (w in the fomulation formula)
|
nikcleju@2
|
448
|
nikcleju@2
|
449 intercept_: float or array, shape =(n_targets,)
|
nikcleju@2
|
450 independent term in decision function.
|
nikcleju@2
|
451
|
nikcleju@2
|
452 Notes
|
nikcleju@2
|
453 -----
|
nikcleju@2
|
454 Orthogonal matching pursuit was introduced in G. Mallat, Z. Zhang,
|
nikcleju@2
|
455 Matching pursuits with time-frequency dictionaries, IEEE Transactions on
|
nikcleju@2
|
456 Signal Processing, Vol. 41, No. 12. (December 1993), pp. 3397-3415.
|
nikcleju@2
|
457 (http://blanche.polytechnique.fr/~mallat/papiers/MallatPursuit93.pdf)
|
nikcleju@2
|
458
|
nikcleju@2
|
459 This implementation is based on Rubinstein, R., Zibulevsky, M. and Elad,
|
nikcleju@2
|
460 M., Efficient Implementation of the K-SVD Algorithm using Batch Orthogonal
|
nikcleju@2
|
461 Matching Pursuit Technical Report - CS Technion, April 2008.
|
nikcleju@2
|
462 http://www.cs.technion.ac.il/~ronrubin/Publications/KSVX-OMP-v2.pdf
|
nikcleju@2
|
463
|
nikcleju@2
|
464 See also
|
nikcleju@2
|
465 --------
|
nikcleju@2
|
466 orthogonal_mp
|
nikcleju@2
|
467 orthogonal_mp_gram
|
nikcleju@2
|
468 lars_path
|
nikcleju@2
|
469 Lars
|
nikcleju@2
|
470 LassoLars
|
nikcleju@2
|
471 decomposition.sparse_encode
|
nikcleju@2
|
472 decomposition.sparse_encode_parallel
|
nikcleju@2
|
473
|
nikcleju@2
|
474 """
|
nikcleju@2
|
475 def __init__(self, copy_X=True, copy_Gram=True,
|
nikcleju@2
|
476 copy_Xy=True, n_nonzero_coefs=None, tol=None,
|
nikcleju@2
|
477 fit_intercept=True, normalize=True, precompute_gram=False):
|
nikcleju@2
|
478 self.n_nonzero_coefs = n_nonzero_coefs
|
nikcleju@2
|
479 self.tol = tol
|
nikcleju@2
|
480 self.fit_intercept = fit_intercept
|
nikcleju@2
|
481 self.normalize = normalize
|
nikcleju@2
|
482 self.precompute_gram = precompute_gram
|
nikcleju@2
|
483 self.copy_Gram = copy_Gram
|
nikcleju@2
|
484 self.copy_Xy = copy_Xy
|
nikcleju@2
|
485 self.copy_X = copy_X
|
nikcleju@2
|
486
|
nikcleju@2
|
487 def fit(self, X, y, Gram=None, Xy=None):
|
nikcleju@2
|
488 """Fit the model using X, y as training data.
|
nikcleju@2
|
489
|
nikcleju@2
|
490 Parameters
|
nikcleju@2
|
491 ----------
|
nikcleju@2
|
492 X: array-like, shape = (n_samples, n_features)
|
nikcleju@2
|
493 Training data.
|
nikcleju@2
|
494
|
nikcleju@2
|
495 y: array-like, shape = (n_samples,) or (n_samples, n_targets)
|
nikcleju@2
|
496 Target values.
|
nikcleju@2
|
497
|
nikcleju@2
|
498 Gram: array-like, shape = (n_features, n_features) (optional)
|
nikcleju@2
|
499 Gram matrix of the input data: X.T * X
|
nikcleju@2
|
500
|
nikcleju@2
|
501 Xy: array-like, shape = (n_features,) or (n_features, n_targets)
|
nikcleju@2
|
502 (optional)
|
nikcleju@2
|
503 Input targets multiplied by X: X.T * y
|
nikcleju@2
|
504
|
nikcleju@2
|
505
|
nikcleju@2
|
506 Returns
|
nikcleju@2
|
507 -------
|
nikcleju@2
|
508 self: object
|
nikcleju@2
|
509 returns an instance of self.
|
nikcleju@2
|
510 """
|
nikcleju@2
|
511 X = np.atleast_2d(X)
|
nikcleju@2
|
512 y = np.atleast_1d(y)
|
nikcleju@2
|
513 n_features = X.shape[1]
|
nikcleju@2
|
514
|
nikcleju@2
|
515 X, y, X_mean, y_mean, X_std = self._center_data(X, y,
|
nikcleju@2
|
516 self.fit_intercept,
|
nikcleju@2
|
517 self.normalize,
|
nikcleju@2
|
518 self.copy_X)
|
nikcleju@2
|
519
|
nikcleju@2
|
520 if self.n_nonzero_coefs == None and self.tol is None:
|
nikcleju@2
|
521 self.n_nonzero_coefs = int(0.1 * n_features)
|
nikcleju@2
|
522
|
nikcleju@2
|
523 if Gram is not None:
|
nikcleju@2
|
524 Gram = np.atleast_2d(Gram)
|
nikcleju@2
|
525
|
nikcleju@2
|
526 if self.copy_Gram:
|
nikcleju@2
|
527 copy_Gram = False
|
nikcleju@2
|
528 Gram = Gram.copy('F')
|
nikcleju@2
|
529 else:
|
nikcleju@2
|
530 Gram = np.asfortranarray(Gram)
|
nikcleju@2
|
531
|
nikcleju@2
|
532 copy_Gram = self.copy_Gram
|
nikcleju@2
|
533 if y.shape[1] > 1: # subsequent targets will be affected
|
nikcleju@2
|
534 copy_Gram = True
|
nikcleju@2
|
535
|
nikcleju@2
|
536 if Xy is None:
|
nikcleju@2
|
537 Xy = np.dot(X.T, y)
|
nikcleju@2
|
538 else:
|
nikcleju@2
|
539 if self.copy_Xy:
|
nikcleju@2
|
540 Xy = Xy.copy()
|
nikcleju@2
|
541 if self.normalize:
|
nikcleju@2
|
542 if len(Xy.shape) == 1:
|
nikcleju@2
|
543 Xy /= X_std
|
nikcleju@2
|
544 else:
|
nikcleju@2
|
545 Xy /= X_std[:, np.newaxis]
|
nikcleju@2
|
546
|
nikcleju@2
|
547 if self.normalize:
|
nikcleju@2
|
548 Gram /= X_std
|
nikcleju@2
|
549 Gram /= X_std[:, np.newaxis]
|
nikcleju@2
|
550
|
nikcleju@2
|
551 norms_sq = np.sum(y ** 2, axis=0) if self.tol is not None else None
|
nikcleju@2
|
552 self.coef_ = orthogonal_mp_gram(Gram, Xy, self.n_nonzero_coefs,
|
nikcleju@2
|
553 self.tol, norms_sq,
|
nikcleju@2
|
554 copy_Gram, True).T
|
nikcleju@2
|
555 else:
|
nikcleju@2
|
556 precompute_gram = self.precompute_gram
|
nikcleju@2
|
557 if precompute_gram == 'auto':
|
nikcleju@2
|
558 precompute_gram = X.shape[0] > X.shape[1]
|
nikcleju@2
|
559 self.coef_ = orthogonal_mp(X, y, self.n_nonzero_coefs, self.tol,
|
nikcleju@2
|
560 precompute_gram=self.precompute_gram,
|
nikcleju@2
|
561 copy_X=self.copy_X).T
|
nikcleju@2
|
562
|
nikcleju@2
|
563 self._set_intercept(X_mean, y_mean, X_std)
|
nikcleju@2
|
564 return self |