def super_diagonal_tensor(shape, distr='ones', values=None): """ Generates a tensor of any dimension with random or specified numbers across the super-diagonal and zeros elsewhere Parameters ---------- shape : tuple Specifies the dimensions of the tensor ``len(shape)`` defines the order of the tensor, whereas its values specify sizes of dimensions of the tensor. distr : str, optional Specifies the random generation using a class of the numpy.random module values : list Array of values on the super-diagonal of a tensor Returns ------- Tensor Generated tensor according to the parameters specified """ if not isinstance(shape, tuple): raise TypeError("Parameter `shape` should be passed as a tuple!") if shape[1:] != shape[:-1]: raise ValueError("All values in `shape` should have the same value!") inds = shape[0] data = np.zeros(shape) if values is None: values = _predefined_distr(distr, inds) if len(values) != inds: raise ValueError("Dimension mismatch! The specified values do not match " "the specified shape of the tensor provided ({} != {})".format(len(values), inds)) values = np.asarray(values).flatten() np.fill_diagonal(data, values) return Tensor(array=data)
def dense_tensor(shape, distr='uniform', distr_type=0, fxdind=None): """ Generates a dense tensor of any dimension and fills it accordingly Parameters ---------- shape : tuple Specifies the dimensions of the tensor distr : str, optional Specifies the random generation using a class of the numpy.random module distr_type : int, optional Number of indices to not fix. 0 will be applied globally, 1 will apply to fibers, 2 to slices, etc. Returns ------- Tensor Generated tensor according to the parameters specified """ # fxdind: fixed indices if distr_type == 0: data = _predefined_distr(distr, shape) else: data = np.random.uniform(size=shape) raise NotImplementedError('Not implemented in dataset (basic) class') return Tensor(array=data)
def sparse_tensor(shape, distr='uniform', distr_type=0, fxdind=None, pct=0.05): """ Generates a sparse tensor of any dimension and fills it accordingly Parameters ---------- shape : tuple Specifies the dimensions of the tensor distr : str, optional Specifies the random generation using a class of the numpy.random module distr_type : int, optional Number of indices to not fix. 0 will be applied globally, 1 will apply to fibers, 2 to slices, etc. pct : float, optional Percentage of the dataset to be filled Returns ------- Tensor Generated tensor according to the parameters specified """ data_size = np.product(shape) if distr_type == 0: number_non_zero_values = int(data_size * pct) data = np.zeros(data_size) index = np.random.randint(low=0, high=data_size, size=number_non_zero_values) data[index] = _predefined_distr(distr, number_non_zero_values) data = data.reshape(shape) else: raise NotImplementedError('Not implemented in dataset (basic) class') return Tensor(array=data)
def super_symmetric_tensor(shape, tensor=None): """ Generates a tensor of equal dimensions with random or specified numbers, with a specified tensor. Parameters ---------- shape : tuple Specifies the dimensions of the tensor ``len(shape)`` defines the order of the tensor, whereas its values specify sizes of dimensions of the tensor. tensor : Tensor, optional Input tensor to be symmetricised Returns ------- Tensor Generated tensor according to the parameters specified """ dims = len(shape) inds = itertools.permutations(np.arange(dims)) inds = np.array(list(inds)) data = np.zeros(shape) if tensor is None: tensor = dense_tensor(shape) for i, _ in enumerate(inds): data = data + np.transpose(tensor.data, tuple(inds[i, :])) return Tensor(array=data)
def is_toeplitz_tensor(tensor, modes=None): """ Checks if ``tensor`` has Toeplitz structure Parameters ---------- tensor : Tensor Input tensor to check Returns ------- Boolean indicating if Toeplitz matrix """ if tensor.order <= 2: return is_toeplitz_matrix(tensor.data) if modes is None: modes = [0, 1] sz = np.asarray(tensor.shape) availmodes = np.setdiff1d(np.arange(len(sz)), modes) for idx, mode in enumerate(availmodes): dim = sz[mode] # Go through each dim for i in range(dim): t = tensor.access(i, mode) t = Tensor(t) if not (is_toeplitz_tensor(t)): print("Wrong slice: \n{}\n{}".format(t, (i, idx))) return False return True
def test_init_fmat(self): """ Tests for _init_fmat method """ np.random.seed(0) shape = (4, 5, 6) size = reduce(lambda x, y: x * y, shape) tensor = Tensor(np.random.randn(size).reshape(shape)) cpd = CPD() # ------ tests on getting factor matrices of the correct shape for rank_value in range(min(tensor.shape) - 1, max(tensor.shape) + 2): rank = (rank_value, ) fmat = cpd._init_fmat(tensor=tensor, rank=rank) for mode, mat in enumerate(fmat): assert mat.shape == (tensor.shape[mode], rank_value) # ------ tests for the type of initialisation # svd type initialisation should produce factor matrices with orthogonal columns rank = (min(tensor.shape) - 1, ) cpd = CPD(init='svd') fmat = cpd._init_fmat(tensor=tensor, rank=rank) for mat in fmat: result = np.dot(mat.T, mat) true_result = np.eye(rank[0]) np.testing.assert_almost_equal(result, true_result) # svd type initialisation but the `rank` is greater then one of the dimensions then you get random fmat # and there would be a runtime warning rank = (min(tensor.shape) + 1, ) cpd = CPD(init='svd', verbose=True) with pytest.warns(RuntimeWarning): fmat = cpd._init_fmat(tensor=tensor, rank=rank) for mat in fmat: result_1 = np.dot(mat.T, mat) result_2 = np.eye(rank[0]) # since each mat is randomly initialized it is not orthonormal with pytest.raises(AssertionError): np.testing.assert_almost_equal(result_1, result_2) # random type initialisation should produce factor matrices each of which is not orthonormal rank = (3, ) cpd = CPD(init='random') fmat = cpd._init_fmat(tensor=tensor, rank=rank) for mat in fmat: result_1 = np.dot(mat.T, mat) result_2 = np.eye(rank[0]) # since each mat is randomly initialized it is not orthonormal with pytest.raises(AssertionError): np.testing.assert_almost_equal(result_1, result_2) # unknown type of initialisation with pytest.raises(NotImplementedError): rank = (min(tensor.shape) - 1, ) cpd = CPD(init='qwerty') cpd._init_fmat(tensor=tensor, rank=rank)
def test_mape(): """ Tests for mape """ # ------ tests for 1-d case shape = (2, ) size = shape[0] tensor_true = Tensor(np.arange(size).reshape(shape) + 1) tensor_pred = Tensor(np.arange(size).reshape(shape) * 2) true_mape = 0.5 result = mape(tensor_true, tensor_pred) np.testing.assert_array_almost_equal(result, true_mape) # ------ tests for 2-d case shape = (2, 2) size = reduce(lambda x, y: x * y, shape) tensor_true = Tensor(np.arange(size).reshape(shape) + 1) tensor_pred = Tensor(np.arange(size).reshape(shape) * 2) true_mape = 0.4583333333333333 result = mape(tensor_true, tensor_pred) np.testing.assert_array_almost_equal(result, true_mape) # ------ tests for 3-d case shape = (2, 2, 2) size = reduce(lambda x, y: x * y, shape) tensor_true = Tensor(np.arange(size).reshape(shape) + 1) tensor_pred = Tensor(np.arange(size).reshape(shape) * 2) true_mape = 0.5705357142857143 result = mape(tensor_true, tensor_pred) np.testing.assert_array_almost_equal(result, true_mape)
def test_rmse(): """ Tests for rmse """ # ------ tests for 1-d case shape = (2, ) size = shape[0] tensor_true = Tensor(np.arange(size).reshape(shape)) tensor_pred = Tensor(np.arange(size).reshape(shape) * 2) true_rmse = 0.7071067811865476 result = rmse(tensor_true, tensor_pred) np.testing.assert_array_almost_equal(result, true_rmse) # ------ tests for 2-d case shape = (2, 2) size = reduce(lambda x, y: x * y, shape) tensor_true = Tensor(np.arange(size).reshape(shape)) tensor_pred = Tensor(np.arange(size).reshape(shape) * 2) true_rmse = 1.8708286933869707 result = rmse(tensor_true, tensor_pred) np.testing.assert_array_almost_equal(result, true_rmse) # ------ tests for 3-d case shape = (2, 2, 2) size = reduce(lambda x, y: x * y, shape) tensor_true = Tensor(np.arange(size).reshape(shape)) tensor_pred = Tensor(np.arange(size).reshape(shape) * 2) true_rmse = 4.183300132670378 result = rmse(tensor_true, tensor_pred) np.testing.assert_array_almost_equal(result, true_rmse)
def test_residual_rel_error(): # ------ tests for 1-d case shape = (2, ) size = shape[0] tensor_true = Tensor(np.arange(size).reshape(shape)) tensor_pred = Tensor(np.arange(size).reshape(shape) * 2) true_res_rel_error = 1 result = residual_rel_error(tensor_true, tensor_pred) np.testing.assert_array_almost_equal(result, true_res_rel_error) # ------ tests for 2-d case shape = (2, 2) size = reduce(lambda x, y: x * y, shape) tensor_true = Tensor(np.arange(size).reshape(shape)) tensor_pred = Tensor(np.arange(size).reshape(shape) * 2) true_res_rel_error = 1 result = residual_rel_error(tensor_true, tensor_pred) np.testing.assert_array_almost_equal(result, true_res_rel_error) # ------ tests for 3-d case shape = (2, 2, 2) size = reduce(lambda x, y: x * y, shape) tensor_true = Tensor(np.arange(size).reshape(shape)) tensor_pred = Tensor(np.arange(size).reshape(shape) * 2) true_res_rel_error = 1 result = residual_rel_error(tensor_true, tensor_pred) np.testing.assert_array_almost_equal(result, true_res_rel_error)
def residual_tensor(tensor_orig, tensor_approx): """ Residual tensor Parameters ---------- tensor_orig : Tensor tensor_approx : {Tensor, TensorCPD, TensorTKD, TensorTT} Returns ------- residual : Tensor """ if not isinstance(tensor_orig, Tensor): raise TypeError("Unknown data type of original tensor.\n" "The available type for `tensor_A` is `Tensor`") # TODO: make use of direct subtraction of tensors if isinstance(tensor_approx, Tensor): residual = Tensor(tensor_orig.data - tensor_approx.data) elif isinstance(tensor_approx, BaseTensorTD): residual = Tensor(tensor_orig.data - tensor_approx.reconstruct().data) else: raise TypeError("Unknown data type of the approximation tensor!\n" "The available types for `tensor_B` are `Tensor`, `TensorCPD`, `TensorTKD`, `TensorTT`") return residual
def test_init(self): """ Tests for constructor of BaseCPD class """ # Even though we can create such object we shouldn't do that default_params = dict(init='svd', max_iter=50, epsilon=10e-3, tol=10e-5, random_state=None, verbose=False) # basically for coverage tests object of with pytest.raises(NotImplementedError): tensor = Tensor(np.arange(2)) rank = 5 keep_meta = 0 base_cpd = BaseCPD(**default_params) base_cpd.decompose(tensor, rank, keep_meta) with pytest.raises(NotImplementedError): base_cpd = BaseCPD(**default_params) base_cpd.plot()
def super_diag_tensor(shape, values=None): """ Super-diagonal tensor of the specified `order`. Parameters ---------- shape : tuple Desired shape of the tensor. ``len(shape)`` defines the order of the tensor, whereas its values specify sizes of dimensions of the tensor. values : np.ndarray Array of values on the super-diagonal of a tensor. By default contains only ones. Length of this vector defines Kryskal rank which is equal to ``shape[0]``. Returns ------- tensor : Tensor """ order = len(shape) rank = shape[0] if not isinstance(shape, tuple): raise TypeError("Parameter `shape` should be passed as a tuple!") if not all(mode_size == shape[0] for mode_size in shape): raise ValueError("All values in `shape` should have the same value!") if values is None: values = np.ones(rank) # set default values elif isinstance(values, np.ndarray): if values.ndim != 1: raise ValueError("The `values` should be 1-dimensional numpy array!") if values.size != rank: raise ValueError("Dimension mismatch! Not enough or too many `values` for the specified `shape`:\n" "{} != {} (values.size != shape[0])".format(values.size, rank)) else: raise TypeError("The `values` should be passed as a numpy array!") core = np.zeros(shape) core[np.diag_indices(rank, ndim=order)] = values tensor = Tensor(core) return tensor
def _reconstruct(fmat_a, fmat_b, n_mat): """ Reconstruct the tensor and matrix after the coupled factorisation Parameters ---------- fmat_a : List(np.ndarray) Multidimensional data obtained from the factorisation fmat_b : List(np.ndarray) Multidimensional data obtained from the factorisation n_mat : int Number of matrices provided to fuse Returns ------- (core_tensor, lrecon) : np.ndarray or List(np.ndarray) Reconstructed tensor and list of matrices obtained from the factorisation """ core_values = np.repeat(np.array([1]), fmat_a[0].shape[1]) _r = (fmat_a[0].shape[1], ) core_shape = _r * len(fmat_a) core_tensor = super_diag_tensor(core_shape, values=core_values) for mode, fmat in enumerate(fmat_a): core_tensor.mode_n_product(fmat, mode=mode, inplace=True) lrecon = [Tensor(fmat_a[i].dot(fmat_b[i].T)) for i in range(n_mat)] return core_tensor, lrecon
def test_residual_tensor(): """ Tests for computing/creating a residual tensor """ true_default_mode_names = ['mode-0', 'mode-1', 'mode-2'] # ------ tests for residual tensor with the Tensor array_3d = np.array([[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]) true_residual_data = np.zeros(array_3d.shape) tensor_1 = Tensor(array=array_3d) tensor_2 = Tensor(array=array_3d) residual = residual_tensor(tensor_orig=tensor_1, tensor_approx=tensor_2) assert isinstance(residual, Tensor) assert (residual.mode_names == true_default_mode_names) np.testing.assert_array_equal(residual.data, true_residual_data) # ------ tests for residual tensor with the TensorCPD array_3d = np.array([[[100., 250., 400., 550.], [250., 650., 1050., 1450.], [400., 1050., 1700., 2350.]], [[250., 650., 1050., 1450.], [650., 1925., 3200., 4475.], [1050., 3200., 5350., 7500.]]] ) true_residual_data = np.zeros(array_3d.shape) tensor = Tensor(array=array_3d) ft_shape = (2, 3, 4) # define shape of the tensor in full form R = 5 # define Kryskal rank of a tensor in CP form core_values = np.ones(R) fmat = [np.arange(orig_dim * R).reshape(orig_dim, R) for orig_dim in ft_shape] tensor_cpd = TensorCPD(fmat=fmat, core_values=core_values) residual = residual_tensor(tensor_orig=tensor, tensor_approx=tensor_cpd) assert isinstance(residual, Tensor) assert (residual.mode_names == true_default_mode_names) np.testing.assert_array_equal(residual.data, true_residual_data) # ------ tests for residual tensor with the TensorTKD array_3d = np.array([[[378, 1346, 2314, 3282, 4250], [1368, 4856, 8344, 11832, 15320], [2358, 8366, 14374, 20382, 26390], [3348, 11876, 20404, 28932, 37460]], [[1458, 5146, 8834, 12522, 16210], [5112, 17944, 30776, 43608, 56440], [8766, 30742, 52718, 74694, 96670], [12420, 43540, 74660, 105780, 136900]], [[2538, 8946, 15354, 21762, 28170], [8856, 31032, 53208, 75384, 97560], [15174, 53118, 91062, 129006, 166950], [21492, 75204, 128916, 182628, 236340]]]) true_residual_data = np.zeros(array_3d.shape) tensor = Tensor(array=array_3d) ft_shape = (3, 4, 5) # define shape of the tensor in full form ml_rank = (2, 3, 4) # define multi-linear rank of a tensor in Tucker form core_size = reduce(lambda x, y: x * y, ml_rank) core_values = np.arange(core_size).reshape(ml_rank) fmat = [np.arange(ft_shape[mode] * ml_rank[mode]).reshape(ft_shape[mode], ml_rank[mode]) for mode in range(len(ft_shape))] tensor_tkd = TensorTKD(fmat=fmat, core_values=core_values) residual = residual_tensor(tensor_orig=tensor, tensor_approx=tensor_tkd) assert isinstance(residual, Tensor) assert (residual.mode_names == true_default_mode_names) np.testing.assert_array_equal(residual.data, true_residual_data) # ------ tests for residual tensor with the TensorTT array_3d = np.array([[[300, 348, 396, 444, 492, 540], [354, 411, 468, 525, 582, 639], [408, 474, 540, 606, 672, 738], [462, 537, 612, 687, 762, 837], [516, 600, 684, 768, 852, 936]], [[960, 1110, 1260, 1410, 1560, 1710], [1230, 1425, 1620, 1815, 2010, 2205], [1500, 1740, 1980, 2220, 2460, 2700], [1770, 2055, 2340, 2625, 2910, 3195], [2040, 2370, 2700, 3030, 3360, 3690]], [[1620, 1872, 2124, 2376, 2628, 2880], [2106, 2439, 2772, 3105, 3438, 3771], [2592, 3006, 3420, 3834, 4248, 4662], [3078, 3573, 4068, 4563, 5058, 5553], [3564, 4140, 4716, 5292, 5868, 6444]], [[2280, 2634, 2988, 3342, 3696, 4050], [2982, 3453, 3924, 4395, 4866, 5337], [3684, 4272, 4860, 5448, 6036, 6624], [4386, 5091, 5796, 6501, 7206, 7911], [5088, 5910, 6732, 7554, 8376, 9198]]]) true_residual_data = np.zeros(array_3d.shape) tensor = Tensor(array=array_3d) r1, r2 = 2, 3 I, J, K = 4, 5, 6 core_1 = np.arange(I * r1).reshape(I, r1) core_2 = np.arange(r1 * J * r2).reshape(r1, J, r2) core_3 = np.arange(r2 * K).reshape(r2, K) core_values = [core_1, core_2, core_3] ft_shape = (I, J, K) tensor_tt = TensorTT(core_values=core_values) residual = residual_tensor(tensor_orig=tensor, tensor_approx=tensor_tt) assert isinstance(residual, Tensor) assert (residual.mode_names == true_default_mode_names) np.testing.assert_array_equal(residual.data, true_residual_data) # ------ tests that should FAIL for residual tensor due to wrong input type array_3d = np.array([[[0, 1, 2, 3], [4, 5, 6, 7], [8, 9, 10, 11]], [[12, 13, 14, 15], [16, 17, 18, 19], [20, 21, 22, 23]]]) tensor_1 = Tensor(array=array_3d) tensor_2 = array_3d with pytest.raises(TypeError): residual_tensor(tensor_orig=tensor_1, tensor_approx=tensor_2) tensor_1 = array_3d tensor_2 = Tensor(array=array_3d) with pytest.raises(TypeError): residual_tensor(tensor_orig=tensor_1, tensor_approx=tensor_2)
def test_decompose(self): """ Tests for decompose method """ # ------ tests for termination conditions captured_output = io.StringIO( ) # Create StringIO object for testing verbosity sys.stdout = captured_output # and redirect stdout. np.random.seed(0) shape = (6, 7, 8) size = reduce(lambda x, y: x * y, shape) array_3d = np.random.randn(size).reshape(shape) tensor = Tensor(array_3d) rank = (2, ) cpd = RandomisedCPD(verbose=True) # check for termination at max iter cpd.max_iter = 10 cpd.epsilon = 0.01 cpd.tol = 0.0001 cpd.decompose(tensor=tensor, rank=rank) assert not cpd.converged assert len(cpd.cost) == cpd.max_iter assert cpd.cost[-1] > cpd.epsilon # Repeat cpd, test is self.cost is reset cpd.decompose(tensor=tensor, rank=rank) assert len(cpd.cost) == cpd.max_iter # check for termination when acceptable level of approximation is achieved cpd.max_iter = 20 cpd.epsilon = 0.98 cpd.tol = 0.0001 cpd.decompose(tensor=tensor, rank=rank) assert not cpd.converged assert len(cpd.cost) < cpd.max_iter assert cpd.cost[-1] <= cpd.epsilon # check for termination at convergence cpd.max_iter = 20 cpd.epsilon = 0.01 cpd.tol = 0.03 cpd.decompose(tensor=tensor, rank=rank) assert cpd.converged assert len(cpd.cost) < cpd.max_iter assert cpd.cost[-1] > cpd.epsilon assert captured_output.getvalue( ) != '' # to check that something was actually printed # ------ tests for correct output type and values shape = (4, 5, 6) size = reduce(lambda x, y: x * y, shape) array_3d = np.arange(size, dtype='float32').reshape(shape) tensor = Tensor(array_3d) rank = (7, ) cpd = RandomisedCPD(init='random', max_iter=50, epsilon=10e-3, tol=10e-5) tensor_cpd = cpd.decompose(tensor=tensor, rank=rank) assert isinstance(tensor_cpd, TensorCPD) assert tensor_cpd.order == tensor.order assert tensor_cpd.rank == rank # check dimensionality of computed factor matrices for mode, fmat in enumerate(tensor_cpd.fmat): assert fmat.shape == (tensor.shape[mode], rank[0]) tensor_rec = tensor_cpd.reconstruct() np.testing.assert_almost_equal(tensor_rec.data, tensor.data) # ------ tests that should FAIL due to wrong input type cpd = RandomisedCPD() # tensor should be Tensor class with pytest.raises(TypeError): shape = (5, 5, 5) size = reduce(lambda x, y: x * y, shape) incorrect_tensor = np.arange(size).reshape(shape) correct_rank = (2, ) cpd.decompose(tensor=incorrect_tensor, rank=correct_rank) # rank should be a tuple with pytest.raises(TypeError): shape = (5, 5, 5) size = reduce(lambda x, y: x * y, shape) correct_tensor = Tensor(np.arange(size).reshape(shape)) incorrect_rank = [2] cpd.decompose(tensor=correct_tensor, rank=incorrect_rank) # incorrect length of rank with pytest.raises(ValueError): shape = (5, 5, 5) size = reduce(lambda x, y: x * y, shape) correct_tensor = Tensor(np.arange(size).reshape(shape)) incorrect_rank = (2, 3) cpd.decompose(tensor=correct_tensor, rank=incorrect_rank) # invalid sample size with pytest.raises(ValueError): cpd = RandomisedCPD(sample_size=0) shape = (5, 5, 5) size = reduce(lambda x, y: x * y, shape) correct_tensor = Tensor(np.arange(size).reshape(shape)) incorrect_rank = (2, ) cpd.decompose(tensor=correct_tensor, rank=incorrect_rank)
def decompose(self, tenl, rank): """ Performs Direct fitting using ALS on a list of tensors of order 2 with respect to the specified ``rank``. Parameters ---------- tenl : List(np.ndarray) List of np.ndarray of dimension 2 to be decomposed rank : tuple Desired Kruskal rank for the given ``tensor``. Should contain only one value. If it is greater then any of dimensions then random initialisation is used Returns ------- fmat_u, fmat_s, fmat_v, reconstructed : Tuple(np.ndarray) fmat_u,fmat_s,fmat_v are PARAFAC2 representation of list of tensors reconstructed is the reconstruction of the original tensor directly using fmat_u, fmat_s, fmat_v Notes ----- khatri-rao product should be of matrices in reversed order. But this will duplicate original data (e.g. images) Probably this has something to do with data ordering in Python and how it relates to kr product """ if not isinstance(tenl, list): raise TypeError( "Parameter `tenl` should be a list of np.ndarray objects!") if not all(isinstance(m, np.ndarray) for m in tenl): raise TypeError( "Parameter `tenl` should be a list of np.ndarray objects!") if not isinstance(rank, tuple): raise TypeError("Parameter `rank` should be passed as a tuple!") if len(rank) != 1: raise ValueError( "Parameter `rank` should be tuple with only one value!") self.cost = [] # Reset cost every time when method decompose is called sz = np.array([t.shape for t in tenl]) _m = list(sz[:, 1]) if _m[1:] != _m[:-1]: raise ValueError("Tensors must be of shape I[k] x J") num_t = len(sz) mode_b = _m[0] # Initialisations cpd = CPD(max_iter=1) fmat_h, fmat_v, fmat_s, fmat_u = self._init_fmat(rank, sz) cpd_fmat = None for n_iter in range(self.max_iter): for k in range(num_t): p, _, q = svd(fmat_h.dot(fmat_s[:, :, k]).dot(fmat_v.T).dot( tenl[k].T), rank=rank[0]) fmat_u[k] = q.T.dot(p.T) y = np.zeros((rank[0], mode_b, num_t)) for k in range(num_t): y[:, :, k] = fmat_u[k].T.dot(tenl[k]) fmat = [fmat_h, fmat_v, cpd_fmat] if n_iter == 0: fmat = None decomposed_cpd = cpd.decompose(Tensor(y), rank, factor_mat=fmat) fmat_h, fmat_v, cpd_fmat = decomposed_cpd.fmat cpd_fmat = cpd_fmat.dot(np.diag(decomposed_cpd._core_values)) for k in range(num_t): fmat_s[:, :, k] = np.diag(cpd_fmat[k, :]) reconstructed = [ (fmat_u[k].dot(fmat_h).dot(fmat_s[:, :, k])).dot(fmat_v.T) for k in range(num_t) ] err = np.sum([ np.sum((tenl[k] - reconstructed[k])**2) for k in range(num_t) ]) self.cost.append(err) if self.verbose: print('Iter {}: relative error of approximation = {}'.format( n_iter, self.cost[-1])) # Check termination conditions if self.cost[-1] <= self.epsilon: if self.verbose: print( 'Relative error of approximation has reached the acceptable level: {}' .format(self.cost[-1])) break if self.converged: if self.verbose: print('Converged in {} iteration(s)'.format(len( self.cost))) break if self.verbose and not self.converged and self.cost[-1] > self.epsilon: print('Maximum number of iterations ({}) has been reached. ' 'Variation = {}'.format(self.max_iter, abs(self.cost[-2] - self.cost[-1]))) # TODO: possibly make another structure return fmat_u, fmat_s, fmat_v, reconstructed