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