Exemple #1
0
 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)
Exemple #2
0
 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)
Exemple #3
0
    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)))
Exemple #4
0
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
Exemple #5
0
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
Exemple #6
0
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
Exemple #7
0
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