class_docstr: MPCA implementation compatible with scikit-learn
  
  Args:
      var_ratio (float, optional): Percentage of variance explained (between 0 and 1). Defaults to 0.97.
      max_iter (int, optional): Maximum number of iteration. Defaults to 1.
      vectorize (bool): Whether return the transformed/projected tensor in vector. Defaults to False.
      n_components (int): Number of components to keep. Applies only when vectorize=True. Defaults to None.
  
  Attributes:
      proj_mats (list of arrays): A list of transposed projection matrices, shapes (P_1, I_1), ...,
          (P_N, I_N), where P_1, ..., P_N are output tensor shape for each sample.
      idx_order (array-like): The ordering index of projected (and vectorized) features in decreasing variance.
      mean_ (array-like): Per-feature empirical mean, estimated from the training set, shape (I_1, I_2, ..., I_N).
      shape_in (tuple): Input tensor shapes, i.e. (I_1, I_2, ..., I_N).
      shape_out (tuple): Output tensor shapes, i.e. (P_1, P_2, ..., P_N).
  
  Reference:
      Haiping Lu, K.N. Plataniotis, and A.N. Venetsanopoulos, "MPCA: Multilinear Principal Component Analysis of
      Tensor Objects", IEEE Transactions on Neural Networks, Vol. 19, No. 1, Page: 18-39, January 2008. For initial
      Matlab implementation, please go to https://uk.mathworks.com/matlabcentral/fileexchange/26168.
  
  Examples:
      >>> import numpy as np
      >>> from kale.embed.factorization import MPCA
      >>> x = np.random.random((40, 20, 25, 20))
      >>> x.shape
      (40, 20, 25, 20)
      >>> mpca = MPCA()
      >>> x_projected = mpca.fit_transform(x)
      >>> x_projected.shape
      (40, 18, 23, 18)
      >>> x_projected = mpca.transform(x)
      >>> x_projected.shape
      (40, 7452)
      >>> x_projected = mpca.transform(x)
      >>> x_projected.shape
      (40, 50)
      >>> x_rec = mpca.inverse_transform(x_projected)
      >>> x_rec.shape
      (40, 20, 25, 20)
docstr: Solve MPCA
docstr_tok: ["Solve","MPCA"]
code: 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
  
imports: import logging
  import warnings
  import numpy
  from numpy.linalg import multi_dot
  from scipy import linalg
  from sklearn.base import BaseEstimator, TransformerMixin
  from sklearn.metrics.pairwise import pairwise_kernels
  from sklearn.preprocessing import KernelCenterer, LabelBinarizer
  from sklearn.utils.validation import check_is_fitted
  from tensorly.base import fold, unfold
  from tensorly.tenalg import multi_mode_dot
label_desc: Use image node_id 3 for calling the MPCA obj's underlying member method code with example usage: obj._fit(x) and returns: self