diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pyCSalgos/OMP/omp_sk_bugfix.py	Fri Oct 21 13:53:49 2011 +0000
@@ -0,0 +1,564 @@
+"""Orthogonal matching pursuit algorithms
+"""
+
+# Author: Vlad Niculae
+#
+# License: BSD Style.
+
+from warnings import warn
+
+import numpy as np
+from scipy import linalg
+from scipy.linalg.lapack import get_lapack_funcs
+
+#from .base import LinearModel
+#from ..utils.arrayfuncs import solve_triangular
+from sklearn.linear_model.base import LinearModel
+from sklearn.utils.arrayfuncs import solve_triangular
+
+premature = """ Orthogonal matching pursuit ended prematurely due to linear
+dependence in the dictionary. The requested precision might not have been met.
+"""
+
+
+def _cholesky_omp(X, y, n_nonzero_coefs, tol=None, copy_X=True):
+    """Orthogonal Matching Pursuit step using the Cholesky decomposition.
+
+Parameters:
+-----------
+X: array, shape = (n_samples, n_features)
+Input dictionary. Columns are assumed to have unit norm.
+
+y: array, shape = (n_samples,)
+Input targets
+
+n_nonzero_coefs: int
+Targeted number of non-zero elements
+
+tol: float
+Targeted squared error, if not None overrides n_nonzero_coefs.
+
+copy_X: bool, optional
+Whether the design matrix X must be copied by the algorithm. A false
+value is only helpful if X is already Fortran-ordered, otherwise a
+copy is made anyway.
+
+Returns:
+--------
+gamma: array, shape = (n_nonzero_coefs,)
+Non-zero elements of the solution
+
+idx: array, shape = (n_nonzero_coefs,)
+Indices of the positions of the elements in gamma within the solution
+vector
+
+"""
+    if copy_X:
+        X = X.copy('F')
+    else: # even if we are allowed to overwrite, still copy it if bad order
+        X = np.asfortranarray(X)
+
+    min_float = np.finfo(X.dtype).eps
+    nrm2, swap = linalg.get_blas_funcs(('nrm2', 'swap'), (X,))
+    potrs, = get_lapack_funcs(('potrs',), (X,))
+
+    alpha = np.dot(X.T, y)
+    residual = y
+    n_active = 0
+    indices = range(X.shape[1]) # keeping track of swapping
+
+    #max_features = X.shape[1] if tol is not None else n_nonzero_coefs
+    # Nic: tol not None should not overide n_nonzero_coefs, but act together
+    max_features = n_nonzero_coefs
+    
+    L = np.empty((max_features, max_features), dtype=X.dtype)
+    L[0, 0] = 1.
+
+    while True:
+        lam = np.argmax(np.abs(np.dot(X.T, residual)))
+        if lam < n_active or alpha[lam] ** 2 < min_float:
+            # atom already selected or inner product too small
+            warn(premature)
+            break
+        if n_active > 0:
+            # Updates the Cholesky decomposition of X' X
+            L[n_active, :n_active] = np.dot(X[:, :n_active].T, X[:, lam])
+            solve_triangular(L[:n_active, :n_active], L[n_active, :n_active])
+            v = nrm2(L[n_active, :n_active]) ** 2
+            if 1 - v <= min_float: # selected atoms are dependent
+                warn(premature)
+                break
+            L[n_active, n_active] = np.sqrt(1 - v)
+        X.T[n_active], X.T[lam] = swap(X.T[n_active], X.T[lam])
+        alpha[n_active], alpha[lam] = alpha[lam], alpha[n_active]
+        indices[n_active], indices[lam] = indices[lam], indices[n_active]
+        n_active += 1
+        # solves LL'x = y as a composition of two triangular systems
+        gamma, _ = potrs(L[:n_active, :n_active], alpha[:n_active], lower=True,
+                         overwrite_b=False)
+
+        residual = y - np.dot(X[:, :n_active], gamma)
+        if tol is not None and nrm2(residual) ** 2 <= tol:
+            break
+        #elif n_active == max_features:
+        # Nic: tol not None should not overide n_nonzero_coefs, but act together
+        if n_active == max_features:
+            break
+
+    return gamma, indices[:n_active]
+
+
+def _gram_omp(Gram, Xy, n_nonzero_coefs, tol_0=None, tol=None,
+              copy_Gram=True, copy_Xy=True):
+    """Orthogonal Matching Pursuit step on a precomputed Gram matrix.
+
+This function uses the the Cholesky decomposition method.
+
+Parameters:
+-----------
+Gram: array, shape = (n_features, n_features)
+Gram matrix of the input data matrix
+
+Xy: array, shape = (n_features,)
+Input targets
+
+n_nonzero_coefs: int
+Targeted number of non-zero elements
+
+tol_0: float
+Squared norm of y, required if tol is not None.
+
+tol: float
+Targeted squared error, if not None overrides n_nonzero_coefs.
+
+copy_Gram: bool, optional
+Whether the gram matrix must be copied by the algorithm. A false
+value is only helpful if it is already Fortran-ordered, otherwise a
+copy is made anyway.
+
+copy_Xy: bool, optional
+Whether the covariance vector Xy must be copied by the algorithm.
+If False, it may be overwritten.
+
+Returns:
+--------
+gamma: array, shape = (n_nonzero_coefs,)
+Non-zero elements of the solution
+
+idx: array, shape = (n_nonzero_coefs,)
+Indices of the positions of the elements in gamma within the solution
+vector
+
+"""
+    Gram = Gram.copy('F') if copy_Gram else np.asfortranarray(Gram)
+
+    if copy_Xy:
+        Xy = Xy.copy()
+
+    min_float = np.finfo(Gram.dtype).eps
+    nrm2, swap = linalg.get_blas_funcs(('nrm2', 'swap'), (Gram,))
+    potrs, = get_lapack_funcs(('potrs',), (Gram,))
+
+    indices = range(len(Gram)) # keeping track of swapping
+    alpha = Xy
+    tol_curr = tol_0
+    delta = 0
+    n_active = 0
+
+    max_features = len(Gram) if tol is not None else n_nonzero_coefs
+    L = np.empty((max_features, max_features), dtype=Gram.dtype)
+    L[0, 0] = 1.
+
+    while True:
+        lam = np.argmax(np.abs(alpha))
+        if lam < n_active or alpha[lam] ** 2 < min_float:
+            # selected same atom twice, or inner product too small
+            warn(premature)
+            break
+        if n_active > 0:
+            L[n_active, :n_active] = Gram[lam, :n_active]
+            solve_triangular(L[:n_active, :n_active], L[n_active, :n_active])
+            v = nrm2(L[n_active, :n_active]) ** 2
+            if 1 - v <= min_float: # selected atoms are dependent
+                warn(premature)
+                break
+            L[n_active, n_active] = np.sqrt(1 - v)
+        Gram[n_active], Gram[lam] = swap(Gram[n_active], Gram[lam])
+        Gram.T[n_active], Gram.T[lam] = swap(Gram.T[n_active], Gram.T[lam])
+        indices[n_active], indices[lam] = indices[lam], indices[n_active]
+        Xy[n_active], Xy[lam] = Xy[lam], Xy[n_active]
+        n_active += 1
+        # solves LL'x = y as a composition of two triangular systems
+        gamma, _ = potrs(L[:n_active, :n_active], Xy[:n_active], lower=True,
+                         overwrite_b=False)
+
+        beta = np.dot(Gram[:, :n_active], gamma)
+        alpha = Xy - beta
+        if tol is not None:
+            tol_curr += delta
+            delta = np.inner(gamma, beta[:n_active])
+            tol_curr -= delta
+            if tol_curr <= tol:
+                break
+        elif n_active == max_features:
+            break
+
+    return gamma, indices[:n_active]
+
+
+def orthogonal_mp(X, y, n_nonzero_coefs=None, tol=None, precompute_gram=False,
+                  copy_X=True):
+    """Orthogonal Matching Pursuit (OMP)
+
+Solves n_targets Orthogonal Matching Pursuit problems.
+An instance of the problem has the form:
+
+When parametrized by the number of non-zero coefficients using
+`n_nonzero_coefs`:
+argmin ||y - X\gamma||^2 subject to ||\gamma||_0 <= n_{nonzero coefs}
+
+When parametrized by error using the parameter `tol`:
+argmin ||\gamma||_0 subject to ||y - X\gamma||^2 <= tol
+
+Parameters
+----------
+X: array, shape = (n_samples, n_features)
+Input data. Columns are assumed to have unit norm.
+
+y: array, shape = (n_samples,) or (n_samples, n_targets)
+Input targets
+
+n_nonzero_coefs: int
+Desired number of non-zero entries in the solution. If None (by
+default) this value is set to 10% of n_features.
+
+tol: float
+Maximum norm of the residual. If not None, overrides n_nonzero_coefs.
+
+Nic: tol does not oveeride n_nonzero_coefs, the wto conditions act jointly!
+
+precompute_gram: {True, False, 'auto'},
+Whether to perform precomputations. Improves performance when n_targets
+or n_samples is very large.
+
+copy_X: bool, optional
+Whether the design matrix X must be copied by the algorithm. A false
+value is only helpful if X is already Fortran-ordered, otherwise a
+copy is made anyway.
+
+Returns
+-------
+coef: array, shape = (n_features,) or (n_features, n_targets)
+Coefficients of the OMP solution
+
+See also
+--------
+OrthogonalMatchingPursuit
+orthogonal_mp_gram
+lars_path
+decomposition.sparse_encode
+decomposition.sparse_encode_parallel
+
+Notes
+-----
+Orthogonal matching pursuit was introduced in G. Mallat, Z. Zhang,
+Matching pursuits with time-frequency dictionaries, IEEE Transactions on
+Signal Processing, Vol. 41, No. 12. (December 1993), pp. 3397-3415.
+(http://blanche.polytechnique.fr/~mallat/papiers/MallatPursuit93.pdf)
+
+This implementation is based on Rubinstein, R., Zibulevsky, M. and Elad,
+M., Efficient Implementation of the K-SVD Algorithm using Batch Orthogonal
+Matching Pursuit Technical Report - CS Technion, April 2008.
+http://www.cs.technion.ac.il/~ronrubin/Publications/KSVX-OMP-v2.pdf
+
+"""
+    X, y = map(np.asanyarray, (X, y))
+    if y.ndim == 1:
+        y = y[:, np.newaxis]
+    if copy_X:
+        X = X.copy('F')
+        copy_X = False
+    else:
+        X = np.asfortranarray(X)
+    if y.shape[1] > 1: # subsequent targets will be affected
+        copy_X = True
+    if n_nonzero_coefs == None and tol == None:
+        n_nonzero_coefs = int(0.1 * X.shape[1])
+    if tol is not None and tol < 0:
+        raise ValueError("Epsilon cannot be negative")
+    if tol is None and n_nonzero_coefs <= 0:
+        raise ValueError("The number of atoms must be positive")
+    if tol is None and n_nonzero_coefs > X.shape[1]:
+        raise ValueError("The number of atoms cannot be more than the number \
+of features")
+    if precompute_gram == 'auto':
+        precompute_gram = X.shape[0] > X.shape[1]
+    if precompute_gram:
+        G = np.dot(X.T, X)
+        G = np.asfortranarray(G)
+        Xy = np.dot(X.T, y)
+        if tol is not None:
+            norms_squared = np.sum((y ** 2), axis=0)
+        else:
+            norms_squared = None
+        return orthogonal_mp_gram(G, Xy, n_nonzero_coefs, tol, norms_squared,
+                                  copy_Gram=copy_X, copy_Xy=False)
+
+    coef = np.zeros((X.shape[1], y.shape[1]))
+    for k in xrange(y.shape[1]):
+        x, idx = _cholesky_omp(X, y[:, k], n_nonzero_coefs, tol,
+                               copy_X=copy_X)
+        coef[idx, k] = x
+    return np.squeeze(coef)
+
+
+def orthogonal_mp_gram(Gram, Xy, n_nonzero_coefs=None, tol=None,
+                       norms_squared=None, copy_Gram=True,
+                       copy_Xy=True):
+    """Gram Orthogonal Matching Pursuit (OMP)
+
+Solves n_targets Orthogonal Matching Pursuit problems using only
+the Gram matrix X.T * X and the product X.T * y.
+
+Parameters
+----------
+Gram: array, shape = (n_features, n_features)
+Gram matrix of the input data: X.T * X
+
+Xy: array, shape = (n_features,) or (n_features, n_targets)
+Input targets multiplied by X: X.T * y
+
+n_nonzero_coefs: int
+Desired number of non-zero entries in the solution. If None (by
+default) this value is set to 10% of n_features.
+
+tol: float
+Maximum norm of the residual. If not None, overrides n_nonzero_coefs.
+
+norms_squared: array-like, shape = (n_targets,)
+Squared L2 norms of the lines of y. Required if tol is not None.
+
+copy_Gram: bool, optional
+Whether the gram matrix must be copied by the algorithm. A false
+value is only helpful if it is already Fortran-ordered, otherwise a
+copy is made anyway.
+
+copy_Xy: bool, optional
+Whether the covariance vector Xy must be copied by the algorithm.
+If False, it may be overwritten.
+
+Returns
+-------
+coef: array, shape = (n_features,) or (n_features, n_targets)
+Coefficients of the OMP solution
+
+See also
+--------
+OrthogonalMatchingPursuit
+orthogonal_mp
+lars_path
+decomposition.sparse_encode
+decomposition.sparse_encode_parallel
+
+Notes
+-----
+Orthogonal matching pursuit was introduced in G. Mallat, Z. Zhang,
+Matching pursuits with time-frequency dictionaries, IEEE Transactions on
+Signal Processing, Vol. 41, No. 12. (December 1993), pp. 3397-3415.
+(http://blanche.polytechnique.fr/~mallat/papiers/MallatPursuit93.pdf)
+
+This implementation is based on Rubinstein, R., Zibulevsky, M. and Elad,
+M., Efficient Implementation of the K-SVD Algorithm using Batch Orthogonal
+Matching Pursuit Technical Report - CS Technion, April 2008.
+http://www.cs.technion.ac.il/~ronrubin/Publications/KSVX-OMP-v2.pdf
+
+"""
+    Gram, Xy = map(np.asanyarray, (Gram, Xy))
+    if Xy.ndim == 1:
+        Xy = Xy[:, np.newaxis]
+        if tol is not None:
+            norms_squared = [norms_squared]
+
+    if n_nonzero_coefs == None and tol is None:
+        n_nonzero_coefs = int(0.1 * len(Gram))
+    if tol is not None and norms_squared == None:
+        raise ValueError('Gram OMP needs the precomputed norms in order \
+to evaluate the error sum of squares.')
+    if tol is not None and tol < 0:
+        raise ValueError("Epsilon cennot be negative")
+    if tol is None and n_nonzero_coefs <= 0:
+        raise ValueError("The number of atoms must be positive")
+    if tol is None and n_nonzero_coefs > len(Gram):
+        raise ValueError("The number of atoms cannot be more than the number \
+of features")
+    coef = np.zeros((len(Gram), Xy.shape[1]))
+    for k in range(Xy.shape[1]):
+        x, idx = _gram_omp(Gram, Xy[:, k], n_nonzero_coefs,
+                           norms_squared[k] if tol is not None else None, tol,
+                           copy_Gram=copy_Gram, copy_Xy=copy_Xy)
+        coef[idx, k] = x
+    return np.squeeze(coef)
+
+
+class OrthogonalMatchingPursuit(LinearModel):
+    """Orthogonal Mathching Pursuit model (OMP)
+
+Parameters
+----------
+n_nonzero_coefs: int, optional
+Desired number of non-zero entries in the solution. If None (by
+default) this value is set to 10% of n_features.
+
+tol: float, optional
+Maximum norm of the residual. If not None, overrides n_nonzero_coefs.
+
+fit_intercept: boolean, optional
+whether to calculate the intercept for this model. If set
+to false, no intercept will be used in calculations
+(e.g. data is expected to be already centered).
+
+normalize: boolean, optional
+If False, the regressors X are assumed to be already normalized.
+
+precompute_gram: {True, False, 'auto'},
+Whether to use a precomputed Gram and Xy matrix to speed up
+calculations. Improves performance when `n_targets` or `n_samples` is
+very large. Note that if you already have such matrices, you can pass
+them directly to the fit method.
+
+copy_X: bool, optional
+Whether the design matrix X must be copied by the algorithm. A false
+value is only helpful if X is already Fortran-ordered, otherwise a
+copy is made anyway.
+
+copy_Gram: bool, optional
+Whether the gram matrix must be copied by the algorithm. A false
+value is only helpful if X is already Fortran-ordered, otherwise a
+copy is made anyway.
+
+copy_Xy: bool, optional
+Whether the covariance vector Xy must be copied by the algorithm.
+If False, it may be overwritten.
+
+
+Attributes
+----------
+coef_: array, shape = (n_features,) or (n_features, n_targets)
+parameter vector (w in the fomulation formula)
+
+intercept_: float or array, shape =(n_targets,)
+independent term in decision function.
+
+Notes
+-----
+Orthogonal matching pursuit was introduced in G. Mallat, Z. Zhang,
+Matching pursuits with time-frequency dictionaries, IEEE Transactions on
+Signal Processing, Vol. 41, No. 12. (December 1993), pp. 3397-3415.
+(http://blanche.polytechnique.fr/~mallat/papiers/MallatPursuit93.pdf)
+
+This implementation is based on Rubinstein, R., Zibulevsky, M. and Elad,
+M., Efficient Implementation of the K-SVD Algorithm using Batch Orthogonal
+Matching Pursuit Technical Report - CS Technion, April 2008.
+http://www.cs.technion.ac.il/~ronrubin/Publications/KSVX-OMP-v2.pdf
+
+See also
+--------
+orthogonal_mp
+orthogonal_mp_gram
+lars_path
+Lars
+LassoLars
+decomposition.sparse_encode
+decomposition.sparse_encode_parallel
+
+"""
+    def __init__(self, copy_X=True, copy_Gram=True,
+            copy_Xy=True, n_nonzero_coefs=None, tol=None,
+            fit_intercept=True, normalize=True, precompute_gram=False):
+        self.n_nonzero_coefs = n_nonzero_coefs
+        self.tol = tol
+        self.fit_intercept = fit_intercept
+        self.normalize = normalize
+        self.precompute_gram = precompute_gram
+        self.copy_Gram = copy_Gram
+        self.copy_Xy = copy_Xy
+        self.copy_X = copy_X
+
+    def fit(self, X, y, Gram=None, Xy=None):
+        """Fit the model using X, y as training data.
+
+Parameters
+----------
+X: array-like, shape = (n_samples, n_features)
+Training data.
+
+y: array-like, shape = (n_samples,) or (n_samples, n_targets)
+Target values.
+
+Gram: array-like, shape = (n_features, n_features) (optional)
+Gram matrix of the input data: X.T * X
+
+Xy: array-like, shape = (n_features,) or (n_features, n_targets)
+(optional)
+Input targets multiplied by X: X.T * y
+
+
+Returns
+-------
+self: object
+returns an instance of self.
+"""
+        X = np.atleast_2d(X)
+        y = np.atleast_1d(y)
+        n_features = X.shape[1]
+
+        X, y, X_mean, y_mean, X_std = self._center_data(X, y,
+                                                        self.fit_intercept,
+                                                        self.normalize,
+                                                        self.copy_X)
+
+        if self.n_nonzero_coefs == None and self.tol is None:
+            self.n_nonzero_coefs = int(0.1 * n_features)
+
+        if Gram is not None:
+            Gram = np.atleast_2d(Gram)
+
+            if self.copy_Gram:
+                copy_Gram = False
+                Gram = Gram.copy('F')
+            else:
+                Gram = np.asfortranarray(Gram)
+
+            copy_Gram = self.copy_Gram
+            if y.shape[1] > 1: # subsequent targets will be affected
+                copy_Gram = True
+
+            if Xy is None:
+                Xy = np.dot(X.T, y)
+            else:
+                if self.copy_Xy:
+                    Xy = Xy.copy()
+                if self.normalize:
+                    if len(Xy.shape) == 1:
+                        Xy /= X_std
+                    else:
+                        Xy /= X_std[:, np.newaxis]
+
+            if self.normalize:
+                Gram /= X_std
+                Gram /= X_std[:, np.newaxis]
+
+            norms_sq = np.sum(y ** 2, axis=0) if self.tol is not None else None
+            self.coef_ = orthogonal_mp_gram(Gram, Xy, self.n_nonzero_coefs,
+                                            self.tol, norms_sq,
+                                            copy_Gram, True).T
+        else:
+            precompute_gram = self.precompute_gram
+            if precompute_gram == 'auto':
+                precompute_gram = X.shape[0] > X.shape[1]
+            self.coef_ = orthogonal_mp(X, y, self.n_nonzero_coefs, self.tol,
+                                       precompute_gram=self.precompute_gram,
+                                       copy_X=self.copy_X).T
+
+        self._set_intercept(X_mean, y_mean, X_std)
+        return self
\ No newline at end of file