Beispiel #1
0
def cpd(T, R, options=False):
    """
    Given a tensor T and a rank R, this function computes an approximated CPD of T with rank r. The factors matrices are
    given in the form of a list [W^(1),...,W^(L)]. They are such that sum_(r=1)^R W[:,r]^(1) ⊗ ... ⊗ W[:,r]^(L) is an
    approximation for T, where W[:,r]^(l) denotes the r-th column of W^(l). The same goes for the other factor matrices.

    Inputs
    ------
    T: float array
        Objective tensor in coordinates.
    R: int
        The desired rank of the approximating tensor.
    options: class with the following parameters
        maxiter: int
            Number of maximum iterations allowed for the dGN function. Default is 200.
        tol, tol_step, tol_improv, tol_grad: float
            Tolerance criterion to stop the iteration process of the dGN function. Default is 1e-6 for all. Let T^(k) be
            the approximation at the k-th iteration, with corresponding CPD w^(k) in vectorized form. The program stops 
            if
                1) |T - T^(k)| / |T| < tol
                2) | w^(k-1) - w^(k) | < tol_step
                3) | |T - T^(k-1)| / |T| - |T - T^(k)| / |T| | < tol_improv
                4) | grad F(w^(k)) | < tol_grad, where F(w^(k)) = 1/2 |T - T^(k)|^2
        tol_mlsvd: float
            Tolerance criterion for the truncation. The idea is to obtain a truncation (U_1,...,U_L)*S such that
            |T - (U_1,...,U_L)*S| / |T| < tol_mlsvd. Default is 1e-16. If tol_mlsvd = -1 the program uses the original 
        tensor, so the computation of the MLSVD is not performed.
        trunc_dims: int or list of ints
            Consider a third order tensor T. If trunc_dims is not 0, then it should be a list with three integers
            [R1,R2,R3] such that 1 <= R1 <= m, 1 <= R2 <= n, 1 <= R3 <= p. The compressed tensor will have dimensions
            (R1,R2,R3). Default is 0, which means 'automatic' truncation.
        initialization: string or list
            This options is used to choose the initial point to start the iterations. For more information, check the 
            function starting_point.
        refine: bool
            If True, after the dGN iterations the program uses the solution to repeat the dGN over the original space
            using the solution as starting point. Default is False.
        symm: bool
            The user should set symm to True if the objective tensor is symmetric, otherwise symm is False. Default is
            False.
        trials: int
            This parameter is only used for tensor with order higher than 3. The computation of the tensor train CPD 
            requires the computation of several CPD of third order tensors. If only one of these CPD's is of low 
            quality (divergence or local minimum) then all effort is in vain. One work around is to compute several
            CPD'd and keep the best, for third order tensor. The parameter trials defines the maximum number of
            times we repeat the computation of each third order CPD. These trials stops when the relative error is
            less than 1e-4 or when the maximum number of trials is reached. Default is trials=1.
        display: -2, -1, 0, 1, 2, 3 or 4
            This options is used to control how information about the computations are displayed on the screen. The 
            possible values are -1, 0, 1 (default), 2, 3, 4. Notice that display=3 makes the overall running time large
            since it will force the program to show intermediate errors which are computationally costly. -1 is a
            special option for displaying minimal relevant information for tensors with order higher then 3. We
            summarize the display options below.
                -2: display same as options -1 plus the Tensor Train error
                -1: display only the errors of each CPD computation and the final relevant information
                0: no information is printed
                1: partial information is printed
                2: full information is printed
                3: full information + errors of truncation and starting point are printed
                4: almost equal to display = 3 but now there are more digits displayed on the screen (display = 3 is a
                "cleaner" version of display = 4, with less information).
        epochs: int
            Number of Tensor Train CPD cycles. Use only for tensor with order higher than 3. Default is epochs=1.

    It is not necessary to create 'options' with all parameters described above. Any missing parameter is assigned to
    its default value automatically. For more information about the options, check the Tensor Fox tutorial at

        https://github.com/felipebottega/Tensor-Fox/tree/master/tutorial
    
    Outputs
    -------
    factors: list of float 2D arrays with shape (dims[i], R) each
        The factors matrices which corresponds to an approximate CPD for T.
    final_outputs: list of classes
        Each tricpd and bicpd call gives a output class with all sort of information about the computations. The list 
        'final_outputs' contains all these classes.
    """

    # INITIAL PREPARATIONS

    # Verify if T is sparse, in which case it will be given as a list with the data.
    if type(T) == list:
        T_orig = deepcopy(T)
        T = deepcopy(T_orig)
        data_orig, idxs_orig, dims_orig = T_orig
    else:
        dims_orig = T.shape
    L = len(dims_orig)

    # Set options.
    options = aux.make_options(options, L)
    method = options.method
    display = options.display
    tol_mlsvd = options.tol_mlsvd
    if type(tol_mlsvd) == list:
        if L > 3:
            tol_mlsvd = tol_mlsvd[0]
        else:
            tol_mlsvd = tol_mlsvd[1]

    # Test consistency of dimensions and rank.
    aux.consistency(R, dims_orig, options)

    # Verify method.
    if method == 'dGN' or method == 'als':
        factors, output = tricpd(T, R, options)
        return factors, output

    # Change ordering of indexes to improve performance if possible.
    T, ordering = aux.sort_dims(T)
    if type(T) == list:
        Tsize = norm(T[0])
        dims = T[2]
        # If T is sparse, we must use the classic method, and tol_mlsvd is set to the default 1e-16 in the case the
        # user requested -1 or 0.
        if tol_mlsvd < 0:
            options.tol_mlsvd = 1e-16
            tol_mlsvd = 1e-16
    else:
        Tsize = norm(T)
        dims = T.shape

    # COMPRESSION STAGE

    if display != 0:
        print(
            '-----------------------------------------------------------------------------------------------'
        )
        print('Computing MLSVD')

    # Compute compressed version of T with the MLSVD. We have that T = (U_1,...,U_L)*S.
    if display > 2 or display < -1:
        S, U, T1, sigmas, best_error = cmpr.mlsvd(T, Tsize, R, options)
    else:
        S, U, T1, sigmas = cmpr.mlsvd(T, Tsize, R, options)

    if display != 0:
        if prod(array(S.shape) == array(dims)):
            if tol_mlsvd == -1:
                print('    No compression and no truncation requested by user')
                print('    Working with dimensions', dims)
            else:
                print('    No compression detected')
                print('    Working with dimensions', dims)
        else:
            print('    Compression detected')
            print('    Compressing from', dims, 'to', S.shape)
        if display > 2 or display < -1:
            print('    Compression relative error = {:7e}'.format(best_error))
        print()

    # Increase dimensions if r > min(S.shape).
    S_orig_dims = S.shape
    if R > min(S_orig_dims):
        inflate_status = True
        S = cnv.inflate(S, R, S_orig_dims)
    else:
        inflate_status = False

    # For higher order tensors the trunc_dims options is only valid for the original tensor and its MLSVD.
    options.trunc_dims = 0

    # TENSOR TRAIN AND DAMPED GAUSS-NEWTON STAGE

    factors, outputs = highcpd(S, R, options)
    factors = cnv.deflate(factors, S_orig_dims, inflate_status)

    # Use the orthogonal transformations to work in the original space.
    for l in range(L):
        factors[l] = dot(U[l], factors[l])

    # FINAL WORKS

    # Compute error.
    if type(T1) == ndarray:
        T1_approx = empty(T1.shape)
        T1_approx = cnv.cpd2unfold1(T1_approx, factors)
        rel_error = crt.fastnorm(T1, T1_approx) / Tsize

        # Go back to the original dimension ordering.
        factors = aux.unsort_dims(factors, ordering)

    else:
        # Go back to the original dimension ordering.
        factors = aux.unsort_dims(factors, ordering)

        rel_error = crt.sparse_fastnorm(data_orig, idxs_orig, dims_orig,
                                        factors) / Tsize

    num_steps = 0
    for output in outputs:
        num_steps += output.num_steps
    accuracy = max(0, 100 * (1 - rel_error))

    if options.display != 0:
        print()
        print(
            '==============================================================================================='
        )
        print(
            '==============================================================================================='
        )
        print('Final results')
        print('    Number of steps =', num_steps)
        print('    Relative error =', rel_error)
        acc = float('%.6e' % Decimal(accuracy))
        print('    Accuracy = ', acc, '%')

    final_outputs = aux.make_final_outputs(num_steps, rel_error, accuracy,
                                           outputs, options)

    return factors, final_outputs
Beispiel #2
0
def mlsvd(T, Tsize, R, options):
    """
    This function computes a truncated MLSVD of tensors of any order. The output is such that T = (U_1,...,U_L)*S, and
    UT is the list of the transposes of U.
    The parameter n_iter of the randomized SVD is set to 2. It is only good to increase this value when the tensor has
    much noise. Still this issue is addressed by the low rank CPD approximation, so n_iter=2 is enough.

    Inputs
    ------
    T: float array
        Objective tensor in coordinates.
    Tsize: float
        Frobenius norm of T.
    R: int
        An upper bound for the multilinear rank of T. Normally one will use the rank of T.
    options: class with the parameters previously defined.

    Outputs
    -------
    S: float array
        Core tensor of the MLSVD.
    U: list of float 2-D arrays
        List with truncated matrices of the original U.
    T1: float 2-D arrays
        First unfolding of T.
    sigmas: list of float 1-D arrays
        List with truncated arrays of the original sigmas.
    """

    # INITIALIZE RELEVANT VARIABLES.

    sigmas = []
    U = []

    # Verify if T is sparse, in which case it will be given as a list with the data.
    if type(T) == list:
        data, idxs, dims = T
    else:
        dims = T.shape
    L = len(dims)

    # Set options.
    options = aux.make_options(options, L)
    trunc_dims = options.trunc_dims
    display = options.display
    mlsvd_method = options.mlsvd_method
    tol_mlsvd = options.tol_mlsvd
    if type(tol_mlsvd) == list:
        if L > 3:
            tol_mlsvd = tol_mlsvd[0]
        else:
            tol_mlsvd = tol_mlsvd[1]
    gpu = options.gpu
    if gpu:
        import pycuda.gpuarray as gpuarray
        import pycuda.autoinit
        from skcuda import linalg, rlinalg

    # tol_mlsvd = -1 means no truncation and no compression, that is, the original tensor.
    if tol_mlsvd == -1:
        T1 = cnv.unfold(T, 1)
        U = [identity(dims[l]) for l in range(L)]
        sigmas = [ones(dims[l]) for l in range(L)]
        if display > 2 or display < -1:
            return T, U, T1, sigmas, 0.0
        else:
            return T, U, T1, sigmas

    # T is sparse.
    elif type(T) == list:
        for l in range(L):
            Tl = cnv.sparse_unfold(data, idxs, dims, l + 1)
            if l == 0:
                T1 = cnv.sparse_unfold(data, idxs, dims, l + 1)
            mlsvd_method = 'sparse'
            U, sigmas, Vlt, dim = compute_svd(Tl, U, sigmas, dims, R,
                                              mlsvd_method, tol_mlsvd, gpu, L,
                                              l)

        # Compute (U_1^T,...,U_L^T)*T = S.
        new_dims = [U[l].shape[1] for l in range(L)]
        UT = [U[l].T for l in range(L)]
        S = mlinalg.sparse_multilin_mult(UT, data, idxs, new_dims)

    # Compute MLSVD base on sequentially truncated method.
    elif mlsvd_method == 'seq':
        S_dims = copy(dims)
        S = T
        for l in range(L):
            Sl = cnv.unfold(S, l + 1)
            if l == 0:
                T1 = cnv.unfold_C(S, l + 1)
            U, sigmas, Vlt, dim = compute_svd(Sl, U, sigmas, dims, R,
                                              mlsvd_method, tol_mlsvd, gpu, L,
                                              l)

            # Compute l-th unfolding of S truncated at the l-th mode.
            Sl = (Vlt.T * sigmas[-1]).T
            S_dims[l] = dim
            S = empty(S_dims, dtype=float64)
            S = cnv.foldback(S, Sl, l + 1)

    # Compute MLSVD based on classic method.
    elif mlsvd_method == 'classic':
        for l in range(L):
            Tl = cnv.unfold(T, l + 1)
            if l == 0:
                T1 = cnv.unfold_C(T, l + 1)
            U, sigmas, Vlt, dim = compute_svd(Tl, U, sigmas, dims, R,
                                              mlsvd_method, tol_mlsvd, gpu, L,
                                              l)

        # Compute (U_1^T,...,U_L^T)*T = S.
        UT = [U[l].T for l in range(L)]
        S = mlinalg.multilin_mult(UT, T1, dims)

    # Specific truncation is given by the user.
    if type(trunc_dims) == list:
        slices = []
        for l in range(L):
            slices.append(slice(0, trunc_dims[l]))
            if trunc_dims[l] > U[l].shape[1]:
                print('trunc_dims[', l, '] =', trunc_dims[l], 'and U[', l,
                      '].shape =', U[l].shape)
                sys.exit(
                    'Must have trunc_dims[l] <= min(dims[l], R) for all mode l=1...'
                    + str(L))
            U[l] = U[l][:, :trunc_dims[l]]
        S = S[tuple(slices)]

    # Compute error of compressed tensor.
    if display > 2 or display < -1:
        if type(T) == list:
            best_error = mlinalg.compute_error(T, Tsize, S, U, dims)
        else:
            S1 = cnv.unfold(S, 1)
            best_error = mlinalg.compute_error(T, Tsize, S1, U, S.shape)
        return S, U, T1, sigmas, best_error

    return S, U, T1, sigmas