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
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
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
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)
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}
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
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))
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)
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
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
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
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)
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
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