Пример #1
0
def multilin_mult(U, T1, dims):
    """    
    Performs the multilinear multiplication (U[0]^T,...,U[L-1]^T)*T, where dims = T.shape. We need the first unfolding
    T1 of T to start the computations.

    Inputs
    ------
    U: list of 2-D arrays
    T1: 2-D array
        First unfolding of T.
    dims: list of ints
        Dimension of T.

    Outputs
    -------
    S: float array
        S is the resulting multidimensional of the multilinear multiplication (U[0]^T,...,U[L-1]^T)*T.
    """

    L = len(dims)
    # dims_out are the dimensions of the output tensor.
    dims_out = list(dims)

    unfolding1 = T1
    for l in range(L):
        unfolding2 = dot(U[l], unfolding1)
        # Update the current dimension of dims_out.
        dims_out[l] = U[l].shape[0]
        S = empty(dims_out)
        S = cnv.foldback(S, unfolding2, l + 1)
        if l < L - 1:
            unfolding1 = cnv.unfold(S, l + 2)
        else:
            return S
Пример #2
0
def compute_step(Tsize, Tl, T1_approx, factors, orig_factors, data, x, y, inner_parameters, it, old_error):
    """    
    This function uses the chosen inner method to compute the next step.
    """

    # Initialize first variables.
    L = len(factors)
    damp, inner_method, cg_maxiter, cg_factor, cg_tol, tol_jump, symm, factors_norm, fix_mode = inner_parameters
    if type(inner_method) == list:
        inner_method = inner_method[it]

    # Call the inner method.
    if inner_method == 'cg':
        cg_maxiter = 1 + (L-2) * int(cg_factor * randint(1 + it**0.4, 2 + it**0.9))
        y, grad, JT_J_grad, itn, residualnorm = cg(Tl, factors, data, y, damp, cg_maxiter, cg_tol)
        
    elif inner_method == 'cg_static':
        y, grad, JT_J_grad, itn, residualnorm = cg(Tl, factors, data, y, damp, cg_maxiter, cg_tol)

    elif inner_method == 'als':
        factors = als.als_iteration(Tl, factors, fix_mode)
        x = concatenate([factors[l].flatten('F') for l in range(L)])
        y *= 0
        
    elif inner_method == 'direct':
        y, grad, itn, residualnorm = direct(Tl, factors, data, y, damp)

    else:
        sys.exit("Wrong inner method name. Must be 'cg', 'cg_static', 'als' or 'direct'.")

    # Update results.
    x = x + y

    # Balance and transform factors.
    factors = cnv.x2cpd(x, factors)
    factors = cnv.transform(factors, symm, factors_norm)

    # Some mode may be fixed when the bicpd is called.
    if L == 3:
        for l in range(L):
            if fix_mode == l:
                factors[l] = deepcopy(orig_factors[l])

    # Compute error.
    T1_approx = cnv.cpd2unfold1(T1_approx, factors)
    error = crt.fastnorm(Tl[0], T1_approx) / Tsize
    
    # Sometimes the step is too bad and increase the error by much. In this case we discard the computed step and
    # use the DogLeg method to compute the next step.
    if it > 3:
        if inner_method == 'cg' or inner_method == 'cg_static':
            if error > tol_jump * old_error:
                x = x - y
                factors, x, y, error = compute_dogleg_steps(Tsize, Tl, T1_approx, factors, grad, JT_J_grad, x, y, error, inner_parameters)

    if inner_method == 'als':
        return T1_approx, factors, x, y, [nan], '-', Tsize*error, error

    return T1_approx, factors, x, y, grad, itn, residualnorm, error
Пример #3
0
def compute_dogleg_steps(Tsize, Tl, T1_approx, factors, grad, JT_J_grad, x, y, error, inner_parameters):
    """
    Compute Dogleg step.
    """

    count = 0
    best_x = x.copy()
    best_y = y.copy()
    best_error = error
    best_factors = deepcopy(factors)
    gain_ratio = 1
    delta = 1
    
    while gain_ratio > 0:
        # Keep the previous value of x and error to compare with the new ones in the next iteration.
        old_x = x
        old_y = y
        old_error = error
        damp, inner_method, cg_maxiter, cg_factor, cg_tol, tol_jump, symm, factors_norm, fix_mode = inner_parameters
        
        # Apply dog leg method.
        y = dogleg(y, grad, JT_J_grad, delta)
        
        # Update results.
        x = x + y

        # Balance and transform factors.
        factors = cnv.x2cpd(x, factors, eq=False)
        factors = cnv.transform(factors, symm, factors_norm)

        # Compute error.
        T1_approx = cnv.cpd2unfold1(T1_approx, factors)
        error = crt.fastnorm(Tl[0], T1_approx) / Tsize

        # Update gain ratio.
        gain_ratio = update_gain_ratio(damp, old_error, error, Tsize, old_x, x, grad)
       
        if error < old_error:
            best_x = x.copy()
            best_y = y.copy()
            best_error = error
            best_factors = deepcopy(factors)

        # Update delta.
        delta = update_delta(delta, gain_ratio, norm(x - old_x))
                
        count += 1
        if count > 10:
            break
        
    return best_factors, best_x, best_y, best_error
Пример #4
0
def rank1_terms_list(factors):
    """
    Compute each rank 1 term, as a multidimensional array, of the CPD. Let T be the corresponding the tensor, in
    coordinates, of thr CPD given by factors, and let rank1_terms = [T_1, T_2, ..., T_R] be the output of this function.
    Then we have that T_1 + T_2 + ... + T_R = T.

    Inputs
    ------
    factors: list of float 2-D ndarrays with shape (dims[i], R) each
        The CPD factors of some tensor.

    Outputs
    -------
    rank1_terms: list of float ndarrays
        Each tensor rank1_terms[r] is the r-th rank-1 term of the given CPD.
    """

    R = factors[0].shape[1]
    L = len(factors)
    rank1_terms = []

    for r in range(R):
        vectors = []
        for l in range(L):
            # vectors[l] = [w_r^(1),w_r^(2),...,w_r^(L)], which represents w_r^(1) ⊗ w_r^(2) ⊗ ... ⊗ w_r^(L).
            v = factors[l][:, r]
            vectors.append(v.reshape(v.size, 1))
        term = cnv.cpd2tens(vectors)
        rank1_terms.append(term)

    return rank1_terms
Пример #5
0
def multilin_mult_cpd(U, W, dims):
    """    
    Performs the multilinear multiplication (U[0],...,U[L-1])*(W[0], ..., W[L-1])*I = (U[0]*W[0],...,U[L-1]*W[L-1])*I, 
    where I.shape = dims = (W[0].shape[1],...,W[L-1].shape[1]) are the size of the columns of the W's.

    Inputs
    ------
    U: list of 2-D arrays
    W: list of 2-D arrays
    dims: list of ints

    Outputs
    -------
    S: float array
        S is the resulting multidimensional of the mentioned multiplication.
    """

    L = len(dims)
    # dims_out are the dimensions of the output tensor.
    dims_out = []
    W_new = []

    for l in range(L):
        W_new.append(dot(U[l], W[l]))
        dims_out.append(W_new[l].shape[0])

    S = cnv.cpd2tens(W_new)
    return S
Пример #6
0
def multirank_approx(T, multi_rank, options):
    """
    This function computes an approximation of T with multilinear rank = multi_rank. Truncation the core tensor of the
    MLSVD doesn't gives the best low multirank approximation, but gives very good approximations.
    
    Inputs
    ------
    T: float array
    multi_rank: list of int
        The desired low multilinear rank.
        
    Outputs
    -------
    T_approx: float array
        The approximating tensor with multilinear rank = multi_rank.
    """

    # Compute dimensions and norm of T.
    dims = T.shape
    sorted_dims = sort(array(dims))
    L = len(dims)
    Tsize = norm(T)

    # Compute truncated MLSVD of T.
    options = aux.make_options(options, L)
    options.display = 0
    options.trunc_dims = multi_rank
    R_gen = int(ceil(prod(sorted_dims) / (np.sum(sorted_dims) - L + 1)))
    S, U, UT, sigmas = cmpr.mlsvd(T, Tsize, R_gen, options)

    # Construct the corresponding tensor T_approx.
    S1 = cnv.unfold(S, 1)
    T_approx = multilin_mult(U, S1, multi_rank)

    return T_approx
Пример #7
0
def hessian(factors, P1, P2, sum_dims):
    """
    Approximate Hessian matrix of the error function.
    """

    L = len(factors)
    R = factors[0].shape[1]
    dims = [factors[l].shape[0] for l in range(L)]
    H = zeros((R * sum(dims), R * sum(dims)))
    vec_factors = [zeros(R*dims[l]) for l in range(L)]
    fortran_factors = [np.array(factors[l], order='F') for l in range(L)]
    for l in range(L):
        vec_factors[l] = cnv.vec(factors[l], vec_factors[l], dims[l], R) 
        vec_factors[l] = vec_factors[l].reshape(R*dims[l], 1).T
                
    for l in range(L):
        I = identity(dims[l])
        # Block H_{ll}.
        H[sum_dims[l]:sum_dims[l+1], sum_dims[l]:sum_dims[l+1]] = mlinalg.kronecker(P1[l, :, :], I)
        for ll in range(l):              
            I = ones((dims[l], dims[ll]))
            tmp1 = mlinalg.kronecker(P2[l, ll, :, :], I)
            tmp2 = zeros((R*dims[l], R*dims[ll]))
            tmp2 = compute_blocks(tmp2, fortran_factors[l], vec_factors[ll], tuple(dims), R, l, ll)  
            # Blocks H_{l, ll} and H_{ll, l}.
            H[sum_dims[l]:sum_dims[l+1], sum_dims[ll]:sum_dims[ll+1]] = \
                mlinalg.hadamard(tmp1, tmp2, H[sum_dims[l]:sum_dims[l+1], sum_dims[ll]:sum_dims[ll+1]])  
            H[sum_dims[ll]:sum_dims[ll+1], sum_dims[l]:sum_dims[l+1]] = H[sum_dims[l]:sum_dims[l+1], sum_dims[ll]:sum_dims[ll+1]].T               
              
    return H
Пример #8
0
def smart_random(S, dims, R):
    """
    This function generates 1 + int(sqrt(prod(dims))) samples of random possible initializations. The closest to S is
    saved. This method draws R random points in S and generates a tensor with rank <= R from them. The distribution is
    such that it tries to maximize the energy of the sampled tensor, so the error is minimized.
    
    Inputs
    ------
    S: float array
        The core tensor of the MLSVD of T.
    dims: list or tuple
        The dimensions (shape) of S.
    R: int
        The desired rank.
        
    Outputs
    -------
    best_factors: list of float 2D arrays
    """

    # Initialize auxiliary values and arrays.
    dims = array(dims)
    samples = 1 + int(sqrt(prod(dims)))
    best_error = inf
    S1 = cnv.unfold(S, 1)
    Ssize = norm(S)

    # Start search for a good initial point.
    for sample in range(samples):
        init_factors = smart_sample(S, dims, R)
        # Compute error.
        S1_init = empty(S1.shape)
        S1_init = cnv.cpd2unfold1(S1_init, init_factors)
        rel_error = norm(S1 - S1_init) / Ssize
        if rel_error < best_error:
            best_error = rel_error
            best_factors = init_factors.copy()

    return best_factors
Пример #9
0
def matvec(factors, P2, P_VT_W, result, result_tmp, z, A, B, dims, sum_dims):
    """
    Makes the matrix-vector computation (Jf^T * Jf)*v.
    """

    L = len(factors)
    R = factors[0].shape[1]
    
    result_tmp *= 0    
    result_tmp = matvec_inner(A, P2, P_VT_W, result_tmp, L)
                
    for l in range(L):    
        dot(factors[l], result_tmp[l], out=result[l])
        result[l] += B[l]
        z[sum_dims[l]: sum_dims[l+1]] = cnv.vec(result[l], z[sum_dims[l]: sum_dims[l+1]], dims[l], R)

    return z
Пример #10
0
def starting_point(T, Tsize, S, U, R, ordering, options):
    """
    This function generates a starting point to begin the iterations. There are three options:
        - list: the user may give a list with the factor matrices to be used as starting point.
        - 'random': the entries the factor matrices are generated by the normal distribution with mean 0 and variance 1.
        - 'smart_random': generates a random starting point with a method based on the MLSVD which always guarantee a 
           small relative error. Check the function 'smart_random' for more details about this method.
        - 'smart': works similar as the smart_random method, but this one is deterministic and generates the best rank-R
           approximation based on the MLSVD.
    
    Inputs
    ------
    T: float array
    Tsize: float
    S: float array
        The core tensor of the MLSVD of T.
    U: list of float 2D arrays
        Each element of U is a orthogonal matrix of the MLSVD of T.
    R: int
        the desired rank.
    ordering: list of ints
       Since the cpd may change the index ordering, this list may be necessary if the user gives an initialization,
       which will be based on the original ordering.
    options: class
    
    Outputs
    -------
    init_factors: list of float 2D arrays
    """

    # Extract all variable from the class of options.
    initialization = options.initialization
    c = options.factors_norm
    symm = options.symm
    display = options.display
    dims = S.shape
    L = len(dims)

    if type(initialization) == list:
        init_factors = [
            dot(U[l].T, initialization[ordering[l]]) for l in range(L)
        ]

    elif initialization == 'random':
        init_factors = [randn(dims[l], R) for l in range(L)]

    elif initialization == 'smart_random':
        init_factors = smart_random(S, dims, R)

    elif initialization == 'smart':
        init_factors = smart(S, dims, R)

    else:
        sys.exit('Error with init parameter.')

    if type(initialization) != list:

        # Depending on the tensor, the factors may have null entries. We want to avoid that. The solution is to
        # introduce a very small random noise.
        init_factors = clean_zeros(init_factors, dims, R)

        # Make all factors balanced.
        init_factors = cnv.equalize(init_factors, R)

        # Apply additional transformations if requested.
        init_factors = cnv.transform(init_factors, symm, c)

    if display > 2 or display < -1:
        S_init = cnv.cpd2tens(init_factors)
        if type(T) == list:
            rel_error = mlinalg.compute_error(T, Tsize, S_init, U, dims)
        else:
            S1_init = cnv.unfold(S_init, 1)
            rel_error = mlinalg.compute_error(T, Tsize, S1_init, U, dims)
        return init_factors, rel_error

    return init_factors
Пример #11
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
Пример #12
0
def dGN(T, factors, R, options):
    """
    This function uses the Damped Gauss-Newton method to compute an approximation of T with rank R. A starting point to
    initiate the iterations must be given. This point is given by the parameter factors.
    The Damped Gauss-Newton method is an iterative method, updating a point x at each iteration. The last computed x is
    gives an approximate CPD in flat form, and from this we have the components to form the actual CPD.

    Inputs
    ------
    T: float array
    factors: list of 2-D arrays
        The factor matrices used as starting point.
    R: int
        The desired rank of the approximating tensor.
    options: class
        Class with the options. See the Auxiliar module documentation for more information.

    Outputs
    -------
    best_factors: list of 2-D arrays
        The factor matrices of the approximated CPD of T.
    step_sizes: float 1-D array
        Distance between the computed points at each iteration.
    errors: float 1-D array
        Error of the computed approximating tensor at each iteration. 
    improv: float 1-D array
        Improvement of the error at each iteration. More precisely, the difference between the relative error of the
        current iteration and the previous one.
    gradients: float 1-D array
        Gradient of the error function at each iteration.
    stop: 0, 1, 2, 3, 4, 5, 6 or 7
        This value indicates why the dGN function stopped. Below we summarize the cases.
        0: errors[it] < tol. Relative error is small enough.
        1: step_sizes[it] < tol_steps. Steps are small enough.
        2: improv[it] < tol_improv. Improvement in the relative error is small enough.
        3: gradients[it] < tol_grad. Gradient is small enough (infinity norm).
        4: mean(abs(errors[it-k : it] - errors[it-k-1 : it-1]))/Tsize < 10*tol_improv. Average of the last
            k = 1 + int(maxiter/10) relative errors is small enough. Keeping track of the averages is useful when the
            errors improvements are just a little above the threshold for a long time. We want them above the threshold
            indeed, but not too close for a long time.
        5: limit of iterations reached.
        6: dGN diverged.
        7: Average improvement is too small compared to the average error.
        8: no refinement was performed (this is not really a stopping condition, but it is necessary to indicate when
        the program can't give a stopping condition in the refinement stage).
    """

    # INITIALIZE RELEVANT VARIABLES 

    # Extract all relevant variables from the class of options.
    init_damp = options.init_damp
    maxiter = options.maxiter
    tol = options.tol
    tol_step = options.tol_step
    tol_improv = options.tol_improv
    tol_grad = options.tol_grad
    tol_jump = options.tol_jump
    symm = options.symm
    display = options.display
    factors_norm = options.factors_norm
    inner_method = options.inner_method 
    cg_maxiter = options.cg_maxiter 
    cg_factor = options.cg_factor 
    cg_tol = options.cg_tol

    # Verify if some factor should be fixed or not. This only happens when the bicpd function was called.
    L = len(factors)
    fix_mode = -1
    orig_factors = [[] for l in range(L)]
    for l in range(L):
        if type(factors[l]) == list:
            fix_mode = l
            orig_factors[l] = deepcopy(factors[l][0])
            factors[l] = factors[l][0]

    # Set the other variables.
    dims = T.shape
    Tsize = norm(T)
    error = 1
    best_error = inf
    stop = 5
    if type(init_damp) == list:
        damp = init_damp[0]
    else:
        damp = init_damp * mean(np.abs(T))
    const = 1 + int(maxiter / 10)
    
    # The program is encouraged to make more iterations for small problems. 
    if R * sum(dims) <= 100: 
        tol = 0
        tol_step = 0
        tol_improv = 0
        tol_grad = 0

    # INITIALIZE RELEVANT ARRAYS

    x = concatenate([factors[l].flatten('F') for l in range(L)])
    y = zeros(R * sum(dims), dtype=float64)
    step_sizes = zeros(maxiter)
    errors = zeros(maxiter)
    improv = zeros(maxiter)
    gradients = zeros(maxiter)
    best_factors = deepcopy(factors)

    # Prepare data to use in each Gauss-Newton iteration.
    data = prepare_data(dims, R)

    # Compute unfoldings.
    Tl = [cnv.unfold_C(T, l+1) for l in range(L)]
    T1_approx = zeros(Tl[0].shape, dtype=float64)

    if display > 1:
        if display == 4:
            print('   ',
                  '{:^9}'.format('Iteration'),
                  '| {:^11}'.format('Rel error'),
                  '| {:^11}'.format('Step size'),
                  '| {:^11}'.format('Improvement'),
                  '| {:^11}'.format('norm(grad)'),
                  '| {:^11}'.format('Predicted error'),
                  '| {:^10}'.format('# Inner iterations'))
        else:
            print('   ',
                  '{:^9}'.format('Iteration'),
                  '| {:^9}'.format('Rel error'),
                  '| {:^11}'.format('Step size'),
                  '| {:^10}'.format('Improvement'),
                  '| {:^10}'.format('norm(grad)'),
                  '| {:^10}'.format('Predicted error'),
                  '| {:^10}'.format('# Inner iterations'))

            # START GAUSS-NEWTON ITERATIONS

    for it in range(maxiter):
        # Keep the previous value of x and error to compare with the new ones in the next iteration.
        old_x = x
        old_error = error

        # Computation of the Gauss-Newton iteration formula to obtain the new point x + y, where x is the 
        # previous point and y is the new step obtained as the solution of min_y |Ay - b|, with 
        inner_parameters = damp, inner_method, cg_maxiter, cg_factor, cg_tol, tol_jump, symm, factors_norm, fix_mode
        T1_approx, factors, x, y, grad, itn, residualnorm, error = \
            compute_step(Tsize, Tl, T1_approx, factors, orig_factors, data, x, y, inner_parameters, it, old_error)

        # Update gain ratio and damping parameter. 
        gain_ratio = update_gain_ratio(damp, old_error, error, Tsize, old_x, x, grad)
        damp = update_damp(damp, init_damp, gain_ratio, it)

        # Update best solution.
        if error < best_error:
            best_error = error
            best_factors = deepcopy(factors)

        # Save relevant information about the current iteration.
        errors[it] = error
        step_sizes[it] = norm(x - old_x) / norm(old_x)
        gradients[it] = norm(grad, inf)
        if it == 0:
            improv[it] = errors[it]
        else:
            improv[it] = np.abs(errors[it] - errors[it-1])

        # Show information about current iteration.
        if display > 1:
            if display == 4:
                print('    ',
                      '{:^8}'.format(it+1),
                      '| {:^10.5e}'.format(errors[it]),
                      '| {:^10.5e}'.format(step_sizes[it]),
                      '| {:^10.5e}'.format(improv[it]),
                      '| {:^11.5e}'.format(gradients[it]),
                      '| {:^15.5e}'.format(residualnorm),
                      '| {:^16}'.format(itn))
            else:
                print('   ',
                      '{:^9}'.format(it+1),
                      '| {:^9.2e}'.format(errors[it]),
                      '| {:^11.2e}'.format(step_sizes[it]),
                      '| {:^11.2e}'.format(improv[it]),
                      '| {:^10.2e}'.format(gradients[it]),
                      '| {:^15.2e}'.format(residualnorm),
                      '| {:^16}'.format(itn))

        # Stopping conditions.
        if it > 1:
            if errors[it] < tol:
                stop = 0
                break
            if step_sizes[it] < tol_step:
                stop = 1
                break
            if improv[it] < tol_improv:
                stop = 2
                break
            if gradients[it] < tol_grad:
                stop = 3
                break
            if it > 2*const and it % const == 0:
                # Let const=1+int(maxiter/10). Comparing the average errors of const consecutive iterations prevents
                # the program to continue iterating when the error starts to oscillate without decreasing.
                mean1 = mean(errors[it - 2*const: it - const])
                mean2 = mean(errors[it - const: it])
                if mean1 - mean2 <= tol_improv:
                    stop = 4
                    break
                # If the average improvements is too small compared to the average errors, the program stops. 
                mean3 = mean(improv[it - const: it])
                if mean3 < 1e-3 * mean2:
                    stop = 7
                    break
            # Prevent blow ups.
            if error > max(1, Tsize ** 2) / (1e-16 + tol):
                stop = 6
                break

    # SAVE LAST COMPUTED INFORMATION

    errors = errors[0: it+1]
    step_sizes = step_sizes[0: it+1]
    improv = improv[0: it+1]
    gradients = gradients[0: it+1]

    return best_factors, step_sizes, errors, improv, gradients, stop
Пример #13
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
Пример #14
0
def bicpd(T, R, fixed_factor, options):
    """
    Practically the same as tricpd, but this function keeps the some factor fixed during all the computations. This
    function is to be used as part of the tensor train cpd.
    """

    # INITIALIZE RELEVANT VARIABLES

    # Extract all variable from the class of options.
    initialization = options.initialization
    refine = options.refine
    symm = options.symm
    display = options.display
    tol_mlsvd = options.tol_mlsvd
    bi_method = options.bi_method_parameters[0]
    if type(tol_mlsvd) == list:
        tol_mlsvd = tol_mlsvd[1]

    # Set the other variables.
    m, n, p = T.shape
    Tsize = norm(T)
    ordering = [0, 1, 2]

    # Test consistency of dimensions and rank.
    aux.consistency(R, (m, n, p), options)

    # COMPRESSION STAGE

    if display > 0:
        print(
            '-----------------------------------------------------------------------------------------------'
        )
        print('Computing MLSVD of T')

    # Compute compressed version of T with the MLSVD. We have that T = (U1, U2, U3)*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)
    R1, R2, R3 = S.shape
    U1, U2, U3 = U

    # When the tensor is symmetric we want S to have equal dimensions.
    if symm:
        R_min = min(R1, R2, R3)
        R1, R2, R3 = R_min, R_min, R_min
        S = S[:R_min, :R_min, :R_min]
        U1, U2, U3 = U1[:, :R_min], U2[:, :R_min], U3[:, :R_min]

    if display > 0:
        if (R1, R2, R3) == (m, n, p):
            if tol_mlsvd == -1:
                print('    No compression and no truncation requested by user')
                print('    Working with dimensions', T.shape)
            else:
                print('    No compression detected')
                print('    Working with dimensions', T.shape)
        else:
            print('    Compression detected')
            print('    Compressing from', T.shape, 'to', S.shape)
        if display > 2:
            print('    Compression relative error = {:7e}'.format(best_error))

    # GENERATION OF STARTING POINT STAGE

    # Generate initial to start dGN.
    if display > 2 or display < -1:
        [X, Y, Z], init_error = init.starting_point(T, Tsize, S, U, R,
                                                    ordering, options)
    else:
        [X, Y, Z] = init.starting_point(T, Tsize, S, U, R, ordering, options)

    # Discard the factor computed in start_point and use the previous one. Then project it on the compressed space.
    if fixed_factor[1] == 0:
        X = dot(U1.T, fixed_factor[0])
        X = [X, 0]
    elif fixed_factor[1] == 1:
        Y = dot(U2.T, fixed_factor[0])
        Y = [Y, 1]
    elif fixed_factor[1] == 2:
        Z = dot(U3.T, fixed_factor[0])
        Z = [Z, 2]

    if display > 0:
        print(
            '-----------------------------------------------------------------------------------------------'
        )
        if type(initialization) == list:
            print('Type of initialization: fixed + user')
        else:
            print('Type of initialization: fixed +', initialization)
        if display > 2:
            if fixed_factor[1] == 0:
                S_init = cnv.cpd2tens([X[0], Y, Z])
            elif fixed_factor[1] == 1:
                S_init = cnv.cpd2tens([X, Y[0], Z])
            elif fixed_factor[1] == 2:
                S_init = cnv.cpd2tens([X, Y, Z[0]])
            S1_init = cnv.unfold(S_init, 1)
            init_error = mlinalg.compute_error(T, Tsize, S1_init, [U1, U2, U3],
                                               (R1, R2, R3))
            print(
                '    Initial guess relative error = {:5e}'.format(init_error))

    # DAMPED GAUSS-NEWTON STAGE

    if display > 0:
        print(
            '-----------------------------------------------------------------------------------------------'
        )
        print('Computing CPD of T')

    # Compute the approximated tensor in coordinates with dGN or ALS.
    if bi_method == 'als':
        factors, step_sizes_main, errors_main, improv_main, gradients_main, stop_main = \
            als.als(S, [X, Y, Z], R, options)
    else:
        factors, step_sizes_main, errors_main, improv_main, gradients_main, stop_main = \
            gn.dGN(S, [X, Y, Z], R, options)
    X, Y, Z = factors

    # FINAL WORKS

    # Use the orthogonal transformations to obtain the CPD of T.
    if fixed_factor[1] == 0:
        Y = dot(U2, Y)
        Z = dot(U3, Z)
    elif fixed_factor[1] == 1:
        X = dot(U1, X)
        Z = dot(U3, Z)
    elif fixed_factor[1] == 2:
        X = dot(U1, X)
        Y = dot(U2, Y)

    # Compute error.
    T1_approx = empty(T1.shape)
    if fixed_factor[1] == 0:
        T1_approx = cnv.cpd2unfold1(T1_approx, [fixed_factor[0], Y, Z])
    elif fixed_factor[1] == 1:
        T1_approx = cnv.cpd2unfold1(T1_approx, [X, fixed_factor[0], Z])
    elif fixed_factor[1] == 2:
        T1_approx = cnv.cpd2unfold1(T1_approx, [X, Y, fixed_factor[0]])

    # Save and display final information.
    step_sizes_refine = array([0])
    errors_refine = array([0])
    improv_refine = array([0])
    gradients_refine = array([0])
    stop_refine = 5
    output = aux.output_info(T1, Tsize, T1_approx, step_sizes_main,
                             step_sizes_refine, errors_main, errors_refine,
                             improv_main, improv_refine, gradients_main,
                             gradients_refine, stop_main, stop_refine, options)

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

    return X, Y, Z, output
Пример #15
0
def tricpd(T, R, options):
    """
    Given a tensor T and a rank R, this function computes an approximated CPD of T with rank R. This function is called
    when the user sets method = 'dGN'.

    Inputs
    ------
    T: float array
    R: int
    options: class
    
    Outputs
    -------
    factors: list of float 2D arrays
    output: class
        This class contains all information needed about the computations made. We summarize these information below.
            num_steps: the total number of steps (iterations) the dGN function used at the two runs.
            accuracy: the accuracy of the solution, which is defined by the formula 100*(1 - rel_error). 0 means 0% of 
                      accuracy (worst case) and 100 means 100% of accuracy (best case).
            rel_error: relative error |T - T_approx|/|T| of the approximation computed. 
            step_sizes: array with the distances between consecutive computed points at each iteration.
            errors: array with the absolute errors of the approximating tensor at each iteration.
            improv: array with the differences between consecutive absolute errors.
            gradients: array with the gradient of the error function at each iteration. We expect that these gradients 
                       converges to zero as we keep iterating since the objective point is a local minimum.
            stop: it is a list of two integers. The first integer indicates why the dGN stopped at the first run, and
                  the second integer indicates why the dGN stopped at the second run (refinement stage). Check the 
                  functions mlsvd and dGN for more information. 
    """

    # INITIALIZE RELEVANT VARIABLES

    # 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)
        dims_orig = T_orig[2]
    else:
        dims_orig = T.shape
    L = len(dims_orig)

    # Set options.
    initialization = options.initialization
    refine = options.refine
    symm = options.symm
    display = options.display
    tol_mlsvd = options.tol_mlsvd
    method = options.method
    if type(tol_mlsvd) == list:
        tol_mlsvd = tol_mlsvd[0]

    # 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:
            tol_mlsvd = 1e-16
            if type(tol_mlsvd) == list:
                options.tol_mlsvd[0] = 1e-16
            else:
                options.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)
    dims_cmpr = S.shape

    # When the tensor is symmetric we want S to have equal dimensions.
    if symm:
        R_min = min(dims_cmpr)
        dims_cmpr = [R_min for l in range(L)]
        dims_cmpr_slices = tuple(slice(R_min) for l in range(L))
        S = S[dims_cmpr_slices]
        U = [U[l][:, :R_min] for l in range(L)]

    if display > 0:
        if dims_cmpr == 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:
            print('    Compression relative error = {:7e}'.format(best_error))

    # GENERATION OF STARTING POINT STAGE

    # Generate initial to start dGN.
    if display > 2 or display < -1:
        init_factors, init_error = init.starting_point(T, Tsize, S, U, R,
                                                       ordering, options)
    else:
        init_factors = init.starting_point(T, Tsize, S, U, R, ordering,
                                           options)

    if display > 0:
        print(
            '-----------------------------------------------------------------------------------------------'
        )
        if type(initialization) == list:
            print('Type of initialization: user')
        else:
            print('Type of initialization:', initialization)
        if display > 2:
            print(
                '    Initial guess relative error = {:5e}'.format(init_error))

    # DAMPED GAUSS-NEWTON STAGE

    if display > 0:
        print(
            '-----------------------------------------------------------------------------------------------'
        )
        print('Computing CPD')

    # Compute the approximated tensor in coordinates with dGN or ALS.
    if method == 'als':
        factors, step_sizes_main, errors_main, improv_main, gradients_main, stop_main = \
            als.als(S, init_factors, R, options)
    else:
        factors, step_sizes_main, errors_main, improv_main, gradients_main, stop_main = \
            gn.dGN(S, init_factors, R, options)

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

    # REFINEMENT STAGE

    # If T is sparse, no refinement is made.
    if type(T) == list:
        refine = False

    if refine:
        if display > 0:
            print()
            print(
                '==============================================================================================='
            )
            print('Computing refinement of solution')

        if display > 2:
            T1_approx = empty(T1.shape)
            T1_approx = cnv.cpd2unfold1(T1_approx, factors)
            init_error = crt.fastnorm(T1, T1_approx) / Tsize
            print(
                '    Initial guess relative error = {:5e}'.format(init_error))

        if display > 0:
            print(
                '-----------------------------------------------------------------------------------------------'
            )
            print('Computing CPD')

        if method == 'als':
            factors, step_sizes_refine, errors_refine, improv_refine, gradients_refine, stop_refine = \
                als.als(T, factors, R, options)
        else:
            factors, step_sizes_refine, errors_refine, improv_refine, gradients_refine, stop_refine = \
                gn.dGN(T, factors, R, options)

    else:
        step_sizes_refine = array([0])
        errors_refine = array([0])
        improv_refine = array([0])
        gradients_refine = array([0])
        stop_refine = 8

    # FINAL WORKS

    # Compute error.
    if type(T1) == ndarray:
        T1_approx = empty(T1.shape)
        T1_approx = cnv.cpd2unfold1(T1_approx, factors)

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

        # Save and display final informations.
        output = aux.output_info(T1, Tsize, T1_approx, step_sizes_main,
                                 step_sizes_refine, errors_main, errors_refine,
                                 improv_main, improv_refine, gradients_main,
                                 gradients_refine, stop_main, stop_refine,
                                 options)
    else:
        # Go back to the original dimension ordering.
        factors = aux.unsort_dims(factors, ordering)

        # Save and display final informations.
        output = aux.output_info(T_orig, Tsize, factors, step_sizes_main,
                                 step_sizes_refine, errors_main, errors_refine,
                                 improv_main, improv_refine, gradients_main,
                                 gradients_refine, stop_main, stop_refine,
                                 options)

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

    return factors, output
Пример #16
0
def highcpd(T, R, options):
    """
    This function makes the calls in order to compute the tensor train of T and obtain the final CPD from it. It is 
    important to realize that this function is limited to tensor where each one of its factors is a full rank matrix. 
    In particular, the rank R must be smaller than all dimensions of T.
    """

    # Create relevant values.
    dims = T.shape
    L = len(dims)
    display = options.display
    max_trials = options.trials
    options.refine = False
    epochs = options.epochs

    # Compute cores of the tensor train of T.
    G = cpdtt(T, R)
    if display > 2 or display < -1:
        print(
            '==============================================================================================='
        )
        print('SVD Tensor train error = ', aux.tt_error(T, G, dims, L))
        print(
            '==============================================================================================='
        )
        print()

    if display != 0:
        print('Total of', L - 2, 'third order CPDs to be computed:')
        print(
            '==============================================================================================='
        )

    cpd_list, outputs, best_Z = aux.cpd_cores(G, max_trials, epochs, R,
                                              display, options)

    # Compute of factors of T.

    # First factor
    factors = [dot(G[0], cpd_list[0][0])]
    # Factors 2 to L-2.
    for l in range(0, L - 2):
        factors.append(cpd_list[l][1])
    B = dot(G[-1].T, best_Z)
    factors.append(B)
    factors = cnv.equalize(factors, R)

    if display > 2 or display < -1:
        G_approx = [G[0]]
        for l in range(1, L - 1):
            temp_factors = cpd_list[l - 1]
            temp_dims = temp_factors[0].shape[0], temp_factors[1].shape[
                0], temp_factors[2].shape[0],
            T_approx = cnv.cpd2tens(temp_factors)
            G_approx.append(T_approx)
        G_approx.append(G[-1])
        print()
        print(
            '==============================================================================================='
        )
        print('CPD Tensor train error = ', aux.tt_error(T, G_approx, dims, L))
        print(
            '==============================================================================================='
        )

    return factors, outputs
Пример #17
0
def als(T, factors, R, options):
    """
    This function uses the ALS method to compute an approximation of T with rank R. 

    Inputs
    ------
    T: float array
    factors: list of float 2-D array
        The factor matrices to be used as starting point.
    R: int. 
        The desired rank of the approximating tensor.
    options: class
        See the function cpd for more information about the options available.
    
    Outputs
    -------
    factors: list of float 2-D array
        The factor matrices of the CPD of T.
    step_sizes: float 1-D array
        Distance between the computed points at each iteration.
    errors: float 1-D array
        Error of the computed approximating tensor at each iteration. 
    improv: float 1-D array
        Improvement of the error at each iteration. More precisely, the difference between the relative error of the
        current iteration and the previous one.
    gradients: float 1-D array
        Gradient of the error function at each iteration.
    stop: 0, 1, 2, 3, 4, 5, 6 or 7
        This value indicates why the function stopped. See the function dGN for more details.
    """

    # INITIALIZE RELEVANT VARIABLES

    # Extract all relevant variables from the class of options.
    maxiter = options.maxiter
    tol = options.tol
    tol_step = options.tol_step
    tol_improv = options.tol_improv
    tol_grad = options.tol_grad
    symm = options.symm
    display = options.display
    factors_norm = options.factors_norm

    # Verify if some factor should be fixed or not. This only happens when the bicpd function was called.
    L = len(factors)
    fix_mode = -1
    orig_factors = [[] for l in range(L)]
    for l in range(L):
        if type(factors[l]) == list:
            fix_mode = l
            orig_factors[l] = factors[l][0].copy()
            factors[l] = factors[l][0]

    # Set the other variables.
    Tsize = norm(T)
    error = 1
    best_error = inf
    stop = 5
    const = 1 + int(maxiter / 10)

    # INITIALIZE RELEVANT ARRAYS

    x = concatenate([factors[l].flatten('F') for l in range(L)])
    step_sizes = empty(maxiter)
    errors = empty(maxiter)
    improv = empty(maxiter)
    gradients = empty(maxiter)
    best_factors = [copy(factors[l]) for l in range(L)]

    # Compute unfoldings.
    Tl = [cnv.unfold(T, l + 1) for l in range(L)]
    T1_approx = empty(Tl[0].shape, dtype=float64)

    if display > 1:
        if display == 4:
            print('   ', '{:^9}'.format('Iteration'),
                  '| {:^11}'.format('Rel error'),
                  '| {:^11}'.format('Step size'),
                  '| {:^11}'.format('Improvement'),
                  '| {:^11}'.format('norm(grad)'))
        else:
            print('   ', '{:^9}'.format('Iteration'),
                  '| {:^9}'.format('Rel error'),
                  '| {:^11}'.format('Step size'),
                  '| {:^10}'.format('Improvement'),
                  '| {:^10}'.format('norm(grad)'))

    # START ALS ITERATIONS

    for it in range(maxiter):
        # Keep the previous value of x and error to compare with the new ones in the next iteration.
        old_x = x
        old_error = error

        # ALS iteration call.
        factors = als_iteration(Tl, factors, fix_mode)
        x = concatenate([factors[l].flatten('F') for l in range(L)])

        # Transform factors.
        factors = cnv.transform(factors, symm, factors_norm)
        # Some mode may be fixed when the bicpd is called.
        if L == 3:
            for l in range(L):
                if fix_mode == l:
                    factors[l] = copy(orig_factors[l])

        # Compute error.
        T1_approx = cnv.cpd2unfold1(T1_approx, factors)
        error = crt.fastnorm(Tl[0], T1_approx) / Tsize

        # Update best solution.
        if error < best_error:
            best_error = error
            for l in range(L):
                best_factors[l] = copy(factors[l])

        # Save relevant information about the current iteration.
        step_sizes[it] = norm(x - old_x)
        errors[it] = error
        gradients[it] = np.abs(old_error - error) / step_sizes[it]
        if it == 0:
            improv[it] = errors[it]
        else:
            improv[it] = np.abs(errors[it - 1] - errors[it])

        # Show information about current iteration.
        if display > 1:
            if display == 4:
                print('    ', '{:^8}'.format(it + 1),
                      '| {:^10.5e}'.format(errors[it]),
                      '| {:^10.5e}'.format(step_sizes[it]),
                      '| {:^10.5e}'.format(improv[it]),
                      '| {:^11.5e}'.format(gradients[it]))
            else:
                print('   ', '{:^9}'.format(it + 1),
                      '| {:^9.2e}'.format(errors[it]),
                      '| {:^11.2e}'.format(step_sizes[it]),
                      '| {:^11.2e}'.format(improv[it]),
                      '| {:^10.2e}'.format(gradients[it]))

        # Stopping conditions.
        if it > 1:
            if errors[it] < tol:
                stop = 0
                break
            if step_sizes[it] < tol_step:
                stop = 1
                break
            if improv[it] < tol_improv:
                stop = 2
                break
            if gradients[it] < tol_grad:
                stop = 3
                break
            if it > 2 * const and it % const == 0:
                # Let const=1+int(maxiter/10). Comparing the average errors of const consecutive iterations prevents
                # the program to continue iterating when the error starts to oscillate without decreasing.
                mean1 = mean(errors[it - 2 * const:it - const])
                mean2 = mean(errors[it - const:it])
                if mean1 - mean2 <= tol_improv:
                    stop = 4
                    break
                # If the average improvements is too small compared to the average errors, the program stops.
                mean3 = mean(improv[it - const:it])
                if mean3 < 1e-3 * mean2:
                    stop = 7
                    break
            # Prevent blow ups.
            if error > max(1, Tsize**2) / (1e-16 + tol):
                stop = 6
                break

    # SAVE LAST COMPUTED INFORMATION

    errors = errors[0:it + 1]
    step_sizes = step_sizes[0:it + 1]
    improv = improv[0:it + 1]
    gradients = gradients[0:it + 1]

    return factors, step_sizes, errors, improv, gradients, stop
Пример #18
0
def test_truncation(T,
                    trunc_list,
                    display=True,
                    n_iter=2,
                    power_iteration_normalizer='none'):
    """
    This function test one or several possible truncations for the MLSVD of T, showing the  error of the truncations. It
    is possible to accomplish the same results calling the function mlsvd with display=3 but this is not advisable since
    each call recomputes the same unfolding SVD's.
    The variable trunc_list must be a list of truncations. Even if it is only one truncation, it must be a list with one
    truncation only.
    """

    # Set the main variables about T.
    dims = T.shape
    L = len(dims)
    Tsize = norm(T)

    # Transform list into array and get the maximum for each dimension.
    max_trunc_dims = np.max(array(trunc_list), axis=0)

    # Compute truncated SVD of all unfoldings of T.
    sigmas = []
    U = []
    T1 = empty((dims[0], prod(dims) // dims[0]), dtype=float64)
    for l in range(L):
        Tl = cnv.unfold(T, l + 1)
        if l == 0:
            T1 = cnv.unfold_C(T, l + 1)
        low_rank = min(dims[l], max_trunc_dims[l])
        Ul, sigma_l, Vlt = rand_svd(
            Tl,
            low_rank,
            n_iter=n_iter,
            power_iteration_normalizer=power_iteration_normalizer)
        sigmas.append(sigma_l)
        U.append(Ul)

    # Save errors in a list.
    trunc_error = []

    # Truncated MLSVD.
    for trunc in trunc_list:
        # S, U and UT truncated.
        current_dims = trunc
        current_U = []
        current_sigmas = []
        for l in range(L):
            current_U.append(U[l][:, :current_dims[l]])
            current_sigmas.append(sigmas[l][:current_dims[l]])
        current_UT = [current_U[l].T for l in range(L)]
        S = mlinalg.multilin_mult(current_UT, T1, dims)

        # Error of truncation.
        S1 = cnv.unfold(S, 1)
        current_error = mlinalg.compute_error(T, Tsize, S1, current_U,
                                              current_dims)
        trunc_error.append(current_error)

        # Display results.
        if display:
            print('Truncation:', current_dims)
            print('Error:', current_error)
            print()

    return trunc_error