def __call__(self, Z): if Z.ndim == 1: return normalize(Z) elif Z.ndim == 2: # e.g a 2D patch in a feature_map shape = Z.shape return normalize(Z.flatten()).reshape(shape)
def replace_coherent_atoms(X, y, D, n_class_atoms, thresh=None, kappa=None, unused_data=None): # replace the coherent atoms of the sub-dictionaries in D n_classes = len(n_class_atoms) Xc = [] for c in range(n_classes): x_c = y == c # extract the datapoints for # this class Xc.append(X[:, x_c]) dicts = [] for c in range(n_classes): dicts.append(D[:, get_class_atoms(c, n_class_atoms)]) for c in range(n_classes): Dc = dicts[c] for j in range(c + 1, n_classes): Dj = dicts[j] c_atom_pairs = find_coherent_atoms(Dc, Dj, thresh=thresh, kappa=kappa) if unused_data is None: # we remove the coherent atoms from both Dc and Dj for c_atoms in c_atom_pairs: Dc = np.delete(Dc, c_atoms[0], 1) Dj = np.delete(Dj, c_atoms[1], 1) n_class_atoms[c] = n_class_atoms[c] - 1 n_class_atoms[j] = n_class_atoms[j] - 1 else: # replace them with one datapoint that # hasn't been used for c_atoms in c_atom_pairs: # there are datapoints available # to be used as atoms if len(unused_data[c]) != 0: _idx = np.random.choice(unused_data[c], size=1) idx = _idx[0] Dc[:, c_atoms[0]] = Xc[c][:, idx] Dc[:, c_atoms[0]] = normalize(Dc[:, c_atoms[0]]) unused_data[c].remove(idx) if len(unused_data[j]) != 0: _idx = np.random.choice(unused_data[j], size=1) idx = _idx[0] Dj[:, c_atoms[1]] = Xc[j][:, idx] Dj[:, c_atoms[1]] = normalize(Dj[:, c_atoms[1]]) unused_data[j].remove(idx) dicts[c], dicts[j] = Dc, Dj # merge the sub-dictionaries D = dicts[0] for c in range(1, n_classes): D = np.hstack((D, dicts[c])) return D, n_class_atoms
def force_mi(D, X, Z, unused_data, eta, max_tries=100): # force mutual incoherence within a dictionary n_atoms = D.shape[1] G = np.abs(np.dot(D.T, D)) np.fill_diagonal(G, 0) for atom_idx1 in range(n_atoms): atom_idx2 = np.argmax(G[atom_idx1, :]) # the maximum coherence mcoh = G[atom_idx1, atom_idx2] if mcoh < eta: print "less than the eta" continue # choose one of the two to replace # should we choose the one least used? if norm(Z[atom_idx1, :]) > norm(Z[atom_idx2, :]): c_atom = atom_idx1 else: c_atom = atom_idx2 # new_atom = None cnt = 0 available_data = unused_data[:] min_idx = None min_coh = mcoh while mcoh > eta: # replace the coherent atom if cnt > max_tries: break # no datapoint available to be used as atom if len(available_data) == 0: return D _idx = np.random.choice(available_data, size=1) if len(_idx) == 0: return D, unused_data idx = _idx[0] new_atom = X[:, idx] new_atom = normalize(new_atom) available_data.remove(idx) g = np.abs(np.dot(D.T, new_atom)) mcoh = np.max(g) if mcoh < min_coh or min_coh is None: min_coh = mcoh min_idx = idx cnt += 1 D[:, c_atom] = X[:, min_idx] D[:, c_atom] = normalize(D[:, c_atom]) unused_data.remove(min_idx) return D, unused_data
def approx_ksvd(Y, D, X, n_cycles=1, verbose=True): # the approximate KSVD algorithm n_atoms = D.shape[1] n_features, n_samples = Y.shape unused_atoms = [] R = Y - fast_dot(D, X) for c in range(n_cycles): for k in range(n_atoms): if verbose: sys.stdout.write("\r" + "k-svd..." + ":%3.2f%%" % ((k / float(n_atoms)) * 100)) sys.stdout.flush() # find all the datapoints that use the kth atom omega_k = X[k, :] != 0 if not np.any(omega_k): # print "this atom is not used" unused_atoms.append(k) continue Rk = R[:, omega_k] + np.outer(D[:, k], X[k, omega_k]) # update of D[:,k] D[:, k] = np.dot(Rk, X[k, omega_k]) D[:, k] = normalize(D[:, k]) # update of X[:,k] X[k, omega_k] = np.dot(Rk.T, D[:, k]) # update the residual R[:, omega_k] = Rk - np.outer(D[:, k], X[k, omega_k]) print "" return D, X, unused_atoms
def ksvd_dict_learn(X, n_atoms, init_dict='data', sparse_coder=None, max_iter=20, non_neg=False, approx=False, eta=None, n_cycles=1, n_jobs=1, mmap=False, verbose=True): """ The K-SVD algorithm X: the data matrix of shape (n_features,n_samples) n_atoms: the number of atoms in the dictionary sparse_coder: must be an instance of the sparse_coding.sparse_encoder class approx: if true, invokes the approximate KSVD algorithm max_iter: the maximum number of iterations non_neg: if set to True, it uses non-negativity constraints n_cycles: the number of updates per atom (Dictionary Update Cycles) n_jobs: the number of CPU threads mmap: if set to True, the algorithm applies memory mapping to save memory """ n_features, n_samples = X.shape shape = (n_atoms, n_samples) Z = np.zeros(shape) # dictionary initialization # track the datapoints that are not used as atoms unused_data = [] if init_dict == 'data': from .utils import init_dictionary D, unused_data = init_dictionary(X, n_atoms, method=init_dict, return_unused_data=True) else: D = np.copy(init_dict) if mmap: D = get_mmap(D) sparse_coder.mmap = True print "dictionary initialized" max_patience = 10 error_curr = 0 error_prev = 0 it = 0 patience = 0 approx_errors = [] while it < max_iter and patience < max_patience: print "----------------------------" print "iteration", it print "" it_start = time.time() if verbose: t_sparse_start = time.time() # sparse coding Z = sparse_coder(X, D) if verbose: t_sparse_duration = time.time() - t_sparse_start print "sparse coding took", t_sparse_duration, "seconds" t_dict_start = time.time() # ksvd to learn the dictionary set_openblas_threads(n_jobs) if approx: D, _, unused_atoms = approx_ksvd(X, D, Z, n_cycles=n_cycles) elif non_neg: D, _, unused_atoms = nn_ksvd(X, D, Z, n_cycles=it) else: D, _, unused_atoms = ksvd(X, D, Z, n_cycles=n_cycles) set_openblas_threads(1) if verbose: t_dict_duration = time.time() - t_dict_start print "K-SVD took", t_dict_duration, "seconds" print "" if verbose: print "number of unused atoms:", len(unused_atoms) # replace the unused atoms in the dictionary for j in range(len(unused_atoms)): # no datapoint available to be used as atom if len(unused_data) == 0: break _idx = np.random.choice(unused_data, size=1) idx = _idx[0] D[:, unused_atoms[j]] = X[:, idx] D[:, unused_atoms[j]] = normalize(D[:, unused_atoms[j]]) unused_data.remove(idx) if eta is not None: # do not force incoherence in the last iteration if it < max_iter - 1: # force Mutual Incoherence D, unused_data = force_mi(D, X, Z, unused_data, eta) if verbose: amc = average_mutual_coherence(D) print "average mutual coherence:", amc it_duration = time.time() - it_start # calculate the approximation error error_curr = approx_error(D, Z, X, n_jobs=2) approx_errors.append(error_curr) if verbose: print "error:", error_curr print "error difference:", (error_curr - error_prev) error_prev = error_curr print "duration:", it_duration, "seconds" if (it > 0) and (error_curr > 0.9 * error_prev or error_curr > error_prev): patience += 1 it += 1 print "" return D, Z