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