2. Basic Operations¶
Most operations we offer return results with tf.Tensor
form, except some build-in class methods in our module.
To begin with, load in operation module:
import factorizer.base.ops as ops
2.1. Basic Operations with Matrices¶
Hadamard Products¶
The Hadamard product is the elementwise matrix product. Given matrices \(\mathbf{A}\) and \(\mathbf{B}\), both of size \(\mathit{I} \times \mathit{J}\), their Hadamard product is denoted by \(\mathbf{A} \ast \mathbf{B}\). The result is defined by
For instance, using matrices \(\mathbf{A}\) and \(\mathbf{B}\) defined as
Using DTensor
to store matrices, \(\, \mathbf{A} \ast \mathbf{B}\) can be performed as:
>>> A = DTensor(tf.constant([[1,4,7], [2,5,8],[3,6,9]]))
>>> B = DTensor(tf.constant([[2,3,4], [2,3,4],[2,3,4]]))
>>> result = A*B # result is a DTensor with shape (3,3)
>>> tf.Session().run(result.T)
array([[ 2, 12, 28],
[ 4, 15, 32],
[ 6, 18, 36]], dtype=int32)
Using tf.Tensor
to store matrices, \(\, \mathbf{A} \ast \mathbf{B}\) can be performed as:
>>> A = tf.constant([[1,4,7], [2,5,8],[3,6,9]])
>>> B = tf.constant([[2,3,4], [2,3,4],[2,3,4]])
>>> tf.Session().run(ops.hadamard([A,B]))
array([[ 2, 12, 28],
[ 4, 15, 32],
[ 6, 18, 36]], dtype=int32)
hadamard()
also supports the Hadamard products of more than two matrices:
>>> C = tf.constant(np.random.rand(3,3))
>>> D = tf.constant(np.random.rand(3,3))
>>> tf.Session().run(ops.hadamard([A, B, C, D], skip_matrices_index=[1]))
# the result is equal to tf.Session().run(ops.hadamard([A, C, D]))
Kronecker Products¶
The Kronecker product of matrices \(\, \mathbf{A} \in \mathbb{R}^{\mathit{I} \times \mathit{J}}\) and \(\mathbf{B} \in \mathbb{R}^{\mathit{K} \times \mathit{L}}\) is denoted by \(\mathbf{A} \otimes \mathbf{B}\). The result is a matrix of size \((\mathit{IK}) \times (\mathit{JL})\) (See Kolda’s [1] for more details).
For example, matrices \(\mathbf{A}\) and \(\mathbf{B}\) is defined as
To perform \(\mathbf{A} \otimes \mathbf{B}\) with tf.Tensor
objects:
>>> A = tf.constant([[1,2,3,4],[5,6,7,8],[9,10,11,12]]) # the shape of A is (3, 4)
>>> B = tf.constant([[1,1,1,1,1],[2,2,2,2,2]]) # the shape of B is (2, 5)
>>> tf.Session().run(ops.kron([A, B]))
# the shape of result is (6, 20)
array([[ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4],
[ 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8],
[ 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8],
[10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 14, 14, 14, 14, 14, 16, 16, 16, 16, 16],
[ 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12],
[18, 18, 18, 18, 18, 20, 20, 20, 20, 20, 22, 22, 22, 22, 22, 24, 24, 24, 24, 24]], dtype=int32)
To perform \(\mathbf{B} \otimes \mathbf{A}\):
>>> tf.Session().run(ops.kron([A, B], reverse=True))
# the shape of result is (6, 20)
array([[ 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4, 1, 2, 3, 4],
[ 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8, 5, 6, 7, 8],
[ 9, 10, 11, 12, 9, 10, 11, 12, 9, 10, 11, 12, 9, 10, 11, 12, 9, 10, 11, 12],
[ 2, 4, 6, 8, 2, 4, 6, 8, 2, 4, 6, 8, 2, 4, 6, 8, 2, 4, 6, 8],
[10, 12, 14, 16, 10, 12, 14, 16, 10, 12, 14, 16, 10, 12, 14, 16, 10, 12, 14, 16],
[18, 20, 22, 24, 18, 20, 22, 24, 18, 20, 22, 24, 18, 20, 22, 24, 18, 20, 22, 24]], dtype=int32)
It might seem useless when using reverse=True
to calculate the Kronecker product of two matrices, considering ops.kron([B, A])
also do the same work, but it is considerable efficient to perform \(X_1 \otimes X_2 \otimes \cdots \otimes X_N\) using reverse=True
when
given a list of tf.Tensor
objects matrices = [X_1, X_2, ..., X_N]
:
>>> tf.Session().run(ops.kron(matrices, reverse=True))
If the matrices are given in DTensor
form:
>>> A = DTensor(tf.constant([[1,2,3,4],[5,6,7,8],[9,10,11,12]]))
Then \(\mathbf{A} \otimes \mathbf{B}\) can be performed as:
>>> dtensor_B = DTensor(tf.constant([[1,1,1,1,1],[2,2,2,2,2]]))
>>> tf.Session().run(A.kron(dtensor_B).T) # A.kron(dtensor_B) returns a DTensor
array([[ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4],
[ 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8],
[ 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8],
[10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 14, 14, 14, 14, 14, 16, 16, 16, 16, 16],
[ 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12],
[18, 18, 18, 18, 18, 20, 20, 20, 20, 20, 22, 22, 22, 22, 22, 24, 24, 24, 24, 24]], dtype=int32)
or
>>> tf_B = tf.constant([[1,1,1,1,1],[2,2,2,2,2]])
>>> tf.Session().run(A.kron(tf_B).T) # A.kron(tf_B) returns a DTensor
array([[ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4],
[ 2, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 6, 8, 8, 8, 8, 8],
[ 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 7, 7, 7, 7, 7, 8, 8, 8, 8, 8],
[10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 14, 14, 14, 14, 14, 16, 16, 16, 16, 16],
[ 9, 9, 9, 9, 9, 10, 10, 10, 10, 10, 11, 11, 11, 11, 11, 12, 12, 12, 12, 12],
[18, 18, 18, 18, 18, 20, 20, 20, 20, 20, 22, 22, 22, 22, 22, 24, 24, 24, 24, 24]], dtype=int32)
Khatri-Rao Products¶
The Khatri-Rao product can be expressed in Kronecker product form. Given matrices \(\mathbf{A} \in \mathbb{R}^{\mathit{I} \times \mathit{K}}\) and \(\mathbf{B} \in \mathbb{R}^{\mathit{J} \times \mathit{K}}\) , their Khatri-Rao product is denoted by \(\mathbf{A} \odot \mathbf{B}\). The result is a matrix of size \((\mathit{IJ}) \times (\mathit{K})\) and defined by
Let’s take a look at matrices \(\mathbf{A}\) and \(\mathbf{B}\) defined as
To perform \(\mathbf{A} \odot \mathbf{B}\):
>>> A = tf.constant([[1,2,3,4],[5,6,7,8],[9,10,11,12]]) # the shape of A is (3, 4)
>>> B = tf.constant([[1,1,1,1],[2,2,2,2]]) # the shape of B is (2, 4)
>>> tf.Session().run(ops.khatri([A, B]))
# the shape of the result is (6, 4)
array([[ 1, 2, 3, 4],
[ 2, 4, 6, 8],
[ 5, 6, 7, 8],
[10, 12, 14, 16],
[ 9, 10, 11, 12],
[18, 20, 22, 24]], dtype=int32)
khatri()
function also offers skip_matrices_index
to ignore specific matrices in the computation. For example, given matrices = [A, B, C, D]
to
calculate \(\mathbf{A} \odot \mathbf{B} \odot \mathbf{D}\):
>>> C = tf.constant(np.random.rand(4,4))
>>> D = tf.constant(np.random.rand(5,4))
>>> matrices = [A, B, C, D]
>>> tf.Session().run(ops.khatri(matrices, skip_matrices_index=[2]))
# the shape of the result is (30, 4)
To obtain the result of \(\mathbf{D} \odot \mathbf{C} \odot \mathbf{B} \odot \mathbf{A}\):
>>> tf.Session().run(ops.khatri(matrices, reverse=True))
# the shape of the result is (120, 4)
DTensor
class also offers class method DTensor.khatri()
which accepts only one single DTensor
object or tf.Tensor
object:
>>> A = DTensor(tf.constant([[1,2,3,4],[5,6,7,8],[9,10,11,12]]))
>>> B = tf.constant([[1,1,1,1],[2,2,2,2]])
>>> tf.Session().run(A.khatri(B).T)
# the shape of the result is (6, 4)
array([[ 1, 2, 3, 4],
[ 2, 4, 6, 8],
[ 5, 6, 7, 8],
[10, 12, 14, 16],
[ 9, 10, 11, 12],
[18, 20, 22, 24]], dtype=int32)
2.2. Basic Operations with Tensors¶
Addition & Subtraction¶
Given a DTensor
object, it is easy to perform addition and subtraction.
>>> X = DTensor(tf.constant([[[1,2,3],[4,5,6]],[[7,8,9],[10,11,12]]])) # the shape of tensor X is (2, 2, 3)
>>> Y = DTensor(tf.constant([[[-1,-2,-3],[-4,-5,-6]],[[-7,-8,-9],[-10,-11,-12]]])) # the shape of tensor Y is (2, 2, 3)
>>> sum_X_Y = X + Y # sum_X_Y is a DTensor
>>> sub_X_Y = X - Y # sub_X_Y is a DTensor
>>> tf.Session().run(sum_X_Y.T)
array([[[0, 0, 0],
[0, 0, 0]],
[[0, 0, 0],
[0, 0, 0]]], dtype=int32)
>>> tf.Session().run(sub_X_Y.T)
array([[[ 2, 4, 6],
[ 8, 10, 12]],
[[14, 16, 18],
[20, 22, 24]]], dtype=int32)
The second operand can also be a tf.Tensor
object:
>>> Z = tf.constant([[[-1,-2,-3],[-4,-5,-6]],[[-7,-8,-9],[-10,-11,-12]]]) # the shape of tensor Z is (2, 2, 3)
>>> sum_X_Z = X + Z # sum_X_Z is a DTensor
>>> sub_X_Z = X - Z # sub_X_Z is a DTensor
>>> tf.Session().run(sum_X_Z.T)
array([[[0, 0, 0],
[0, 0, 0]],
[[0, 0, 0],
[0, 0, 0]]], dtype=int32)
>>> tf.Session().run(sub_X_Z.T)
array([[[ 2, 4, 6],
[ 8, 10, 12]],
[[14, 16, 18],
[20, 22, 24]]], dtype=int32)
Inner Products¶
The inner product of two same-sized tensor \(\mathcal{X}, \mathcal{Y} \in \mathbb{R}^{\mathit{I}_1 \times \mathit{I}_2 \times \cdots \times \mathit{I}_N}\) is the sum of products of their entries, which can be denoted as \(\langle \mathcal{X} , \mathcal{Y} \rangle\).
Given tensor \(\mathcal{X}, \mathcal{Y} \in \mathbb{R}^\mathit{3 \times 3 \times 2}\) defined by their frontal slices:
>>> X = tf.constant(np.array([[[1,10],[4,13],[7,16]], [[2,11],[5,14],[8,17]], [[3,12],[6,15],[9,18]]])) # the shape of X is (3, 3, 2)
>>> Y = tf.constant(np.array([[[1,1],[1,1],[1,1]], [[1,1],[1,1],[1,1]], [[1,1],[1,1],[1,1]]])) # the shape of Y is (3, 3, 2)
To calculate \(\langle \mathcal{X} , \mathcal{Y} \rangle\):
>>> tf.Session().run(ops.inner(X, Y))
171
Warning
Notice that ops.inner()
function does not support implicit type-casting, so be careful when using tensors
of different dtype
!
Vectorization & Reconstruction¶
The vectorization of a tensor is ordering the tensor into a vector. And the process transforming the vector back to the tensor is called reconstruction or reshaping.
Take the tensor \(\mathcal{X} \in \mathbb{R}^{\mathit{3} \times \mathit{3} \times \mathit{2}}\) defined before as example.
>>> X = tf.constant(np.array([[[1,10],[4,13],[7,16]], [[2,11],[5,14],[8,17]], [[3,12],[6,15],[9,18]]])) # the shape of X is (3, 3, 2)
>>> vec = ops.vectorize(X)
>>> tf.Session().run(vec)
array([ 1, 10, 4, 13, 7, 16, 2, 11, 5, 14, 8, 17, 3, 12, 6, 15, 9, 18])
To reconstruct the vector:
>>> tf.Session().run(ops.vec_to_tensor(vec,(3,3,2)))
array([[[ 1, 10],
[ 4, 13],
[ 7, 16]],
[[ 2, 11],
[ 5, 14],
[ 8, 17]],
[[ 3, 12],
[ 6, 15],
[ 9, 18]]])
Unfolding & Folding¶
Unfolding, also known as matricization, is the process of reordering the elements of an N -way array into a matrix. Here we call operation mode-n matricization as unfolding in default.
Let the frontal slices of \(\mathcal{X} \in \mathbb{R}^{\mathit{3} \times \mathit{4} \times \mathit{2}}\) be
>>> X = tf.constant([[[1, 13], [4, 16], [7, 19], [10, 22]], [[2, 14], [5, 17], [8, 20], [11, 23]], [[3, 15], [6, 18], [9, 21], [12, 24]]]) # the shape of X is (3, 4, 2)
To get the mode-1 matricization of tensor \(\mathcal{X}\):
>>> tf.Session().run(ops.unfold(X, 0))
array([[ 1, 4, 7, 10, 13, 16, 19, 22],
[ 2, 5, 8, 11, 14, 17, 20, 23],
[ 3, 6, 9, 12, 15, 18, 21, 24]], dtype=int32)
To get the mode-2 matricization of tensor \(\mathcal{X}\):
>>> tf.Session().run(ops.unfold(X, 1))
array([[ 1, 2, 3, 13, 14, 15],
[ 4, 5, 6, 16, 17, 18],
[ 7, 8, 9, 19, 20, 21],
[10, 11, 12, 22, 23, 24]], dtype=int32)
To get the mode-3 matricization of tensor \(\mathcal{X}\):
>>> tf.Session().run(ops.unfold(X, 2))
array([[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
[13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]], dtype=int32)
For a DTensor
object, class method DTensor.unfold()
is available:
>>> X = DTensor(tf.constant([[[1, 13], [4, 16], [7, 19], [10, 22]], [[2, 14], [5, 17], [8, 20], [11, 23]], [[3, 15], [6, 18], [9, 21], [12, 24]]]))
>>> tf.Session().run(X.unfold(mode=0).T) # mode-1 matricization, X.unfold(mode=0) return a DTensor
array([[ 1, 4, 7, 10, 13, 16, 19, 22],
[ 2, 5, 8, 11, 14, 17, 20, 23],
[ 3, 6, 9, 12, 15, 18, 21, 24]], dtype=int32)
General Matricization¶
According to Kolda’s [2], general matricization can flatten a high-order tensor into a matrix with size defined with row indices and column indices.
Given tensor \(\mathcal{X} \in \mathbb{R}^{\mathit{I}_1 \times \mathit{I}_2 \times \cdots \times \mathit{I}_N}\), if we want to rearrange it into a matrix with size \(\mathit{J}_1 \times \mathit{J}_2\),
The set \(\{ r_1, \cdots, r_K \}\) defines those indices that will mapped to the row indices of the resulting matrix and the set \(\{ c_1, \cdots, c_L \}\) defines those indices that will mapped to the column indices.
Note
The order of \(\{ r_1, \cdots, r_K \}\) or \(\{ c_1, \cdots, c_L \}\) is not necessarily ascending or descending.
Take a look at tensor \(\mathcal{X}\) defined as:
>>> X = tf.constant([[[1, 13], [4, 16], [7, 19], [10, 22]], [[2, 14], [5, 17], [8, 20], [11, 23]], [[3, 15], [6, 18], [9, 21], [12, 24]]]) # the shape of X is (3, 4, 2)
To mapped \(\mathcal{X}\) into matrix of size \((4 \times 3) \times 2 = 12 \times 2\):
>>> r_axis = [1,0] # indices of row
>>> c_axis = 2 # indices of column
>>> mat = ops.t2mat(X, r_axis, c_axis) # mat is a tf.Tensor
>>> tf.Session().run(mat)
array([[ 1, 13],
[ 2, 14],
[ 3, 15],
[ 4, 16],
[ 5, 17],
[ 6, 18],
[ 7, 19],
[ 8, 20],
[ 9, 21],
[10, 22],
[11, 23],
[12, 24]], dtype=int32)
function ops.t2mat()
can also perform mode-n matricization mapping indices appropriately:
To perform Kolda-type mode-2 unfolding:
>>> mat1 = ops.t2mat(X, 1, [2,0])
To perform LMV-type mode-2 unfolding:
>>> mat2 = ops.t2mat(X, 1, [0,2])
DTensor
also offers class method:
>>> X = DTensor(tf.constant([[[1, 13], [4, 16], [7, 19], [10, 22]], [[2, 14], [5, 17], [8, 20], [11, 23]], [[3, 15], [6, 18], [9, 21], [12, 24]]])) # the shape of X is (3, 4, 2)
>>> mat3 = X.t2mat([1,0], 2) # mat3 is a DTensor
>>> tf.Session().run(mat3.T)
array([[ 1, 13],
[ 2, 14],
[ 3, 15],
[ 4, 16],
[ 5, 17],
[ 6, 18],
[ 7, 19],
[ 8, 20],
[ 9, 21],
[10, 22],
[11, 23],
[12, 24]], dtype=int32)
The n -mode Products¶
- The n-mode product of a tensor \(\mathcal{X} \in \mathbb{R}^{\mathit{I}_1 \times \mathit{I}_2 \times \cdots \times \mathit{I}_N}\) with
- a matrix \(\mathbf{A} \in \mathbb{R}^{\mathit{J} \times \mathit{I}_n}\) is denoted by \(\mathcal{X} \times_n \mathbf{A}\) and
is of size \(\mathit{I}_1 \times \cdots \times \mathit{I}_{n-1} \times \mathit{J} \times \mathit{I}_{n+1} \times \cdots \times \mathit{I}_N\).
Let the frontal slices of \(\mathcal{X} \in \mathbb{R}^{\mathit{3} \times \mathit{4} \times \mathit{2}}\) be
>>> X = tf.constant([[[1, 13], [4, 16], [7, 19], [10, 22]], [[2, 14], [5, 17], [8, 20], [11, 23]], [[3, 15], [6, 18], [9, 21], [12, 24]]]) # the shape of X is (3, 4, 2)
And Let \(\mathbf{A}\) be
>>> A = tf.constant([[1,3,5], [2,4,6]])
Then the product \(\mathcal{Y} = \mathcal{X} \times_1 \mathbf{A} \in \mathbb{R}^{2 \times 4 \times 2}\) is
Now run code below to perform the calculation:
>>> Y = tf.Session().run(ops.ttm(X,[A],[0]))
>>> Y[:,:,0] # the first frontal slice of Y
array([[ 22, 49, 76, 103],
[ 28, 64, 100, 136]], dtype=int32)
>>> Y[:,:,1] # the second frontal slice of Y
array([[130, 157, 184, 211],
[172, 208, 244, 280]], dtype=int32)
It is often desirable to calculate the prduct of a tensor and a sequence of matrices. Let \(\mathcal{X}\) be an \(\mathbb{R}^{\mathit{I}_1 \times \mathit{I}_2 \times \cdots \times \mathit{I}_N}\) tensor, and let \(\mathbf{A}^{(n)} \in \mathbb{R}^{\mathit{J}_n \times \mathit{I}_n}\) for \(n = 1, 2, \cdots, N\). The the sequence of products
To perform this calculation:
>>> A1 = tf.constant(np.random.rand(J1,I1))
>>> A2 = tf.constant(np.random.rand(J2,I2))
...
>>> AN = tf.constant(np.random.rand(JN,IN))
>>> X = tf.constant(np.random.rand(I1, I2, ..., IN))
>>> seq_A = [A1, A2, ..., AN] # map all matrices into a list
>>> B = ops.ttm(X, seq_A, axis=range(N))
>>> tf.Session().run(B)
If needed, arguments transpose
and skip_matrices_index
are also available.
Tensor Contraction¶
The tensor contraction multiplies two tensors along the given axis. Let tensor \(\mathcal{X} \in \mathbb{R}^{\mathit{I}_1 \times \cdots \times \mathit{I}_M \times \mathit{J}_1 \times \cdots \times \mathit{J}_N}\), and tensor \(\mathcal{Y} \in \mathbb{R}^{\mathit{I}_1 \times \cdots \times \mathit{I}_M \times \mathit{K}_1 \times \cdots \times \mathit{K}_P}\), then multiplying both tensors along the first \(M\) modes can be denoted by \(\mathcal{Z} = \langle \mathcal{X} , \mathcal{Y} \rangle_{ \{ 1, \dots , M; 1, \dots, M \} }\). And the size of \(\mathcal{Z}\) is \(\mathit{J}_1 \times \cdots \times \mathit{J}_N \times \mathit{K}_1 \times \cdots \times \mathit{K}_P\). See Cichocki’s [3] for more details.
To perform tensor contraction:
>>> X = tf.constant(np.random.rand(I1, ..., IM, J1, ..., JN))
>>> Y = tf.constant(np.random.rand(I1, ..., IM, K1, ..., KP))
>>> Z = ops.mul(X, Y, a_axis=[0,1,...,M-1], b_axis=[0,1,...,M-1]) # either a_axis or b_axis can also be tuple or a single integer
>>> tf.Session().run(Z) # Z is a tf.Tensor object
The arguments a_axis
and b_axis
specifying the modes of \(\mathcal{X}\) and \(\mathcal{Y}\) for contraction
are not consecutive necessarily, but the sizes of corresponding dimensions must be equal.
Classic matrix multiplication can also be performed with mul()
:
>>> A = tf.constant(np.random.rand(4,5)) # matrix A with shape (4, 5)
>>> B = tf.constant(np.random.rand(5,4)) # matrix B with shape (5, 4)
>>> C = ops.mul(A, B, 1, 0) # same as tf.matmul(A, B, transpose_a=False, transpose_b=False)
>>> D = ops.mul(A, B, 0, 1) # same as tf.matmul(A, B, transpose_a=True, transpose_b=True)
Class DTensor
also provides class method DTensor.mul()
:
>>> X_dtensor = DTensor(np.random.rand(4,5))
>>> Y_dtensor = DTensor(np.random.rand(5,4))
>>> Z_dtensor = X_dtensor.mul(Y_dtensor, a_axis=1, b_axis=0)
# same as DTensor( tf.matmul(X_dtensor.T, Y_dtensor.T, transpose_a=False, transpose_b=False) )
The argument tensor
of DTensor.mul()
only accepts DTensor
object.
2.3. References¶
[1] | Tamara G. Kolda and Brett W. Bader, “Tensor Decompositions and Applications”, SIAM REVIEW, vol. 51, n. 3, pp. 455-500, 2009. |
[2] | Tamara G. Kolda and Brett W. Bader, “Algorithm 862: MATLAB tensor classes for fast algorithm prototyping”, ACM Trans. Math. Softw, 32 (4): 635-653 (2006) |
[3] | Cichocki, Andrzej. “Era of big data processing: A new approach via tensor networks and tensor decompositions.” arXiv preprint arXiv:1403.2048 (2014). |