Exemple #1
0
def generateComplex(I1, In, Jm, X_mode=None, Y_mode=2, R=5, L=7, snr=10):
    if isinstance(In, tuple) or isinstance(In, list):
        X_mode = len(In) + 1
    elif X_mode is None:
        assert 1, "X_mode should not be set to None if In is an int"
    else:
        In = (X_mode - 1) * [In]

    if isinstance(Jm, tuple) or isinstance(Jm, list):
        Y_mode = len(Jm) + 1
    elif X_mode is None:
        assert 1, "Y_mode should not be set to None if Jn is an int"
    else:
        Jm = (Y_mode - 1) * [Jm]

    T = tl.tensor(np.random.normal(size=(R, I1)))
    P, Q = [], []
    for i in range(X_mode - 1):
        P.append(tl.tensor(np.random.normal(size=(In[i], L)).T))
    for i in range(Y_mode - 1):
        Q.append(tl.tensor(np.random.normal(size=(Jm[i], L)).T))

    E = tl.tensor(np.random.normal(size=[I1] + list(In)))
    F = tl.tensor(np.random.normal(size=[I1] + list(Jm)))
    D = tl.tensor(np.random.normal(size=[R] + [L] * (Y_mode - 1)))
    G = tl.tensor(np.random.normal(size=[R] + [L] * (X_mode - 1)))

    data = multi_mode_dot(G, [T] + P, np.arange(X_mode), transpose=True)
    target = multi_mode_dot(D, [T] + Q, np.arange(Y_mode), transpose=True)

    epsilon = 1 / (10 ** (snr / 10))
    data = data + epsilon * E
    target = target + epsilon * F

    return data, target
 def fit(self, X, Y, lambda_regula, get_history=True):
     """
     Fitting model with a list of X and Y
     X: A list of predictor tensors
     Y: A list of response tensors
     X and Y should have the same length
     """
     temp = T_B_Regression(X,
                           Y,
                           lambda_regula,
                           U_init=None,
                           eta=self.eta,
                           maxiter=self.maxiter,
                           get_history=get_history)
     if get_history:
         self.U_fac, self.error_history = temp["para"], temp["err_hist"]
     else:
         self.U_fac = temp["para"]
     predict_train = [
         np.sign(multi_mode_dot(tensor, self.U_fac)) for tensor in X
     ]
     self.MSE_Train = sum([
         zero_one_loss(predY.flatten(), trueY.flatten(), normalize=True)
         for (predY, trueY) in zip(predict_train, Y)
     ]) / len(Y)
     return
Exemple #3
0
    def transform(self, x):
        """Perform dimension reduction on X

        Args:
            x (array-like tensor): Data to perform dimension reduction, shape (n_samples, I_1, I_2, ..., I_N).

        Returns:
            array-like tensor:
                Projected data in lower dimension, shape (n_samples, P_1, P_2, ..., P_N) if self.return_vector==False.
                If self.return_vector==True, features will be sorted based on their explained variance ratio, shape
                (n_samples, P_1 * P_2 * ... * P_N) if self.n_components is None, and shape (n_samples, n_components)
                if self.n_component is a valid integer.
        """
        _check_tensor_dim_shape(x, self.n_dims, self.shape_in)
        x = x - self.mean_

        # projected tensor in lower dimensions
        x_projected = multi_mode_dot(x,
                                     self.proj_mats,
                                     modes=[m for m in range(1, self.n_dims)])

        if self.return_vector:
            x_projected = unfold(x_projected, mode=0)
            x_projected = x_projected[:, self.idx_order]
            if isinstance(self.n_components, int):
                if self.n_components > np.prod(self.shape_out):
                    self.n_components = np.prod(self.shape_out)
                    warn_msg = "n_components exceeds the maximum number, all features will be returned."
                    logging.warning(warn_msg)
                    warnings.warn(warn_msg)
                x_projected = x_projected[:, :self.n_components]

        return x_projected
def get_Binary_FactorU(Tx, Ty, U_list, mode_j, I_j, lambda_regula):
    """
    Estimate current factor matrix U with L2 penalty in Binary detection model;
    It is for one-time estimation only
    Tx: Input Tensor
    Ty: Response Tensor
    mode_j: j-th mode
    I_j: j-th mode dimension
    lambda_regula: regularization parameter for the L2 penalty

    Notice that in Binary case, both Tx and Ty are single tensor
    """
    Phi = Phi_mode(Ty, mode_j)
    temptensor = Tx[0]
    C_prod = tl.unfold(multi_mode_dot(temptensor, U_list), mode_j)
    res = np.multiply(Phi.T, C_prod)
    a = np.shape(res)
    for i in range(a[0]):
        for j in range(a[1]):
            if res[i, j] >= 1:
                Phi[j,
                    i] = 0  # We want to change elements in Phi.T, so have to reverse index


#   Forcing phi_{ij} to be zero, we can actually remove the gradient coming from non_sv loss
    G = F_MM(Tx, mode_j, U_list)
    Iden = np.diag(np.ones(I_j))
    leftMM = np.dot(G, G.T) + 0.5 * lambda_regula * Iden
    LMM = np.linalg.pinv(leftMM)
    RMM = np.dot(G, Phi)
    U_new = np.dot(LMM, RMM)
    return U_new.T
Exemple #5
0
    def fit(self, X):
        """
        Parameter:
            X: array-like, ndarray of shape (I1, I2, ..., n_samples)
        ----------
        Return:
            self
        """
        dim_in = X.shape
        n_spl = dim_in[-1]
        self.n_dim = X.ndim
        self.Xmean = np.mean(X, axis=-1)
        X_ = np.zeros(X.shape)
        # init
        Phi = dict()
        Us = dict()  # eigenvectors
        Vs = dict()  # eigenvalues
        cums = dict()  # cumulative distribution of eigenvalues
        tPs = []
        for i in range(n_spl):
            X_[..., i] = X[..., i] - self.Xmean
            for j in range(self.n_dim - 1):
                X_i = unfold(X_[..., i], mode=j)
                if j not in Phi:
                    Phi[j] = 0
                Phi[j] = Phi[j] + np.dot(X_i, X_i.T)

        for i in range(self.n_dim - 1):
            eig_vals, eig_vecs = np.linalg.eig(Phi[i])
            idx_sorted = eig_vals.argsort()[::-1]
            Us[i] = eig_vecs[:, idx_sorted]
            Vs[i] = eig_vals[idx_sorted]
            sum_ = np.sum(Vs[i])
            for j in range(Vs[i].shape[0] - 1, 0, -1):
                if np.sum(Vs[i][j:]) / sum_ > (1 - self.var_exp):
                    cums[i] = j + 1
                    break
            tPs.append(Us[i][:, :cums[i]].T)

        for i_iter in range(self.max_iter):
            Phi = dict()
            for i in range(self.n_dim - 1):
                if i not in Phi:
                    Phi[i] = 0
                for j in range(n_spl):
                    X_i = X_[..., j]
                    Xi_ = multi_mode_dot(
                        X_i, [tPs[m] for m in range(self.n_dim - 1) if m != i],
                        modes=[m for m in range(self.n_dim - 1) if m != i])
                    tXi = unfold(Xi_, i)
                    Phi[i] = np.dot(tXi, tXi.T) + Phi[i]

                eig_vals, eig_vecs = np.linalg.eig(Phi[i])
                idx_sorted = eig_vals.argsort()[::-1]
                tPs[i] = eig_vecs[:, idx_sorted]
                tPs[i] = tPs[i][:, :cums[i]].T
        self.tPs = tPs

        return self
    def forward(self, x):
        """Performs a forward pass"""
        x = tenalg.multi_mode_dot(
            x, self.factors, modes=self.contraction_modes)

        if self.bias is not None:
            return x + self.bias
        else:
            return x
def get_ObjVal(Tx, Ty, U_fac, lambda_regula, cla=False):
    """
    Calculate the objective function value
    """
    ans = 0.5 * lambda_regula * sum([np.linalg.norm(U)**2 for U in U_fac])
    if cla:
        res = 0
        for x, y in zip(Tx, Ty):
            temp_res = 1 - np.multiply(y, multi_mode_dot(x, U_fac)).flatten()
            res += sum([i**2 for i in temp_res if i > 0])
        res = res / len(Ty)
        return ans + res
    else:
        res = sum([
            np.linalg.norm(y, multi_mode_dot(x, U_fac))**2
            for (x, y) in zip(Tx, Ty)
        ]) / len(Ty)
        return ans + res
def tucker_trl(x, weight, project_input=False, bias=None):
    n_input = tl.ndim(x) - 1
    if project_input:
        x = tenalg.multi_mode_dot(x,
                                  weight.factors[:n_input],
                                  modes=range(1, n_input + 1),
                                  transpose=True)
        regression_weights = tenalg.multi_mode_dot(weight.core,
                                                   weight.factors[n_input:],
                                                   modes=range(
                                                       n_input, weight.order))
    else:
        regression_weights = weight.to_tensor()

    if bias is None:
        return tenalg.inner(x, regression_weights, n_modes=tl.ndim(x) - 1)
    else:
        return tenalg.inner(x, regression_weights,
                            n_modes=tl.ndim(x) - 1) + bias
Exemple #9
0
 def inverse_transform(self, X):
     """
     Parameter:
         X: array-like, shape (i1, i2, ..., n_samples)
     ----------
     Return:
         Data in original shape, array-like, shape (I1, I2, ..., n_samples)
     """
     return multi_mode_dot(X,
                           self.tPs,
                           modes=[m for m in range(self.n_dim - 1)],
                           transpose=True)
Exemple #10
0
 def transform(self, X):
     """
     Parameter:
         X: array-like, shape (I1, I2, ..., n_samples)
     ----------
     Return:
         Transformed data, array-like, shape (i1, i2, ..., n_samples)
     """
     n_spl = X.shape[-1]
     for i in range(n_spl):
         X[..., i] = X[..., i] - self.Xmean
     return multi_mode_dot(X,
                           self.tPs,
                           modes=[m for m in range(self.n_dim - 1)])
 def predict(self, newX, newY=None):
     """
     Make Prediction basing on fitted Tucker model
     NewX: list of new observed tensor X
     Tucker_Model: A Tucker_Regression object
     """
     pred = [np.sign(multi_mode_dot(tensor, self.U_fac)) for tensor in newX]
     if newY:
         loss = sum([
             zero_one_loss(predY.flatten(), Y.flatten(), normalize=True)
             for (predY, Y) in zip(pred, newY)
         ]) / len(newY)
         return {"Prediction": pred, "MSE": loss}
     else:
         return {"Prediction": pred}
 def predict(self, newX, newY=None):
     """
     Make Prediction basing on fitted Tucker model
     NewX: list of new observed tensor X
     Tucker_Model: A Tucker_Regression object
     """
     pred = [multi_mode_dot(tensor, self.U_fac) for tensor in newX]
     if newY:
         loss = sum([
             np.linalg.norm((predY - Y))**2
             for (predY, Y) in zip(pred, newY)
         ]) / len(newY)
         return {"Prediction": pred, "MSE": loss}
     else:
         return {"Prediction": pred}
Exemple #13
0
def mach_td(X, rank, p):
    X_s = np.zeros(X.shape).flatten()

    for idx, v in enumerate(X.flatten()):
        coinToss = random.uniform(0,1)        
        if coinToss <= p:
            X_s[idx] = v/p

    X_s = X_s.reshape(X.shape)
    factors = []
    for i in range(X.ndim):
        if rank[i] < X.shape[i]:
           A_s = sa.csr_matrix(tensorly.unfold(X_s,i))
           #print(A_s.nnz)
           factors.append(sla.svds(A_s, k=rank[i], return_singular_vectors='u')[0])
        else:
            U, _, _ = la.svd(tensorly.unfold(X_s,i), full_matrices=False)
            factors.append(U)

    core = ta.multi_mode_dot(X_s,factors, transpose=True)
    return core, factors
Exemple #14
0
def test_mpca_against_baseline():
    x = gait["fea3D"].transpose((3, 0, 1, 2))
    baseline_proj_mats = [
        baseline_model["tUs"][i][0] for i in range(baseline_model["tUs"].size)
    ]
    mpca = MPCA(var_ratio=0.97)
    x_proj = mpca.fit(x).transform(x)
    baseline_proj_x = multi_mode_dot(x, baseline_proj_mats, modes=[1, 2, 3])
    # check whether the output shape is consistent with the baseline output by keeping the same variance ratio 97%
    testing.assert_equal(x_proj.shape, baseline_proj_x.shape)

    for i in range(x.ndim - 1):
        # check whether each eigen-vector column is equal to/opposite of corresponding baseline eigen-vector column
        for j in range(baseline_proj_mats[i].shape[0]):
            # subtraction of eigen-vector columns
            eig_col_sub = mpca.proj_mats[i][j:] - baseline_proj_mats[i][j:]
            # sum of eigen-vector columns
            eig_col_sum = mpca.proj_mats[i][j:] + baseline_proj_mats[i][j:]
            compare_ = np.multiply(eig_col_sub, eig_col_sum)
            # plus one for avoiding inf relative difference
            testing.assert_allclose(compare_ + 1, np.ones(compare_.shape))
Exemple #15
0
def test_mpca_against_baseline(gait, baseline_model):
    x = gait["fea3D"].transpose((3, 0, 1, 2))
    baseline_proj_mats = [
        baseline_model["tUs"][i][0] for i in range(baseline_model["tUs"].size)
    ]
    baseline_mean = baseline_model["TXmean"]
    mpca = MPCA(var_ratio=0.97)
    x_proj = mpca.fit(x).transform(x)
    testing.assert_allclose(baseline_mean, mpca.mean_)
    baseline_proj_x = multi_mode_dot(x - baseline_mean,
                                     baseline_proj_mats,
                                     modes=[1, 2, 3])
    # check whether the output embeddings is close to the baseline output by keeping the same variance ratio 97%
    testing.assert_allclose(x_proj**2, baseline_proj_x**2, rtol=relative_tol)
    # testing.assert_equal(x_proj.shape, baseline_proj_x.shape)

    for i in range(x.ndim - 1):
        # check whether each eigen-vector column is equal to/opposite of corresponding baseline eigen-vector column
        # testing.assert_allclose(abs(mpca.proj_mats[i]), abs(baseline_proj_mats[i]))
        testing.assert_allclose(mpca.proj_mats[i]**2,
                                baseline_proj_mats[i]**2,
                                rtol=relative_tol)
Exemple #16
0
    def inverse_transform(self, x):
        """Reconstruct projected data to the original shape and add the estimated mean back

        Args:
            x (array-like tensor): Data to be reconstructed, shape (n_samples, P_1, P_2, ..., P_N), if
                self.return_vector == False, where P_1, P_2, ..., P_N are the reduced dimensions of of corresponding
                mode (1, 2, ..., N), respectively. If self.return_vector == True, shape (n_samples, self.n_components)
                or shape (n_samples, P_1 * P_2 * ... * P_N).

        Returns:
            array-like tensor:
                Reconstructed tensor in original shape, shape (n_samples, I_1, I_2, ..., I_N)
        """
        # reshape x to tensor in shape (n_samples, self.shape_out) if x has been unfolded
        if x.ndim <= 2:
            if x.ndim == 1:
                # reshape x to a 2D matrix (1, n_components) if x in shape (n_components,)
                x = x.reshape((1, -1))
            n_samples = x.shape[0]
            n_features = x.shape[1]
            if n_features <= np.prod(self.shape_out):
                x_ = np.zeros((n_samples, np.prod(self.shape_out)))
                x_[:, self.idx_order[:n_features]] = x[:]
            else:
                msg = "Feature dimension exceeds the shape upper limit."
                logging.error(msg)
                raise ValueError(msg)

            x = fold(x_, mode=0, shape=((n_samples, ) + self.shape_out))

        x_rec = multi_mode_dot(x,
                               self.proj_mats,
                               modes=[m for m in range(1, self.n_dims)],
                               transpose=True)

        x_rec = x_rec + self.mean_

        return x_rec
    def __getitem__(self, indices):
        if isinstance(indices, int):
            # Select one dimension of one mode
            mixing_factor, *factors = self.factors
            core = tenalg.mode_dot(self.core, mixing_factor[indices, :], 0)
            return core, factors

        elif isinstance(indices, slice):
            mixing_factor, *factors = self.factors
            factors = [mixing_factor[indices, :], *factors]
            return self.__class__(self.core, factors)

        else:
            # Index multiple dimensions
            modes = []
            factors = []
            factors_contract = []
            for i, (index, factor) in enumerate(zip(indices, self.factors)):
                if index is Ellipsis:
                    raise ValueError(
                        f'Ellipsis is not yet supported, yet got indices={indices}, indices[{i}]={index}.'
                    )
                if isinstance(index, int):
                    modes.append(i)
                    factors_contract.append(factor[index, :])
                else:
                    factors.append(factor[index, :])

            core = tenalg.multi_mode_dot(self.core,
                                         factors_contract,
                                         modes=modes)
            factors = factors + self.factors[i + 1:]

            if factors:
                return self.__class__(core, factors)

            # Fully contracted tensor
            return core
Exemple #18
0
    def _fit(self, x):
        """Solve MPCA"""

        shape_ = x.shape  # shape of input data
        n_dims = x.ndim

        self.shape_in = shape_[1:]
        self.mean_ = np.mean(x, axis=0)
        x = x - self.mean_

        # init
        shape_out = ()
        proj_mats = []

        # get the output tensor shape based on the cumulative distribution of eigen values for each mode
        for i in range(1, n_dims):
            mode_data_mat = unfold(x, mode=i)
            singular_vec_left, singular_val, singular_vec_right = linalg.svd(
                mode_data_mat, full_matrices=False)
            eig_values = np.square(singular_val)
            idx_sorted = (-1 * eig_values).argsort()
            cum = eig_values[idx_sorted]
            tot_var = np.sum(cum)

            for j in range(1, cum.shape[0] + 1):
                if np.sum(cum[:j]) / tot_var > self.var_ratio:
                    shape_out += (j, )
                    break
            proj_mats.append(
                singular_vec_left[:, idx_sorted][:, :shape_out[i - 1]].T)

        # set n_components to the maximum n_features if it is None
        if self.n_components is None:
            self.n_components = int(np.prod(shape_out))

        for i_iter in range(self.max_iter):
            for i in range(1, n_dims):  # ith mode
                x_projected = multi_mode_dot(
                    x, [proj_mats[m] for m in range(n_dims - 1) if m != i - 1],
                    modes=[m for m in range(1, n_dims) if m != i])
                mode_data_mat = unfold(x_projected, i)

                singular_vec_left, singular_val, singular_vec_right = linalg.svd(
                    mode_data_mat, full_matrices=False)
                eig_values = np.square(singular_val)
                idx_sorted = (-1 * eig_values).argsort()
                proj_mats[i - 1] = (
                    singular_vec_left[:, idx_sorted][:, :shape_out[i - 1]]).T

        x_projected = multi_mode_dot(x,
                                     proj_mats,
                                     modes=[m for m in range(1, n_dims)])
        x_proj_unfold = unfold(
            x_projected, mode=0
        )  # unfold the tensor projection to shape (n_samples, n_features)
        # x_proj_cov = np.diag(np.dot(x_proj_unfold.T, x_proj_unfold))  # covariance of unfolded features
        x_proj_cov = np.sum(np.multiply(x_proj_unfold.T, x_proj_unfold.T),
                            axis=1)  # memory saving computing covariance
        idx_order = (-1 * x_proj_cov).argsort()

        self.proj_mats = proj_mats
        self.idx_order = idx_order
        self.shape_out = shape_out
        self.n_dims = n_dims

        return self
Exemple #19
0
    def _fit_2d(self, X, Y):
        """
        Compute the HOPLS for X and Y wrt the parameters R, Ln and Km for the special case mode_Y = 2.

        Parameters:
            X: tensorly Tensor, The target tensor of shape [i1, ... iN], N = 2.

            Y: tensorly Tensor, The target tensor of shape [j1, ... jM], M >= 3.

        Returns:
            G: Tensor, The core Tensor of the HOPLS for X, of shape (R, L2, ..., LN).

            P: List, The N-1 loadings of X.

            D: Tensor, The core Tensor of the HOPLS for Y, of shape (R, K2, ..., KN).

            Q: List, The N-1 loadings of Y.

            ts: Tensor, The latent vectors of the HOPLS, of shape (i1, R).
        """

        # Initialization
        Er, Fr = X, Y
        P, T, W, Q = [], [], [], []
        D = tl.zeros((self.R, self.R))
        G = []

        # Beginning of the algorithm
        # Gr, _ = tucker(Er, ranks=[1] + self.Ln)
        for r in range(self.R):
            if torch.norm(Er) > self.epsilon and torch.norm(Fr) > self.epsilon:
                # computing the covariance
                Cr = mode_dot(Er, Fr.t(), 0)

                # HOOI tucker decomposition of C
                Gr_C, latents = tucker(Cr, rank=[1] + self.Ln)

                # Getting P and Q loadings
                qr = latents[0]
                qr /= torch.norm(qr)
                # Pr = latents[1:]
                Pr = [a / torch.norm(a) for a in latents[1:]]
                P.append(Pr)
                tr = multi_mode_dot(Er, Pr, list(range(1, len(Pr) + 1)), transpose=True)
                # Gr_pi = torch.pinverse(matricize(Gr))
                # tr = torch.mm(matricize(tr), Gr_pi)
                GrC_pi = torch.pinverse(matricize(Gr_C))
                tr = torch.mm(matricize(tr), GrC_pi)
                tr /= torch.norm(tr)

                # recomposition of the core tensor of Y
                ur = torch.mm(Fr, qr)
                dr = torch.mm(ur.t(), tr)

                D[r, r] = dr
                Pkron = kronecker([Pr[self.N - n - 1] for n in range(self.N)])
                # P.append(torch.mm(matricize(Gr), Pkron.t()).t())
                # W.append(torch.mm(Pkron, Gr_pi))
                Q.append(qr)
                T.append(tr)
                Gd = tl.tucker_to_tensor([Er, [tr] + Pr], transpose_factors=True)
                Gd_pi = torch.pinverse(matricize(Gd))
                W.append(torch.mm(Pkron, Gd_pi))

                # Deflation
                # X_hat = torch.mm(torch.cat(T, dim=1), torch.cat(P, dim=1).t())
                # Er = X - np.reshape(X_hat, (Er.shape), order="F")
                Er = Er - tl.tucker_to_tensor([Gd, [tr] + Pr])
                Fr = Fr - dr * torch.mm(tr, qr.t())
            else:
                break

        Q = torch.cat(Q, dim=1)
        T = torch.cat(T, dim=1)
        # P = torch.cat(P, dim=1)
        W = torch.cat(W, dim=1)

        self.model = (P, Q, D, T, W)
        return self
Exemple #20
0
    def fit(self, X, Y):
        """
        Compute the HOPLS for X and Y wrt the parameters R, Ln and Km.

        Parameters:
            X: tensorly Tensor, The target tensor of shape [i1, ... iN], N >= 3.

            Y: tensorly Tensor, The target tensor of shape [j1, ... jM], M >= 3.

        Returns:
            G: Tensor, The core Tensor of the HOPLS for X, of shape (R, L2, ..., LN).

            P: List, The N-1 loadings of X.

            D: Tensor, The core Tensor of the HOPLS for Y, of shape (R, K2, ..., KN).

            Q: List, The N-1 loadings of Y.

            ts: Tensor, The latent vectors of the HOPLS, of shape (i1, R).
        """
        # check parameters
        X_mode = len(X.shape)
        Y_mode = len(Y.shape)
        assert Y_mode >= 2, "Y need to be mode 2 minimum."
        assert X_mode >= 3, "X need to be mode 3 minimum."
        assert (
            len(self.Ln) == X_mode - 1
        ), f"The list of ranks for the decomposition of X (Ln) need to be equal to the mode of X -1: {X_mode-1}."
        if Y_mode == 2:
            return self._fit_2d(X, Y)
        assert (
            len(self.Km) == Y_mode - 1
        ), f"The list of ranks for the decomposition of Y (Km) need to be equal to the mode of Y -1: {Y_mode-1}."

        # Initialization
        Er, Fr = X, Y
        In = X.shape
        T, G, P, Q, D, W = [], [], [], [], [], []

        # Beginning of the algorithm
        for r in range(self.R):
            if torch.norm(Er) > self.epsilon and torch.norm(Fr) > self.epsilon:
                Cr = torch.Tensor(np.tensordot(Er, Fr, (0, 0)))
                # HOOI tucker decomposition of C
                _, latents = tucker(Cr, ranks=self.Ln + self.Km)

                # Getting P and Q loadings
                Pr = latents[: len(Er.shape) - 1]
                Qr = latents[len(Er.shape) - 1 :]

                # computing product of Er by latents of X
                tr = multi_mode_dot(Er, Pr, list(range(1, len(Pr))), transpose=True)

                # Getting t as the first leading left singular vector of the product
                tr = torch.svd(matricize(tr))[0][:, 0]
                tr = tr[..., np.newaxis]

                # recomposition of the core tensors
                Gr = tl.tucker_to_tensor(Er, [tr] + Pr, transpose_factors=True)
                Dr = tl.tucker_to_tensor(Fr, [tr] + Qr, transpose_factors=True)
                Pkron = kronecker([Pr[self.N - n - 1] for n in range(self.N)])
                Gr_pi = torch.pinverse(matricize(Gr))
                W.append(torch.mm(Pkron, Gr_pi))

                # Gathering of
                P.append(Pr)
                Q.append(Qr)
                G.append(Gr)
                D.append(Dr)
                T.append(tr)

                # Deflation
                Er = Er - tl.tucker_to_tensor(Gr, [tr] + Pr)
                Fr = Fr - tl.tucker_to_tensor(Dr, [tr] + Qr)
            else:
                break

        T = torch.cat(T, dim=1)
        W = torch.cat(W, dim=1)
        self.model = (P, Q, D, T, W)
        return self
    def decompose(self,
                  T,
                  orthogonal=True,
                  max_iter=200,
                  W_A=np.eye(2),
                  W_B=np.eye(2),
                  whitened=False):
        """
        uses method analogous to Kolda's to compute decomposition of T into two 
        symmetric tensors A and B.

        Parameters
        ----------
        T : 4d array
            Tensor to decompose
        orthogonal : bool, optional
            if True, T should be an odeco tensor train. The default is True.
            Whitening applied if false.
        max_iter : int, optional
            Number of iterations whitening algorithm executes to find psd 
            matrices before declaring failure. The default is 200.

        Raises
        ------
        ValueError
            If T is not a 4-way tensor of equal dimensions

        Returns
        -------
        lammy : 1d array
            weight vector of 3-way symmetric left core of T
        U : 2d array
            columns make up symmetric vectors in decomposition of left core of T.
        mu : 1d array
            weight vector of 3-way symmetric left core of T
        V : 2d array
            columns make up symmetric vectors in decomposition of right core of T.
        """
        if (whitened == True):
            warnings.warn(
                """W_A, W_B, whitened parameters should only be used in 
                          internal recursive call""",
                stacklevel=2)

        # check T is a 4-way partially symmetric tensor
        if len(T.shape) != 4:
            raise ValueError("T must be a 4-way tensor")

        if (T.shape[0] != T.shape[1]) or (T.shape[2] != T.shape[3]):
            raise ValueError("T must be a partially symmetric tensor")

        if (whitened == False) and (T.shape[0] != T.shape[2]):
            raise ValueError("T must be a partially symmetric tensor")

        # take weighted sums of slices of A's dimensions and B's dimensions in T
        S_A, S_B = self.sum_of_slices(T)

        if orthogonal == True:
            # compute eigendecomposition of weighted sums of slices
            vals_A, U = lg.eigh(S_A)
            absvals_A = np.abs(vals_A)
            idx = np.argsort(
                absvals_A)  # find index of sorted absolute eigvals
            # no. nonzero eigvals = rank_A
            rank_A = np.shape(S_A)[0] - np.searchsorted(absvals_A[idx], 10e-10)
            U = (U[:,
                   idx])[:,
                         -rank_A:]  # take rank_A highest corresp. eigenvectors

            vals_B, V = lg.eigh(S_B)
            absvals_B = np.abs(vals_B)
            idx = np.argsort(absvals_B)
            rank_B = np.shape(S_B)[0] - np.searchsorted(absvals_B[idx], 10e-10)
            V = (V[:,
                   idx])[:,
                         -rank_B:]  # take rank_B highest corresp. eigenvectors

            # dewhiten if necessary
            if whitened == True:  # only true for internal recursive call
                U_og = lg.pinv(W_A) @ U
                V_og = lg.pinv(W_B) @ V

            else:
                U_og = U
                V_og = V

            # obtain matrix of products of "eigenvalues" by contracting with eigenvectors
            eig_products = np.empty(shape=(rank_A, rank_B))

            for u in range(rank_A):
                for v in range(rank_B):
                    C = np.tensordot(T, U[:, u], axes=(0, 0))
                    C = np.tensordot(C, U[:, u], axes=(0, 0))
                    C = np.tensordot(C, V[:, v], axes=(0, 0))
                    C = np.tensordot(C, V[:, v], axes=(0, 0))
                    eig_products[u, v] = C / (U_og[:, u] @ V_og[:, v])

            # obtain "eigenvalues" by performing SVD on this rank-1 products matrix
            SVD = lg.svd(eig_products)

            # obtained up to scaling; absorb singular values into lambda WLOG
            lammy = SVD[0][:, 0] * SVD[1][0]
            mu = SVD[2][0, :]

            return lammy, U_og, mu, V_og

        else:  # apply whitening
            # Take sums of slices until psd matrices of ranks A and B are found
            for k in range(max_iter):
                if (np.all(lg.eigvalsh(S_A) > -10e-10)
                        and np.all(lg.eigvalsh(S_B) > -10e-10)):
                    break

                elif max_iter - k <= 1:
                    print(""""Max iterations reached for psd sum of slices 
                          associated with A\n""")
                    return

                S_A, S_B = self.sum_of_slices(T)

            # take "skinny" eigendecompositions of these psd matrices
            D_A, U_A = lg.eigh(S_A)
            rank_A = np.shape(S_A)[0] - np.searchsorted(D_A, 10e-10)
            U_A = np.flip(U_A[:, -rank_A:],
                          axis=1)  # rank_A highest corresp. eigenvectors
            D_A = np.diag(np.flip(
                D_A[-rank_A:]))  # diagonal of rank_A highest eigenvalues

            D_B, U_B = lg.eigh(S_B)
            rank_B = np.shape(S_B)[0] - np.searchsorted(D_B, 10e-10)
            U_B = np.flip(U_B[:, -rank_B:],
                          axis=1)  # rank_B highest corresp. eigenvectors
            D_B = np.diag(np.flip(
                D_B[-rank_B:]))  # diagonal of rank_B highest eigenvalues

            # produce whitening matrices
            W_A = lg.inv(D_A**0.5) @ U_A.T
            W_B = lg.inv(D_B**0.5) @ U_B.T

            # take the tensor-matrix product of W_A along A's modes and W_B along B's modes
            T_bar = multi_mode_dot(T, (W_A, W_A, W_B, W_B), modes=(0, 1, 2, 3))

            # apply orthogonal decomposition to whitened tensor T_bar
            return self.decompose(T_bar, W_A=W_A, W_B=W_B, whitened=True)
Exemple #22
0
    def _fit(self, x):
        """Solve MPCA"""

        shape_ = x.shape  # shape of input data
        n_samples = shape_[0]  # number of samples
        n_dims = x.ndim

        self.shape_in = shape_[1:]
        self.mean_ = np.mean(x, axis=0)
        x = x - self.mean_

        # init
        phi = dict()
        shape_out = ()
        proj_mats = []

        for i in range(1, n_dims):
            for j in range(n_samples):
                # unfold the j_th tensor along the i_th mode
                x_ji = unfold(x[j, :], mode=i - 1)
                if i not in phi:
                    phi[i] = 0
                phi[i] = phi[i] + np.dot(x_ji, x_ji.T)

        # get the output tensor shape based on the cumulative distribution of eigenvalues for each mode
        for i in range(1, n_dims):
            eig_values, eig_vectors = np.linalg.eig(phi[i])
            idx_sorted = (-1 * eig_values).argsort()
            cum = eig_values[idx_sorted]
            var_tot = np.sum(cum)

            for j in range(1, cum.shape[0] + 1):
                if np.sum(cum[:j]) / var_tot > self.var_ratio:
                    shape_out += (j, )
                    break
            proj_mats.append(eig_vectors[:,
                                         idx_sorted][:, :shape_out[i - 1]].T)

        # set n_components to the maximum n_features if it is None
        if self.n_components is None:
            self.n_components = int(np.prod(shape_out))

        for i_iter in range(self.max_iter):
            phi = dict()
            for i in range(1, n_dims):  # ith mode
                if i not in phi:
                    phi[i] = 0
                for j in range(n_samples):
                    xj = multi_mode_dot(
                        x[j, :],  # jth tensor/sample
                        [
                            proj_mats[m]
                            for m in range(n_dims - 1) if m != i - 1
                        ],
                        modes=[m for m in range(n_dims - 1) if m != i - 1],
                    )
                    xj_unfold = unfold(xj, i - 1)
                    phi[i] = np.dot(xj_unfold, xj_unfold.T) + phi[i]

                eig_values, eig_vectors = np.linalg.eig(phi[i])
                idx_sorted = (-1 * eig_values).argsort()
                proj_mats[i -
                          1] = (eig_vectors[:, idx_sorted][:, :shape_out[i -
                                                                         1]]).T

        x_projected = multi_mode_dot(x,
                                     proj_mats,
                                     modes=[m for m in range(1, n_dims)])
        x_proj_unfold = unfold(
            x_projected, mode=0
        )  # unfold the tensor projection to shape (n_samples, n_features)
        # x_proj_cov = np.diag(np.dot(x_proj_unfold.T, x_proj_unfold))  # covariance of unfolded features
        x_proj_cov = np.sum(np.multiply(x_proj_unfold, x_proj_unfold),
                            axis=1)  # memory saving computing covariance
        idx_order = (-1 * x_proj_cov).argsort()

        self.proj_mats = proj_mats
        self.idx_order = idx_order
        self.shape_out = shape_out
        self.n_dims = n_dims

        return self
Exemple #23
0
def MPCA_(X, variance_explained=0.97, max_iter=1):
    """
    Parameter:
        X: array-like, ndarray of shape (I1, I2, ..., n_samples)
        variance_explained: ration of variance to keep (between 0 and 1)
        max_iter: max number of iteration
    ----------
    Return:
        tPs: list of projection matrices
    """
    dim_in = X.shape
    n_spl = dim_in[-1]
    n_dim = X.ndim
    # Is = dim_in[:-1]
    Xmean = np.mean(X, axis=-1)
    X_ = np.zeros(X.shape)
    # init
    Phi = dict()
    Us = dict()  # eigenvectors
    Vs = dict()  # eigenvalues
    cums = dict()  # cumulative distribution of eigenvalues
    tPs = []
    for i in range(n_spl):
        X_[..., i] = X[..., i] - Xmean
        for j in range(n_dim - 1):
            X_i = unfold(X_[..., i], mode=j)
            if j not in Phi:
                Phi[j] = 0
            Phi[j] = Phi[j] + np.dot(X_i, X_i.T)

    for i in range(n_dim - 1):
        eig_vals, eig_vecs = np.linalg.eig(Phi[i])
        idx_sorted = eig_vals.argsort()[::-1]
        Us[i] = eig_vecs[:, idx_sorted]
        Vs[i] = eig_vals[idx_sorted]
        sum_ = np.sum(Vs[i])
        for j in range(Vs[i].shape[0] - 1, 0, -1):
            if np.sum(Vs[i][j:]) / sum_ > (1 - variance_explained):
                cums[i] = j + 1
                break
        tPs.append(Us[i][:, :cums[i]].T)

    for i_iter in range(max_iter):
        Phi = dict()
        for i in range(n_dim - 1):
            # dim_in_ = dim_in[i]
            if i not in Phi:
                Phi[i] = 0
            for j in range(n_spl):
                X_i = X_[..., j]
                Xi_ = multi_mode_dot(
                    X_i, [tPs[m] for m in range(n_dim - 1) if m != i],
                    modes=[m for m in range(n_dim) if m != i])
                tXi = unfold(Xi_, i)
                Phi[i] = np.dot(tXi, tXi.T) + Phi[i]

            eig_vals, eig_vecs = np.linalg.eig(Phi[i])
            idx_sorted = eig_vals.argsort()[::-1]
            tPs[i] = eig_vecs[:, idx_sorted]
            tPs[i] = tPs[i][:, :cums[i]].T

    return tPs
    def __getitem__(self, indices):
        counter = 0
        ndim = self.core.ndim
        new_ndim = 0
        new_factors = []
        out_shape = []
        new_modes = []

        core = self.core

        for (index, shape) in zip(indices, self.tensorized_shape):
            if isinstance(shape, int):
                if index is Ellipsis:
                    raise ValueError(
                        f'Ellipsis is not yet supported, yet got indices={indices}, indices[{i}]={index}.'
                    )
                factor = self.factors[counter]
                if isinstance(index, int):
                    core = tenalg.mode_dot(core, factor[index, :], new_ndim)
                else:
                    contracted = factor[index, :]
                    new_factors.append(contracted)
                    if contracted.shape[0] > 1:
                        out_shape.append(shape)
                        new_modes.append(new_ndim)
                        new_ndim += 1

                counter += 1

            else:  # Tensorized dimension
                n_tensorized_modes = len(shape)

                if index == slice(None) or index == ():
                    new_factors.extend(self.factors[counter:counter +
                                                    n_tensorized_modes])
                    out_shape.append(shape)
                    new_modes.extend(
                        [new_ndim + i for i in range(n_tensorized_modes)])
                    new_ndim += n_tensorized_modes

                else:
                    if isinstance(index, slice):
                        # Since we've already filtered out :, this is a partial slice
                        # Convert into list
                        max_index = math.prod(shape)
                        index = list(range(*index.indices(max_index)))

                    index = np.unravel_index(index, shape)

                    contraction_factors = [
                        f[idx, :] for idx, f in zip(
                            index, self.factors[counter:counter +
                                                n_tensorized_modes])
                    ]
                    if contraction_factors[0].ndim > 1:
                        shared_symbol = einsum_symbols[core.ndim + 1]
                    else:
                        shared_symbol = ''

                    core_symbols = ''.join(einsum_symbols[:core.ndim])
                    factors_symbols = ','.join([
                        f'{shared_symbol}{s}'
                        for s in core_symbols[new_ndim:new_ndim +
                                              n_tensorized_modes]
                    ])
                    res_symbol = core_symbols[:
                                              new_ndim] + shared_symbol + core_symbols[
                                                  new_ndim +
                                                  n_tensorized_modes:]

                    if res_symbol:
                        eq = core_symbols + ',' + factors_symbols + '->' + res_symbol
                    else:
                        eq = core_symbols + ',' + factors_symbols

                    core = torch.einsum(eq, core, *contraction_factors)

                    if contraction_factors[0].ndim > 1:
                        new_ndim += 1

                counter += n_tensorized_modes

        if counter <= ndim:
            out_shape.extend(list(core.shape[new_ndim:]))
            new_modes.extend(list(range(new_ndim, core.ndim)))
            new_factors.extend(self.factors[counter:])

        # Only here until our Tucker class handles partial-Tucker too
        if len(new_modes) != core.ndim:
            core = tenalg.multi_mode_dot(core, new_factors, new_modes)
            new_factors = []

        if new_factors:
            # return core, new_factors, out_shape, new_modes
            return self.__class__(core,
                                  new_factors,
                                  tensorized_shape=out_shape)

        return core