def test_nnls_for_a_vector(self): """ Verifies a question raised by Jeremy: is the nnls working with vectors as input ? """ UtU = np.random.random((15, 15)) nnls.hals_nnls_acc(np.random.random((8, 1)), UtU, np.random.random((15, 1))) with self.assertRaises(err.ArgumentException): nnls.hals_nnls_acc(np.random.random((8)), UtU, np.random.random((15, 1)), nonzero=True)
def test_error_in_optim(self): """ Verifies that errors are raised when necessary. """ UtU = np.random.random((8, 8)) UtU[2, 2] = 0 nnls.hals_nnls_acc(np.random.random((8, 8)), UtU, np.random.random((8, 8))) with self.assertRaises(err.ZeroColumnWhenUnautorized): nnls.hals_nnls_acc(np.random.random((8, 8)), UtU, np.random.random((8, 8)), nonzero=True)
def test_wrong_arguments(self): """ Verifies that errors are raised when necessary. """ with self.assertRaises(err.ArgumentException): nnls.hals_nnls_acc(np.random.random((8, 8)), np.random.random((8, 8)), np.array([])) with self.assertRaises(err.ArgumentException): nnls.hals_nnls_acc(np.random.random((8)), np.random.random((8, 8)), np.random.random((8, 8))) with self.assertRaises(err.ArgumentException): nnls.hals_nnls_acc(np.random.random((8, 8)), np.random.random((8)), np.random.random((8, 8)))
def one_ntd_step(tensor, ranks, in_core, in_factors, norm_tensor, sparsity_coefficients, fixed_modes, normalize, mode_core_norm, alpha=0.5, delta=0.01): """ One pass of Hierarchical Alternating Least Squares update along all modes, and gradient update on the core, which decreases reconstruction error in Nonnegative Tucker Decomposition. Update the factors by solving a least squares problem per mode, as described in [1]. Note that the unfolding order is the one described in [2], which is different from [1]. This function is strictly superior to a least squares solver ran on the matricized problems min_X ||Y - AX||_F^2 since A is structured as a Kronecker product of the other factors/core. Tensors are manipulated with the tensorly toolbox [3]. Parameters ---------- unfolded_tensors: list of array The spectrogram tensor, unfolded according to all its modes. ranks: list of integers Ranks for eac factor of the decomposition. in_core : tensorly tensor Current estimates of the core in_factors: list of array Current estimates for the factors of this NTD. The value of factor[update_mode] will be updated using a least squares update. The values in in_factors are not modified. norm_tensor : float The Frobenius norm of the input tensor sparsity_coefficients: list of float (as much as the number of modes + 1 for the core) The sparsity coefficients on each factor and on the core respectively. fixed_modes: list of integers (between 0 and the number of modes + 1 for the core) Has to be set not to update a factor, taken in the order of modes and lastly on the core. normalize: list of boolean (as much as the number of modes + 1 for the core) A boolean whereas the factors need to be normalized. The normalization is a l_2 normalization on each of the rank components (For the factors, each column will be normalized, ie each atom of the dimension of the current rank). mode_core_norm: integer or None The mode on which normalize the core, or None if normalization shouldn't be enforced. Will only be useful if the last element of the previous "normalise" argument is set to True. Indexes of the modes start at 0. Default: None alpha : positive float Ratio between outer computations and inner loops. Typically set to 0.5 or 1. Set to +inf in the deterministic mode, as it depends on runtime. Default: 0.5 delta : float in [0,1] Early stop criterion, while err_k > delta*err_0. Set small for almost exact nnls solution, or larger (e.g. 1e-2) for inner loops of a NTD computation. Default: 0.01 Returns ------- core: tensorly tensor The core tensor linking the factors of the decomposition factors: list of factors An array containing all the factors computed with the NTD cost_fct_val: The value of the cost function at this step, normalized by the squared norm of the original tensor. References ---------- [1] Tamara G Kolda and Brett W Bader. "Tensor decompositions and applications", SIAM review 51.3 (2009), pp. 455{500. [2] Jeremy E Cohen. "About notations in multiway array processing", arXiv preprint arXiv:1511.01306, (2015). [3] J. Kossai et al. "TensorLy: Tensor Learning in Python", arxiv preprint (2018) """ # Avoiding errors for fixed_value in fixed_modes: sparsity_coefficients[fixed_value] = None # Copy core = in_core.copy() factors = in_factors.copy() # Generating the mode update sequence modes_list = [mode for mode in range(tl.ndim(tensor)) if mode not in fixed_modes] for mode in modes_list: #unfolded_core = tl.base.unfold(core, mode) tic = time.time() # UtU # First, element-wise products # some computations could be reused but the gain is small. elemprod = factors.copy() for i, factor in enumerate(factors): if i != mode: elemprod[i] = tl.dot(tl.conj(tl.transpose(factor)), factor) # Second, the multiway product with core G temp = tl.tenalg.multi_mode_dot(core, elemprod, skip=mode) # this line can be computed with tensor contractions con_modes = [i for i in range(tl.ndim(tensor)) if i != mode] UtU = tl.tenalg.contract(temp, con_modes, core, con_modes) #UtU = unfold(temp, mode)@tl.transpose(unfold(core, mode)) # UtM # First, the contraction of data with other factors temp = tl.tenalg.multi_mode_dot(tensor, factors, skip=mode, transpose = True) # again, computable by tensor contractions #MtU = unfold(temp, mode)@tl.transpose(unfold(core, mode)) MtU = tl.tenalg.contract(temp, con_modes, core, con_modes) UtM = tl.transpose(MtU) # Computing the Kronekcer product #kron = tl.tenalg.kronecker(factors, skip_matrix = mode, reverse = False) #kron_core = tl.dot(kron, tl.transpose(unfolded_core)) #rhs = tl.dot(unfolded_tensors[mode], kron_core) # Maybe suboptimal #cross = tl.dot(tl.transpose(kron_core), kron_core) timer = time.time() - tic # Call the hals resolution with nnls, optimizing the current mode factors[mode] = tl.transpose(nnls.hals_nnls_acc(UtM, UtU, tl.transpose(factors[mode]), maxiter=100, atime=timer, alpha=alpha, delta=delta, sparsity_coefficient = sparsity_coefficients[mode], normalize = normalize[mode])[0]) #refolded_tensor = tl.base.fold(unfolded_tensors[0], 0, tensor_shape) # Core update #all_MtX = tl.tenalg.multi_mode_dot(tensor, factors, transpose = True) # better implementation: reuse the computation of temp ! # Also reuse elemprod form last update all_MtX = tl.tenalg.mode_dot(temp, tl.transpose(factors[modes_list[-1]]), modes_list[-1]) all_MtM = tl.copy(elemprod) all_MtM[modes_list[-1]] = factors[modes_list[-1]].T@factors[modes_list[-1]] #all_MtM = np.array([fac.T@fac for fac in factors]) # Projected gradient gradient_step = 1 #print(f"factors[modes_list[-1]]: {factors[modes_list[-1]]}") #print(f"all_MtM: {all_MtM}") for MtM in all_MtM: #print(f"MtM: {MtM}") gradient_step *= 1/(scipy.sparse.linalg.svds(MtM, k=1)[1][0]) gradient_step = round(gradient_step, 6) # Heurisitc, to avoid consecutive imprecision cnt = 1 upd_0 = 0 upd = 1 if sparsity_coefficients[-1] is None: sparse = 0 else: sparse = sparsity_coefficients[-1] # TODO: dynamic stopping criterion # Maybe: try fast gradient instead of gradient while cnt <= 300 and upd>= delta * upd_0: gradient = - all_MtX + tl.tenalg.multi_mode_dot(core, all_MtM, transpose = False) + sparse * tl.ones(core.shape) # Proposition of reformulation for error computations delta_core = np.minimum(gradient_step*gradient, core) core = core - delta_core upd = tl.norm(delta_core) if cnt == 1: upd_0 = upd cnt += 1 if normalize[-1]: unfolded_core = tl.unfold(core, mode_core_norm) for idx_mat in range(unfolded_core.shape[0]): if tl.norm(unfolded_core[idx_mat]) != 0: unfolded_core[idx_mat] = unfolded_core[idx_mat] / tl.norm(unfolded_core[idx_mat], 2) core = tl.fold(unfolded_core, mode_core_norm, core.shape) # Adding the l1 norm value to the reconstruction error sparsity_error = 0 for index, sparse in enumerate(sparsity_coefficients): if sparse: if index < len(factors): sparsity_error += 2 * (sparse * np.linalg.norm(factors[index], ord=1)) elif index == len(factors): sparsity_error += 2 * (sparse * tl.norm(core, 1)) else: raise NotImplementedError("TODEBUG: Too many sparsity coefficients, should have been raised before.") rec_error = norm_tensor ** 2 - 2*tl.tenalg.inner(all_MtX, core) + tl.tenalg.inner(tl.tenalg.multi_mode_dot(core, all_MtM, transpose = False), core) cost_fct_val = (rec_error + sparsity_error) / (norm_tensor ** 2) #exhaustive_rec_error = (tl.norm(tensor - tl.tenalg.multi_mode_dot(core, factors, transpose = False), 2) + sparsity_error) / norm_tensor #print("diff: " + str(rec_error - exhaustive_rec_error)) #print("max" + str(np.amax(factors[2]))) return core, factors, cost_fct_val # exhaustive_rec_error
def one_step_parafac2(slices, rank, W_list_in, H_in, D_list_in, mu_list_in, norm_slices, previous_cost_fct_val, increasing_mu=True, tol_mu=1e6, step_mu=1.02, init_with_P=True, W_star_in=None, P_list_in=None, sparsity_coefficient=None, fixed_modes=[], normalize=[False, False, False, False, False]): """ One pass of PARAFAC 2 update on all channels Update the factors by solving least squares problems per factor. The order of the factors for "normalize" and "fixed_modes" is as follows: 0 -> W_list, 1 -> H, 2 -> D_list, 3 -> W_star, 4 -> P_list This function add a functionnality, whihch is whether it should increase mu or not. Indeed, as mu increases, convergence is not assured. Observing the previous reconstruction errors, mu stop increasing when the reconstruction error increases, and analogously for the coupling error between W_k and W^*. Parameters ---------- slices : list of array List of the slices of the spectrogram (for each channel) rank : int Rank of the decomposition. W_list_in : list of array Current estimates for the PARAFAC 2 decomposition of the factors W_k (for each channel) H_in : Array Current estimate for the PARAFAC 2 decomposition of the factor H D_list_in : list of diagonal arrays Current estimates for the PARAFAC 2 decomposition of the factors D_k (for each channel) mu_list_in : list of float Parameter for the weight of the latent factor norm_slices : list of norms The Frobenius norms of each slice of the spectrogram (for each channel), in order not to compute them at each iteration previous_rec_error: float The reconstruction error at the last iteration increasing_mu: boolean Whether mu should be increased or not. Passed as variable to store that it is become "False" Default: True tol_mu: float Maximal value of mu Default: 1e6 step_mu: float The multiplicative constant which increases mu at each iteration Default: 1.02 init_with_P: boolean Define Whether the PARAFAC2 decomposition must be performed by initializing P_k or W^*: True: initialize with the P_k False: initialize with W^* W_star_in: Initialization for the W^*, only used if init_with_P is set to False P_list_in: list or None Initialization for the P_k, only used if init_with_P is set to True sparsity_coefficient: float The sparsity coefficient on H. If set to None, the algorithm is computed without sparsity Default: None fixed_modes: array of integers (between 0 and 5) Has to be set not to update a factor, 0 and 1 for U and V respectively Default: [] normalize: array of boolean (5) A boolean where the factors need to be normalized. The normalization is a l_2 normalization on each of the rank components Default: [False, False, False, False, False] Returns ------- W_list, D_list, H, W_star, mu_list: The updated factors cost_fct_val: The value of the cost function at this step, normalized by the squared norm of the original tensor. """ W_list = W_list_in.copy() D_list = D_list_in.copy() H = H_in.copy() mu_list = mu_list_in.copy() cost_fct_val = 0 nb_channel = len(W_list) # Tous les append doivent etre reinit ici if P_list_in is None and W_star_in is None: raise ValueError( 'The list of P_k and W^* are both to None: one has to be set for the operation.' ) elif init_with_P == True and P_list_in is None: raise ValueError( 'PARAFAC2 is set with the init of P_k, but they are set to None.') elif init_with_P == False and W_star_in is None: raise ValueError( 'PARAFAC2 is set with the init of W^*, but it is set to None.') # The initialization is made with the P_k if init_with_P: P_list = P_list_in.copy() W_star = compute_W_star(P_list, W_list, mu_list, nb_channel, normalize=True) if 4 in fixed_modes: P_list = compute_P_k(W_list, W_star, nb_channel) # The initialization is made with W^* else: W_star = W_star_in P_list = compute_P_k(W_list, W_star, nb_channel) if 3 in fixed_modes: W_star = compute_W_star(P_list, W_list, mu_list, nb_channel, normalize=normalize[3]) for k in range(nb_channel): if 0 not in fixed_modes: # Update W_k tic = time.time() DkH = D_list[k] @ H VVt = np.dot(DkH, np.transpose(DkH)) VMt = np.dot(DkH, np.transpose(slices[k])) timer = time.time() - tic W_list[k] = np.transpose( nnls.hals_coupling_nnls_acc(VMt, VVt, np.transpose(W_list[k]), np.transpose(P_list[k] @ W_star), mu_list[k], maxiter=100, atime=timer, alpha=0.5, delta=0.01, normalize=normalize[0], nonzero=False)[0]) if 2 not in fixed_modes: # Update D_k tic = time.time() khatri = tl.tenalg.khatri_rao([W_list[k], H.T]) UtU = np.transpose(khatri) @ khatri # flattening line by line, so no inversion of the matrices in the Khatri-Rao product. UtM_local = (khatri.T) @ (slices[k].flatten()) UtM = np.reshape(UtM_local, (UtM_local.shape[0], 1)) timer = time.time() - tic # Keep only the diagonal coefficients diag_D = np.diagonal(D_list[k]) # Reshape for having a proper column vector (error in nnls function otherwise) diag_D = np.reshape( diag_D, (diag_D.shape[0], 1) ) # It simply instead becomes a vector column instead of a list D_list[k] = nnls.hals_nnls_acc( UtM, UtU, diag_D, maxiter=100, atime=timer, alpha=0.5, delta=0.01, sparsity_coefficient=None, normalize=False, nonzero=False )[0] # All these parameters are not available for a diagonal matrix a, b = D_list[k].shape if a == b: # Make the matrix a diagonal one D_list[k] = np.diag(np.diagonal((D_list[k]))) else: D_list[k] = np.diag(D_list[k].flatten()) if normalize[2]: for note_index in range(rank): norm = np.linalg.norm(D_list[:, note_index], ord='fro') if norm == 0: D_list[:, note_index, note_index] = [ 1 / (nb_channel**2) for k in range(nb_channel) ] else: D_list[:, note_index] /= np.linalg.norm(D_list[:, note_index], ord='fro') if 1 not in fixed_modes: # Update H tic = time.time() UtU = np.zeros((rank, rank)) UtM = np.zeros((rank, (slices[0].shape)[1])) for k in range(nb_channel): WkDk = W_list[k] @ D_list[k] UtU += np.dot(np.transpose(WkDk), WkDk) UtM += np.dot(np.transpose(WkDk), slices[k]) timer = time.time() - tic H = nnls.hals_nnls_acc(UtM, UtU, H, maxiter=100, atime=timer, alpha=0.5, delta=0.01, sparsity_coefficient=sparsity_coefficient, normalize=normalize[1], nonzero=False)[0] couple_error = [] if sparsity_coefficient != None: cost_fct_val = sparsity_coefficient * np.linalg.norm(H, ord=1) for k in range(nb_channel): couple_error.append( np.linalg.norm(W_list[k] - P_list[k] @ W_star, ord='fro')) slice_rec_error = np.linalg.norm( slices[k] - W_list[k] @ D_list[k] @ H)**2 + ( mu_list[k] * couple_error[k]**2) / norm_slices[k] cost_fct_val += slice_rec_error if previous_cost_fct_val != None: if mu_list[k] < tol_mu and (previous_cost_fct_val - cost_fct_val) > 0 and increasing_mu: mu_list[k] *= step_mu elif increasing_mu: # Stop increasing mu for the next iterations increasing_mu = False return W_list, H, D_list, W_star, P_list, mu_list, cost_fct_val, couple_error, increasing_mu
def one_ntf_step(unfolded_tensors, rank, in_factors, norm_tensor, update_rule, beta, sparsity_coefficients, fixed_modes, normalize, alpha=0.5, delta=0.01): """ One pass of Hierarchical Alternating Least Squares update along all modes Update the factors by solving a least squares problem per mode (in hals), as described in [1], or using the Multiplicative Update for the entire factors [2]. Note that the unfolding order is the one described in [3], which is different from [1]. Parameters ---------- unfolded_tensors: list of array The spectrogram tensor, unfolded according to all its modes. in_factors: list of array Current estimates for the PARAFAC decomposition of tensor. The value of factor[update_mode] will be updated using a least squares update. The values in in_factors are not modified. rank: int Rank of the decomposition. norm_tensor : float The Frobenius norm of the input tensor update_rule: string "hals" | "mu" The chosen update rule. HALS performs optimization with the euclidean norm, MU performs the optimization using the $\beta$-divergence loss, which generalizes the Euclidean norm, and the Kullback-Leibler and Itakura-Saito divergences. The chosen beta-divergence is specified with the parameter `beta`. beta: float The beta parameter for the beta-divergence. 2 - Euclidean norm 1 - Kullback-Leibler divergence 0 - Itakura-Saito divergence sparsity_coefficients : List of floats sparsity coefficients for every mode. fixed_modes : List of integers Indexes of modes that are not updated normalize: List of boolean (as much as the number of modes) A boolean where the factors need to be normalized. The normalization is a l_2 normalization on each of the rank components (columnwise) alpha : positive float Ratio between outer computations and inner loops. Typically set to 0.5 or 1. Default: 0.5 delta : float in [0,1] Early stop criterion, while err_k > delta*err_0. Set small for almost exact nnls solution, or larger (e.g. 1e-2) for inner loops of a PARAFAC computation. Default: 0.01 Returns ------- np.array(factors): numpy array An array containing all the factors computed with PARAFAC decomposition cost_fct_val: The value of the cost function at this step, normalized by the squared norm of the original tensor. References ---------- [1] Tamara G Kolda and Brett W Bader. "Tensor decompositions and applications", SIAM review 51.3 (2009), pp. 455{500. [2] Févotte, C., & Idier, J. (2011). Algorithms for nonnegative matrix factorization with the β-divergence. Neural computation, 23(9), 2421-2456. [3] Jeremy E Cohen. "About notations in multiway array processing", arXiv preprint arXiv:1511.01306, (2015). """ if update_rule not in ["hals", "mu"]: raise err.InvalidArgumentValue( f"Invalid update rule: {update_rule}") from None if update_rule == "hals" and beta != 2: raise err.InvalidArgumentValue( f"The hals is only valid for the frobenius norm, corresponding to the beta divergence with beta = 2. Here, beta was set to {beta}. To compute NMF with this value of beta, please use the mu update_rule." ) from None # Avoiding errors for fixed_value in fixed_modes: sparsity_coefficients[fixed_value] = None # Copy factors = in_factors.copy() # Generating the mode update sequence gen = [ mode for mode in range(len(unfolded_tensors)) if mode not in fixed_modes ] for mode in gen: if update_rule == "hals": tic = time.time() # Computing Hadamard of cross-products cross = tl.tensor(tl.ones((rank, rank))) #, **tl.context(tensor)) for i, factor in enumerate(factors): if i != mode: cross *= tl.dot(tl.transpose(factor), factor) # Computing the Khatri Rao product krao = tl.tenalg.khatri_rao(factors, skip_matrix=mode) rhs = tl.dot(unfolded_tensors[mode], krao) timer = time.time() - tic # Call the hals resolution with nnls, optimizing the current mode factors[mode] = tl.transpose( nnls.hals_nnls_acc( tl.transpose(rhs), cross, tl.transpose(factors[mode]), maxiter=100, atime=timer, alpha=alpha, delta=delta, sparsity_coefficient=sparsity_coefficients[mode], normalize=normalize[mode])[0]) elif update_rule == "mu": krao = tl.tenalg.khatri_rao(factors, skip_matrix=mode) factors[mode] = mu.mu_betadivmin(factors[mode], krao.T, unfolded_tensors[mode], beta) # Adding the l1 norm value to the reconstruction error sparsity_error = 0 for index, sparse in enumerate(sparsity_coefficients): if sparse: sparsity_error += 2 * (sparse * np.linalg.norm(factors[index], ord=1)) if update_rule == "hals": # error computation (improved using precomputed quantities) rec_error = norm_tensor**2 - 2 * tl.dot( tl.tensor_to_vec(factors[mode]), tl.tensor_to_vec(rhs)) + tl.norm( tl.dot(factors[mode], tl.transpose(krao)), 2)**2 elif update_rule == "mu": rec_error = beta_div.beta_divergence(unfolded_tensors[mode], factors[mode] @ krao.T, beta) cost_fct_val = (rec_error + sparsity_error) / (norm_tensor**2) return factors, cost_fct_val
def one_nmf_step(data, rank, U_in, V_in, norm_data, update_rule, beta, sparsity_coefficients, fixed_modes, normalize): """ One pass of updates for each factor in NMF Update the factors by solving a nonnegative least squares problem per mode if the update_rule is "hals", or by using the Multiplicative Update on each factor if the update_rule is "mu". Parameters ---------- data: nonnegative array The matrix M, which is factorized, of size m*n rank: integer The rank of the decomposition U_in: array of floats Initial U factor, of size m*r V_in: array of floats Initial V factor, of size r*n norm_data: float The Frobenius norm of the input matrix (data) update_rule: string "hals" | "mu" The chosen update rule. HALS performs optimization with the euclidean norm, MU performs the optimization using the $\beta$-divergence loss, which generalizes the Euclidean norm, and the Kullback-Leibler and Itakura-Saito divergences. The chosen beta-divergence is specified with the parameter `beta`. Default: "hals" beta: float The beta parameter for the beta-divergence. 2 - Euclidean norm 1 - Kullback-Leibler divergence 0 - Itakura-Saito divergence Default: 2 sparsity_coefficients: List of float (two) The sparsity coefficients on U and V respectively. If set to None, the algorithm is computed without sparsity Default: [None, None], fixed_modes: List of integers (between 0 and 2) Has to be set not to update a factor, 0 and 1 for U and V respectively Default: [] normalize: List of boolean (two) A boolean whereas the factors need to be normalized. The normalization is a l_2 normalization on each of the rank components (columnwise for U, linewise for V) Default: [False, False] Returns ------- U, V: numpy arrays Factors of the NMF cost_fct_val: The value of the cost function at this step, normalized by the squared norm of the original matrix. """ if update_rule not in ["hals", "mu"]: raise err.InvalidArgumentValue( f"Invalid update rule: {update_rule}") from None if update_rule == "hals" and beta != 2: raise err.InvalidArgumentValue( f"The hals is only valid for the frobenius norm, corresponding to the beta divergence with beta = 2. Here, beta was set to {beta}. To compute NMF with this value of beta, please use the mu update_rule." ) from None if len(sparsity_coefficients) != 2: raise ValueError("NMF needs 2 sparsity coefficients to be performed") # Copy U = U_in.copy() V = V_in.copy() if 0 not in fixed_modes: # U update if update_rule == "hals": # Set timer for acceleration in hals_nnls_acc tic = time.time() # Computing cross products VVt = np.dot(V, np.transpose(V)) VMt = np.dot(V, np.transpose(data)) # End timer for acceleration in hals_nnls_acc timer = time.time() - tic # Compute HALS/NNLS resolution U = np.transpose( nnls.hals_nnls_acc( VMt, VVt, np.transpose(U_in), maxiter=100, atime=timer, alpha=0.5, delta=0.01, sparsity_coefficient=sparsity_coefficients[0], normalize=normalize[0], nonzero=False)[0]) elif update_rule == "mu": U = mu.mu_betadivmin(U, V, data, beta) if 1 not in fixed_modes: # V update if update_rule == "hals": # Set timer for acceleration in hals_nnls_acc tic = time.time() # Computing cross products UtU = np.dot(np.transpose(U), U) UtM = np.dot(np.transpose(U), data) # End timer for acceleration in hals_nnls_acc timer = time.time() - tic # Compute HALS/NNLS resolution V = nnls.hals_nnls_acc( UtM, UtU, V_in, maxiter=100, atime=timer, alpha=0.5, delta=0.01, sparsity_coefficient=sparsity_coefficients[1], normalize=normalize[1], nonzero=False)[0] elif update_rule == "mu": V = np.transpose(mu.mu_betadivmin(V.T, U.T, data.T, beta)) sparsity_coefficients = np.where( np.array(sparsity_coefficients) == None, 0, sparsity_coefficients) if update_rule == "hals": cost = np.linalg.norm(data - np.dot(U, V), ord='fro')**2 + 2 * ( sparsity_coefficients[0] * np.linalg.norm(U, ord=1) + sparsity_coefficients[1] * np.linalg.norm(V, ord=1)) elif update_rule == "mu": cost = beta_div.beta_divergence(data, np.dot(U, V), beta) #cost = cost/(norm_data**2) return U, V, cost