def OMP(Op, data, niter_outer=10, niter_inner=40, sigma=1e-4, normalizecols=False, show=False): r"""Orthogonal Matching Pursuit (OMP). Solve an optimization problem with :math:`L0` regularization function given the operator ``Op`` and data ``y``. The operator can be real or complex, and should ideally be either square :math:`N=M` or underdetermined :math:`N<M`. Parameters ---------- Op : :obj:`pylops.LinearOperator` Operator to invert data : :obj:`numpy.ndarray` Data niter_outer : :obj:`int` Number of iterations of outer loop niter_inner : :obj:`int` Number of iterations of inner loop sigma : :obj:`list` Maximum L2 norm of residual. When smaller stop iterations. normalizecols : :obj:`list` Normalize columns (``True``) or not (``False``). Note that this can be expensive as it requires applying the forward operator :math:`n_{cols}` times to unit vectors (i.e., containing 1 at position j and zero otherwise); use only when the columns of the operator are expected to have highly varying norms. show : :obj:`bool`, optional Display iterations log Returns ------- xinv : :obj:`numpy.ndarray` Inverted model iiter : :obj:`int` Number of effective outer iterations cost : :obj:`numpy.ndarray`, optional History of cost function See Also -------- ISTA: Iterative Shrinkage-Thresholding Algorithm (ISTA). FISTA: Fast Iterative Shrinkage-Thresholding Algorithm (FISTA). SPGL1: Spectral Projected-Gradient for L1 norm (SPGL1). SplitBregman: Split Bregman for mixed L2-L1 norms. Notes ----- Solves the following optimization problem for the operator :math:`\mathbf{Op}` and the data :math:`\mathbf{d}`: .. math:: ||\mathbf{x}||_0 \quad subj. to \quad ||\mathbf{Op}\mathbf{x}-\mathbf{b}||_2 <= \sigma, using Orthogonal Matching Pursuit (OMP). This is a very simple iterative algorithm which applies the following step: .. math:: \Lambda_k = \Lambda_{k-1} \cup \{ arg max_j |\mathbf{Op}_j^H \mathbf{r}_k| \} \\ \mathbf{x}_k = \{ arg min_{\mathbf{x}} ||\mathbf{Op}_{\Lambda_k} \mathbf{x} - \mathbf{b}||_2 """ Op = LinearOperator(Op) if show: tstart = time.time() print( 'OMP optimization\n' '-----------------------------------------------------------------\n' 'The Operator Op has %d rows and %d cols\n' 'sigma = %.2e\tniter_outer = %d\tniter_inner = %d\n' 'normalization=%s' % (Op.shape[0], Op.shape[1], sigma, niter_outer, niter_inner, normalizecols)) # find normalization factor for each column if normalizecols: ncols = Op.shape[1] norms = np.zeros(ncols) for icol in range(ncols): unit = np.zeros(ncols) unit[icol] = 1 norms[icol] = np.linalg.norm(Op.matvec(unit)) if show: print( '-----------------------------------------------------------------' ) head1 = ' Itn r2norm' print(head1) cols = [] res = data.copy() cost = np.zeros(niter_outer + 1) cost[0] = np.linalg.norm(data) iiter = 0 while iiter < niter_outer and cost[iiter] > sigma: cres = np.abs(Op.rmatvec(res)) if normalizecols: cres = cres / norms # exclude columns already chosen by putting them negative if iiter > 0: cres[cols] = -1 # choose column with max cres imax = np.argwhere(cres == np.max(cres)).ravel() nimax = len(imax) if nimax > 0: imax = imax[np.random.permutation(nimax)[0]] else: imax = imax[0] cols.append(imax) # estimate model for current set of columns Opcol = Op.apply_columns(cols) x = lsqr(Opcol, data, iter_lim=niter_inner)[0] res = data - Opcol.matvec(x) iiter += 1 cost[iiter] = np.linalg.norm(res) if show: if iiter < 10 or niter_outer - iiter < 10 or iiter % 10 == 0: msg = '%6g %12.5e' % (iiter + 1, cost[iiter]) print(msg) xinv = np.zeros(Op.shape[1], dtype=Op.dtype) xinv[cols] = x if show: print('\nIterations = %d Total time (s) = %.2f' % (iiter, time.time() - tstart)) print( '-----------------------------------------------------------------\n' ) return xinv, iiter, cost
def OMP(Op, data, niter_outer=10, niter_inner=40, sigma=1e-4, normalizecols=False, show=False): r"""Orthogonal Matching Pursuit (OMP). Solve an optimization problem with :math:`L0` regularization function given the operator ``Op`` and data ``y``. The operator can be real or complex, and should ideally be either square :math:`N=M` or underdetermined :math:`N<M`. Parameters ---------- Op : :obj:`pylops.LinearOperator` Operator to invert data : :obj:`numpy.ndarray` Data niter_outer : :obj:`int`, optional Number of iterations of outer loop niter_inner : :obj:`int`, optional Number of iterations of inner loop. By choosing ``niter_inner=0``, the Matching Pursuit (MP) algorithm is implemented. sigma : :obj:`list` Maximum L2 norm of residual. When smaller stop iterations. normalizecols : :obj:`list`, optional Normalize columns (``True``) or not (``False``). Note that this can be expensive as it requires applying the forward operator :math:`n_{cols}` times to unit vectors (i.e., containing 1 at position j and zero otherwise); use only when the columns of the operator are expected to have highly varying norms. show : :obj:`bool`, optional Display iterations log Returns ------- xinv : :obj:`numpy.ndarray` Inverted model iiter : :obj:`int` Number of effective outer iterations cost : :obj:`numpy.ndarray`, optional History of cost function See Also -------- ISTA: Iterative Shrinkage-Thresholding Algorithm (ISTA). FISTA: Fast Iterative Shrinkage-Thresholding Algorithm (FISTA). SPGL1: Spectral Projected-Gradient for L1 norm (SPGL1). SplitBregman: Split Bregman for mixed L2-L1 norms. Notes ----- Solves the following optimization problem for the operator :math:`\mathbf{Op}` and the data :math:`\mathbf{d}`: .. math:: ||\mathbf{x}||_0 \quad subj. to \quad ||\mathbf{Op}\mathbf{x}-\mathbf{b}||_2^2 <= \sigma, using Orthogonal Matching Pursuit (OMP). This is a very simple iterative algorithm which applies the following step: .. math:: \Lambda_k = \Lambda_{k-1} \cup \{ arg max_j |\mathbf{Op}_j^H \mathbf{r}_k| \} \\ \mathbf{x}_k = \{ arg min_{\mathbf{x}} ||\mathbf{Op}_{\Lambda_k} \mathbf{x} - \mathbf{b}||_2^2 Note that by choosing ``niter_inner=0`` the basic Matching Pursuit (MP) algorithm is implemented instead. In other words, instead of solving an optimization at each iteration to find the best :math:`\mathbf{x}` for the currently selected basis functions, the vector :math:`\mathbf{x}` is just updated at the new basis function by taking directly the value from the inner product :math:`\mathbf{Op}_j^H \mathbf{r}_k`. In this case it is highly reccomended to provide a normalized basis function. If different basis have different norms, the solver is likely to diverge. Similar observations apply to OMP, even though mild unbalancing between the basis is generally properly handled. """ ncp = get_array_module(data) Op = LinearOperator(Op) if show: tstart = time.time() algname = 'OMP optimization\n' if niter_inner > 0 else 'MP optimization\n' print(algname + '-----------------------------------------------------------------\n' 'The Operator Op has %d rows and %d cols\n' 'sigma = %.2e\tniter_outer = %d\tniter_inner = %d\n' 'normalization=%s' % (Op.shape[0], Op.shape[1], sigma, niter_outer, niter_inner, normalizecols)) # find normalization factor for each column if normalizecols: ncols = Op.shape[1] norms = ncp.zeros(ncols) for icol in range(ncols): unit = ncp.zeros(ncols, dtype=Op.dtype) unit[icol] = 1 norms[icol] = np.linalg.norm(Op.matvec(unit)) if show: print('-----------------------------------------------------------------') head1 = ' Itn r2norm' print(head1) if niter_inner == 0: x = [] cols = [] res = data.copy() cost = ncp.zeros(niter_outer + 1) cost[0] = np.linalg.norm(data) iiter = 0 while iiter < niter_outer and cost[iiter] > sigma: # compute inner products cres = Op.rmatvec(res) cres_abs = np.abs(cres) if normalizecols: cres_abs = cres_abs / norms # choose column with max cres cres_max = np.max(cres_abs) imax = np.argwhere(cres_abs == cres_max).ravel() nimax = len(imax) if nimax > 0: imax = imax[np.random.permutation(nimax)[0]] else: imax = imax[0] # update active set if imax not in cols: addnew = True cols.append(int(imax)) else: addnew = False imax_in_cols = cols.index(imax) # estimate model for current set of columns if niter_inner == 0: # MP update Opcol = Op.apply_columns([int(imax), ]) res -= Opcol.matvec(cres[imax] * ncp.ones(1)) if addnew: x.append(cres[imax]) else: x[imax_in_cols] += cres[imax] else: # OMP update Opcol = Op.apply_columns(cols) if ncp == np: x = lsqr(Opcol, data, iter_lim=niter_inner)[0] else: x = cgls(Opcol, data, ncp.zeros(int(Opcol.shape[1]), dtype=Opcol.dtype), niter=niter_inner)[0] res = data - Opcol.matvec(x) iiter += 1 cost[iiter] = np.linalg.norm(res) if show: if iiter < 10 or niter_outer - iiter < 10 or iiter % 10 == 0: msg = '%6g %12.5e' % (iiter + 1, cost[iiter]) print(msg) xinv = ncp.zeros(int(Op.shape[1]), dtype=Op.dtype) xinv[cols] = ncp.array(x) if show: print('\nIterations = %d Total time (s) = %.2f' % (iiter, time.time() - tstart)) print( '-----------------------------------------------------------------\n') return xinv, iiter, cost