Eigen的Tensor模块在安装头文件的unsupported/Eigen/CXX11/Tensor目录中, 官方提供的文档为Eigen-unsupported: Eigen Tensors, 官方文档为最新版文档, 当前release(VERSON 3.4.0)版本可能还不支持文档中的某些操作. 比如格式化打印等,可能在后续某个版本中开始支持

1. 构造(Constructor)

在使用Tensor类时需要引入头文件 #include <unsupported/Eigen/CXX11/Tensor>

1.1 Tensor类型(动态大小的Tensor对象)

C++
template<typename Scalar_, int NumIndices_, int Options_, typename IndexType_>
class Tensor(size0, size1, ...){}
// Scalar_: Numeric type, 可选 float, double, int ...
// NumIndices_: 维度,最少1维  2,3,4...
// Options_: 内存存储顺序,可选 RowMajor | ColMajor(default) && AutoAlign | DontAlign
// IndexType_: 下标类型 默认为 long

构造一个tensor对象

C++
// 通过 Tensor<data_type, rank>(size0, size1,...) size个数和前面 rank值一致
// 构造一个Rank为3(维度为3)的矩阵 size = (2*3*4)
Eigen::Tensor<float, 3> t_3d(2,3,4);

// 可以给t_3d重新赋值 但是要求 size可以不同,维度必须相同
t_3d = Tensor<float, 3>(3,4,3)

// 通过Tensor<data_type, rank>(size_array) 构造对象
// Eigen::array 类似于std::array
Eigen::array<Eigen::Index, 2> size = {2,2};
Eigen::Tensor<std::string, 2> t_2d(size);

// 所以上面代码也可以这样
Eigen::Tensor<std::string, 2> t_2d(std::array<Eigen::Index, 2>({2, 2}));

// 行优先矩阵
Eigen::Tensor<float, 3, ColMajor> col_major;  // equivalent to Tensor<float, 3>
Eigen::Tensor<float, 3, RowMajor> row_major;

1.2 TensorFixedSize类型(构造固定大小的Tensor对象)

TensorFixedSize编译时大小已知,可以提供能快的计算, 但是不能resize, 如果固定尺寸足够下,就会在栈上分配, 不会在堆上分配和释放内存

C++
// Class TensorFixedSize<data_type, Sizes<size0, size1, ...>>
Eigen::TensorFixedSize<float, Sizes<4, 3>> t_4_3;

1.3 TensorMap类型(从raw buffer转为一个Tensor对象)

会从一个已经分配好的内存生成一个Tensor对象,但是Tensor对象不能resize

C++
int storage[128];  // 2 x 4 x 2 x 8 = 128
Eigen::TensorMap<Eigen::Tensor<int, 4>> t_4d(storage, 2, 4, 2, 8);

// 一个buffer 可以构造多个不同维度的Tensor
Eigen::TensorMap<Eigen::Tensor<int, 2>> t_2d(storage, 16, 8);

// 也可以构造固定大小Tensor
TensorFixedSize<float, Sizes<4, 3>> t_4x3;
TensorMap<Tensor<float, 1>> t_12(t_4x3.data(), 12);

上面提到的Tensor TensorFixedSize TensorMap都是继承自TensorBase,所以构造之后得到的都是TensorBase类型

2. 初始化(Initialization)

2.1 setConstant(const Scalar& val)

初始化为指定常量值val,Scalar是数据类型

C++
Eigen::Tensor<float,2> a(3,4);
a.setConstant(12.5f);
cout << "Constant: " << endl << a << endl << endl;

/**output**
Constant:
12.3 12.3 12.3 12.3
12.3 12.3 12.3 12.3
12.3 12.3 12.3 12.3
*/

2.2 setZero

初始化为0, 相当于setConstant(0)

2.3 setValues({…initializer_list})

通过初始化列表赋值 初始化全部元素

C++
Eigen::Tensor<float, 2> a(2, 3);
a.setValues({{0.0f, 1.0f, 2.0f}, {3.0f, 4.0f, 5.0f}});
cout << "a" << endl << a << endl << endl;

/**output**
a
0 1 2
3 4 5
*/

如果list不够填充全部Tensor,则按照顺序填充

C++
Eigen::Tensor<int, 2> a(2, 3);
a.setConstant(1000);
a.setValues({{10, 20, 30}});
cout << "a" << endl << a << endl;

/**output**
a
10   20   30
1000 1000 1000
*/

2.4 setRandom

初始化为float类型,范围为[-1,1)的随机值

C++
Eigen::Tensor<int, 2> a(3, 4);
a.setRandom();
cout << "Random: " << endl << a << endl << endl;

/**output**
Random:
  0.680375    0.59688  -0.329554    0.10794
 -0.211234   0.823295   0.536459 -0.0452059
  0.566198  -0.604897  -0.444451   0.257742
 */

也可以给自己设置随机数生成方式

C++
a.setRandom<RandomGenerator>();

Tensor内置了两个随机数生成器

C++
UniformRandomGenerator
NormalRandomGenerator

3. 内置数据类型(Built-in Datatypes)

3.1 Dimensions

类似一个整数数组, 可以想数据一样访问单个值,用于表示Tensor的维度

3.1 Index

沿着某个维度索引Tensor

3.1 Scalar

表示Tensor单个元素的数据类型

4. 基本数据访问(Data Access)

4.1 访问数据

通过重载的operator()可以访问指定位置的元素,operator()可以拿到元素的引用,所以可以通过()访问符赋值

C++
// Set the value of the element at position (0, 1, 0);
Eigen::Tensor<float, 3> t_3d(2, 3, 4);
t_3d(0, 1, 0) = 12.0f;
// Initialize all elements to random values.
for (int i = 0; i < 2; ++i) {
    for (int j = 0; j < 3; ++j) {
        for (int k = 0; k < 4; ++k) {
            t_3d(i, j, k) = i + j + k;
        }
    }
}
// Print elements of a tensor.
for (int i = 0; i < 2; ++i) {
    std::cout << t_3d(i, 0, 0);
}

4.2 获取Tensor的属性

属性语法
所有维度尺寸.dimensions()
维度数.NumDimensions
指定维度指定.dimension(Index n)
数据数量.size()

如果表达式还没有被计算,可以用TensorRef指向Tensor对象, 然后获取属性

5. 内存布局(TensorLayout)

矩阵的相关运算和赋值必须使用相同的布局,不同布局之间操作会导致无法编译

C++
Eigen::Tensor<float, 2, Eigen::ColMajor> col_major(2, 4);
Eigen::Tensor<float, 2, Eigen::RowMajor> row_major(2, 4);
Eigen::Tensor<float, 2> col_major_result = col_major;  // ok, layouts match
// Eigen::Tensor<float, 2> col_major_result = row_major;  // will not compile

// 可以通过 swap_layout()实现布局交换 同时也会交换维度顺序
col_major_result = row_major.swap_layout();
eigen_assert(col_major_result.dimension(0) == 4); // 第0个维度 == 4
eigen_assert(col_major_result.dimension(1) == 2);  // 第1个维度 == 2

// 如果想交换布局但是不叫唤维度顺序, 可以通过shuffle处理
// shuffle({1,0}) 表示第0维和第一维交换  第1维和第0维交换
// shuffle() 会生成一个新的Tensor  相当于numpy的transpose()操作 
std::array<int, 2> shuffle({1, 0});
col_major_result = row_major.swap_layout().shuffle(shuffle);
eigen_assert(col_major_result.dimension(0) == 2);
eigen_assert(col_major_result.dimension(1) == 4);

6. 表达式计算规则

Tensor在计算加法的过程是构造一个Eigen::TensorCwiseBinaryOp<scalar_sum_op>(t1, t2)的操作符对象,引用了两个Tensor的对象,和一个sum操作符。只有在结果赋值给新的Tensor对象时才会执行计算,这种计算机制允许惰性的计算和优化,也会使Tensor计算更快

C++
Eigen::Tensor<float, 3> t1(2, 3, 2);
t1.setRandom();
Eigen::Tensor<float, 3> t2(2, 3, 2);
t2.setRandom();

Eigen::Tensor<float, 3> t3 = t1 + t2;
Eigen::Tensor<float, 3> t4 = t1 - t2;

如果是多个嵌套的Tensor算子,例如表达式t1 + t2 * 0.3f实际上用算子的树的形式表示

C++
TensorCwiseBinaryOp<scalar_sum_op>(t1, TensorCwiseUnaryOp<scalar_mul_op>(t2, 0.3f))

6.1. C++ “auto” 和 Tensor 操作

Tensor的auto关键字不会返回Tensor计算的结果, 只会返回计算的表达式

C++
Eigen::Tensor<float, 3> t3 = t1 + t2;
auto t4 = t1 + t2;
// t4 不能得到计算的返回的结果 只会的到上面所说的构造的计算树  等到真正赋值给Tensor的时候才会计算

std::cout << t3(0, 0, 0);  // OK prints the value of t1(0, 0, 0) + t2(0, 0, 0)
std::cout << t4(0, 0, 0);  // Compilation error!

// 比如计算 exp((t1 + t2) * 0.2f)
auto t3 = t1 + t2;
auto t4 = t3 * 0.2f;
auto t5 = t4.exp();
Eigen::Tensor<float, 3> result = t5;  // 只有在这一步才会计算结果

// 当然也可以一步直接计算出来
Eigen::Tensor<float, 3> result = ((t1 + t2) * 0.2f).exp();

6.2. 控制表达式求值的时机

表达式求值的方法

  • 赋值给“Tensor/TensorFixedSize/TensorMap`
  • 使用eval()方法
  • 赋值给一个TensorRef

第一种方法前面介绍了, 不过在给TensorFixedSize赋值时要知道Tensor的rand和size

使用eval()

在大新的表达式复合计算时,有时想提前计算表达式的中间值,通过调用eval()方法则可

C++
// 对于前面的例子 如果想要提前计算出 t1 + t2 可以这样写
Eigen::Tensor<float, 3> result = ((t1 + t2).eval() * 0.2f).exp();


// 相当于将计算的结果保存在一个临时Tensor中
Eigen::TensorFixedSize<float, Sizes<4, 4, 2>> tmp = t1 + t2;
Tensor<float, 3> result = (tmp * 0.2f).exp();
C++
// 比如这个例子 会更好的理解eval()
// 此时t3还是操作符结果 没有真的计算
auto t3 = (t1 + t2).eval();

// 此时还没有被计算
auto t4 = (t3 * 0.2f).exp();

// 这时候 t3才会被计算为一个中间值 然后继续计算后面的结果
Tensor<float, 3> result = t4;

上面的例子不能对性能产生多大影响,下面这个例子, 将会因为多次计算无意义的中间值

C++
Tensor<...> X ...;
Tensor<...> Y = ((X - X.maximum(depth_dim).reshape(dims2d).broadcast(bcast)) * beta).exp();
// 在broadcast时 会多次计算前面的maximum()表达式

// 在 maximum() 之后eval() 会只计算一次maximum()
Tensor<...> Y = ((X - X.maximum(depth_dim).eval().reshape(dims2d).broadcast(bcast)) * beta).exp();

如果Y的结果不止要计算, 还会赋值给Y, 会导致Y值混乱, 产生错误的结果

C++
 Tensor<...> Y ...;
 Y = Y / (Y.sum(depth_dim).reshape(dims2d).broadcast(bcast));

// 可以在sum()和reshape()之间插入eval()保证在完成对Y的更新之前计算好sum
 Y = Y / (Y.sum(depth_dim).eval().reshape(dims2d).broadcast(bcast));

如果需要通过shuffle操作, 则需要在最右侧轻质eval获取值

Bash
 Y.shuffle(...) = (Y / (Y.sum(depth_dim).eval().reshape(dims2d).broadcast(bcast))).eval();

6.3. 结果分配给TensorRef(Assigning to a TensorRef)

如果你只需要从一个表达式的值中访问几个元素,你可以通过使用TensorRef来避免在一个完整的张量中具体化这个值

TensorRef是任何Eigen Operation的小型包装类。它为()运算符提供重载,使您可以访问表达式中的各个值。TensorRef 很方便,因为 Operation 本身不提供访问单个元素的方法。

只有在需要表达式子集的时候才使用TensorRef, TensorRef只计算访问的值,如果是访问所有值,Tensor计算会更快

C++
// 为表达式创建一个 TensorRef。表达式未被评估
TensorRef<Tensor<float, 3> > ref = ((t1 + t2) * 0.2f).exp();

// 使用“ref”访问单个元素。表达式被评估
float at_0 = ref(0, 0, 0);
cout << ref(0, 1, 0);

7. 元素操作(Element-Wise)

7.1. 一元元素操作

输入是一个Tensor, 返回一个对应Tensor相同类型和尺寸的Tensor, 是对Tensor进行逐元素运算

操作语法备注
相反数operator-()Eigen::Tensor<float,2> b = -a;a为二维Tensor
平方根sqrt()a\sqrt{a}
反平方根rsqrt()相当于开平方并取倒数 a.rsqrt()==> 1a\frac{1}{\sqrt{a}}
平方square()
inverse()求逆矩阵
自然指数exp()exe^x
对数log()/log1p()/log2()lnxln^x/loge1x{log_{e-1}}^x/log2x{log_2}^x
绝对值abs()
pow(ScalarExponent exponent)Tensor类型是整数,没法执行小数次幂 ==> a0.5a^{0.5}{a为float/double类型}
数乘operator*(Scalar scale)
最大值cwiseMax(Scalar threshold)逐元素和threshold比较, 取最大值
最小值cwiseMin(Scalar threshold)逐元素和threshold比较, 取最小值
自定义的一元运算unaryExpr(const CustomUnaryOp& func)CustomUnaryOp是类似于仿函数功能的函数

operator-()

C++
Eigen::Tensor<float, 2> a(2, 3);
a.setConstant(1.0f);
Eigen::Tensor<float, 2> b = -a;
cout << "a" << endl << a << endl << endl;
cout << "b" << endl << b << endl << endl;

/**output**
a
1 1 1
1 1 1

b
-1 -1 -1
-1 -1 -1
*/

exp()

C++
Eigen::Tensor<float, 2> a(2, 3);
a.setValues({{1.718281828459045, 2.718281828459045, 3.718281828459045}, {0, 1, 2}});
Eigen::Tensor<float, 2> b = a.log();
Eigen::Tensor<float, 2> c = a.log1p();
Eigen::Tensor<float, 2> d = a.log2();
cout << "a" << endl << a << endl << endl;
cout << "b" << endl << b << endl << endl;
cout << "c" << endl << c << endl << endl;
cout << "d" << endl << d << endl << endl;

/**output**
a
1.71828 2.71828 3.71828
      0       1       2

b
0.541325        1  1.31326
    -inf        0 0.693147

c
       1  1.31326  1.55144
       0 0.693147  1.09861

d
0.780967   1.4427  1.89464
    -inf        0        1
*/

pow()

C++
Eigen::Tensor<int, 2> a(2, 3);
a.setValues({{0, 1, 8}, {27, 64, 125}});
Eigen::Tensor<double, 2> b = a.cast<double>().pow(1.0 / 3.0);
cout << "a" << endl << a << endl << endl;
cout << "b" << endl << b << endl << endl;

/**output**
a
0   1   8
27  64 125

b
0 1 2
3 4 5
*/

operator*()

C++
Eigen::Tensor<int, 2> a(2, 3);
a.setConstant(3);
Eigen::Tensor<int, 2> b = a * 2;
Eigen::Tensor<int, 2> c = 2 * a;

cout << "a" << endl << a << endl << endl;
cout << "b" << endl << b << endl << endl;
cout << "c" << endl << c << endl << endl;

/**output**
a
3 3 3
3 3 3

b
6 6 6
6 6 6

c
6 6 6
6 6 6
*/

cwiseMax() / cwiseMin()

C++
Eigen::Tensor<int, 2> a(2, 3);
a.setValues({{1, 2, 3}, {4, 5, 6}});
Eigen::Tensor<int, 2> b = a.cwiseMax(4);
cout << "a" << endl << a << endl << endl;
cout << "b" << endl << b << endl << endl;

/**output**
a
1 2 3
4 5 6

b
4 4 4
4 5 6
*/

7.2. 二元元素操作

输入是两个Tensor,这两个Tensor应该同类型和尺寸, 结果是一个Tensor和输入Tensor相同类型和尺寸,

操作语法备注
逐元素求和operator+(const OtherDerived& other)
逐元素求差operator-(const OtherDerived& other)
逐元素求乘operator*(const OtherDerived& other)
逐元素求除operator/(const OtherDerived& other)整数不支持除法操作, 可以通过cast() 转为小数操作
对应系数的最大值cwiseMax(const OtherDerived& other)两个矩阵的对应元素的最大值生成的矩阵
对应系数的最小值cwiseMin(const OtherDerived& other)两个矩阵的对应元素的最大值生成的矩阵

7.3. 其他操作

constant 生成和当前矩阵一样大的常数矩阵

C++
Eigen::Tensor<float, 2> a(2, 3);
a.setConstant(1.0f);
Eigen::Tensor<float, 2> b = a + a.constant(2.0f);
Eigen::Tensor<float, 2> c = b * b.constant(0.2f);
cout << "a" << endl << a << endl << endl;
cout << "b" << endl << b << endl << endl;
cout << "c" << endl << c << endl << endl;

/**output**
a
1 1 1
1 1 1

b
3 3 3
3 3 3

c
0.6 0.6 0.6
0.6 0.6 0.6
*/

random 返回和当前矩阵一样大的随机数矩阵

C++
Eigen::Tensor<float, 2> a(2, 3);
a.setConstant(1.0f);
Eigen::Tensor<float, 2> b = a + a.random();
cout << "a" << endl << a << endl << endl;
cout << "b" << endl << b << endl << endl;

/**output**
a
1 1 1
1 1 1

b
1.68038   1.5662  1.82329
0.788766  1.59688 0.395103
*/

select() 三元运算符 相当于Tensor的if-then-else运算

C++
// 三个矩阵必须相同维度和大小, if必须为bool类型。then和else必须为同一类型, 结果也是这个类型
// if 为 true, 输入then的值,否则输入else的值
Tensor<bool, 3> if = ...;
Tensor<float, 3> then = ...;
Tensor<float, 3> else = ...;
Tensor<float, 3> result = if.select(then, else);

contract() Tensor收缩

Tensor收缩是矩阵乘积对多维情况的推广, 这里Tensor收缩和numpy中的高维矩阵matmul的计算不同(大于等于3维的情况下)

  • 小于3维可以使用contract计算矩阵乘法
  • 大于等于3维需要外层循环,内层矩阵乘计算矩阵乘法
C++
// 使用秩为 2 的张量创建 2 个矩阵
Eigen::Tensor<int, 2> a(2, 3);
a.setValues({{1, 2, 3}, {6, 5, 4}});
Eigen::Tensor<int, 2> b(3, 2);
b.setValues({{1, 2}, {4, 5}, {5, 6}});

cout << "a" << endl << a << endl << endl;
cout << "b" << endl << b << endl << endl;

// 计算传统矩阵乘积
Eigen::array<Eigen::IndexPair<int>, 1> product_dims = { Eigen::IndexPair<int>(1, 0) };
Eigen::Tensor<int, 2> AB = a.contract(b, product_dims);

cout << "AB" << endl << AB << endl << endl;

// 计算矩阵转置的乘积
Eigen::array<Eigen::IndexPair<int>, 1> transposed_product_dims = { Eigen::IndexPair<int>(0, 1) };
Eigen::Tensor<int, 2> AtBt = a.contract(b, transposed_product_dims);

cout << "AtBt" << endl << AB << endl << endl;

// 使用双重收缩收缩到标量值。
// 两个张量的第一个坐标和第二个坐标一样收缩,即计算元素的平方和。
Eigen::array<Eigen::IndexPair<int>, 2> double_contraction_product_dims = { Eigen::IndexPair<int>(0, 0), Eigen::IndexPair<int>(1, 1) };
Eigen::Tensor<int, 0> AdoubleContractedA = a.contract(a, double_contraction_product_dims);

cout << "AdoubleContractedA" << endl << AdoubleContractedA << endl << endl;

// 提取张量收缩的标量值以供进一步使用
int value = AdoubleContractedA(0);

/**output**
a
1 2 3
6 5 4

b
1 2
4 5
5 6

AB
24 30
46 61

AtBt
24 30
46 61

AdoubleContractedA
91
*/

矩阵乘法

numpy中 矩阵乘法有两种表示方法

第一种方法A @ B

第二种方法np.matmul(A,B)

二维矩阵可以直接使用contract

C++
// Create 2 matrices using tensors of rank 2
Eigen::Tensor<int, 2> a(2, 3);
a.setValues({{1, 2, 3}, {6, 5, 4}});
Eigen::Tensor<int, 2> b(3, 2);
b.setValues({{1, 2}, {4, 5}, {5, 6}});

// Compute the traditional matrix product
Eigen::array<Eigen::IndexPair<int>, 1> product_dims = { Eigen::IndexPair<int>(1, 0) };
Eigen::Tensor<int, 2> AB = a.contract(b, product_dims);

三维矩阵二维矩阵相乘可以通过

二维矩阵三维矩阵相乘需要将二维升维到三维, 按照三维矩阵乘法操作

支持2维以上的乘法运算 运算规则和Numpy的matmul相同

C++
template <typename Scalar_, int NumIdxs_>
Eigen::Tensor<Scalar_, NumIdxs_> broadcast(const Eigen::Tensor<Scalar_, NumIdxs_>& t,
                                                          const std::array<long, NumIdxs_>& dest) {
    auto dims = t.dimensions();

    std::array<long, NumIdxs_> bcast;
    std::transform(dest.begin(), dest.end(), dims.begin(), bcast.begin(), [](long max, long x) -> long {
        if (max == 1) {
            return 1;
        }
        return max - x + 1 > 0 ? max - x + 1 : 1;
    });
    if (std::all_of(bcast.begin(), bcast.end(), [](long i) { return i == 1; })) {
        return t;
    } else {
        return t.broadcast(bcast);
    }
}

template <typename Scalar_, int NumIdxs_>
Eigen::Tensor<Scalar_, NumIdxs_> matmul(Eigen::Tensor<Scalar_, NumIdxs_>& lt,
                                                       Eigen::Tensor<Scalar_, NumIdxs_>& rt) {
    auto lt_dims = lt.dimensions();
    auto rt_dims = rt.dimensions();
    eigen_assert(lt_dims[NumIdxs_ - 1] == rt_dims[NumIdxs_ - 2]);

    auto product_dims = Eigen::array<Eigen::IndexPair<int>, 1>{Eigen::IndexPair<int>(1, 0)};
    if (rt_dims.size() <= 2) {
        return lt.contract(rt, product_dims);
    }

    bool is_equal = true;
    std::array<long, NumIdxs_> bcast_arr;
    bcast_arr.fill(1);
    for (size_t i = 0; i < NumIdxs_ - 2; ++i) {
        if (lt_dims[i] != rt_dims[i]) {
            is_equal = false;
        }
        bcast_arr[i] = std::max(lt_dims[i], rt_dims[i]);
    }

    // broadcast
    Eigen::Tensor<Scalar_, NumIdxs_>* lt_bcast = &lt;
    Eigen::Tensor<Scalar_, NumIdxs_>* rt_bcast = &rt;

    if (!is_equal) {
        *lt_bcast = broadcast<Scalar_, NumIdxs_>(lt, bcast_arr);
        *rt_bcast = broadcast<Scalar_, NumIdxs_>(rt, bcast_arr);
    }

    auto product = [](const std::array<long, NumIdxs_>& dim) {
        return std::accumulate(dim.begin(), dim.end() - 2, 1, [](long x, long y) { return x * y; });
    };

    auto lt_fdims = lt_bcast->dimensions();
    auto rt_fdims = rt_bcast->dimensions();

    Eigen::array<long, 3> lt_dim({product(lt_fdims), lt_fdims[NumIdxs_ - 2], lt_fdims[NumIdxs_ - 1]});
    Eigen::array<long, 3> rt_dim({product(rt_fdims), rt_fdims[NumIdxs_ - 2], rt_fdims[NumIdxs_ - 1]});

    Eigen::Tensor<Scalar_, 3> lt_dim3 = lt_bcast->reshape(lt_dim);
    Eigen::Tensor<Scalar_, 3> rt_dim3 = rt_bcast->reshape(rt_dim);

    Eigen::Tensor<Scalar_, 3> res_tensor(lt_dim[0], lt_dim[1], rt_dim[2]);

    for (int i = 0; i < lt_dim[0]; ++i) {
        res_tensor.chip(i, 0) = lt_dim3.chip(i, 0).contract(rt_dim3.chip(i, 0), product_dims);
    }

    std::array<long, NumIdxs_> res_arr{1};
    for (size_t i = 0; i < NumIdxs_ - 2; ++i) {
        res_arr[i] = lt_fdims[i];
    }
    res_arr[NumIdxs_ - 2] = lt_fdims[NumIdxs_ - 2];
    res_arr[NumIdxs_ - 1] = rt_fdims[NumIdxs_ - 1];

    return res_tensor.reshape(res_arr);
}

norm()

支持高维矩阵norm运算 运算规则和Numpy相同

C++
template <typename Scalar_, int NumIdxs_>
Eigen::Tensor<Scalar_, NumIdxs_> norm(const Eigen::Tensor<Scalar_, NumIdxs_>& t, const long axis) {
    if (axis == -1) {
        std::array<Eigen::Index, NumIdxs_> size;
        size.fill(1);

        Eigen::Tensor<Scalar_, NumIdxs_> res(size);
        Eigen::Tensor<Scalar_, 0> val = t.square().sum().sqrt();
        res.setConstant(val(0));

        return res;
    }

    std::array<Eigen::Index, NumIdxs_> size = t.dimensions();
    eigen_assert(axis < static_cast<long>(size.size()) && axis >= 0);
    size[axis] = 1;

    std::array<long, 1> dims({axis});
    Eigen::Tensor<Scalar_, NumIdxs_> res = t.square().sum(dims).reshape(size).eval().sqrt();

    return res;
}

cross

支持高维矩阵的叉乘运算

C++
template <typename Scalar_>
Eigen::Tensor<Scalar_, 1> cross1(const Eigen::Tensor<Scalar_, 1>& lt,
                                                const Eigen::Tensor<Scalar_, 1>& rt) {
    // 确保输入张量的维度是(2, 2)
    assert(lt.dimension(0) == 3 && rt.dimension(0) == 3);

    // 计算二维叉积(结果是标量)
    Eigen::Tensor<float, 1> result(3);
    result(0) = lt(1) * rt(2) - lt(2) * rt(1);
    result(1) = lt(2) * rt(0) - lt(0) * rt(2);
    result(2) = lt(0) * rt(1) - lt(1) * rt(0);
    return result;
}

template <typename Scalar_>
Eigen::Tensor<Scalar_, 2> cross2(const Eigen::Tensor<Scalar_, 2>& lt,
                                                const Eigen::Tensor<Scalar_, 2>& rt,
                                                const long axis) {
    eigen_assert(axis < 2 && axis >= 0);
    eigen_assert(lt.dimension(axis) == 2 || lt.dimension(axis) == 3);
    eigen_assert(rt.dimension(axis) == 2 || rt.dimension(axis) == 3);

    auto dims = lt.dimensions();
    Eigen::Tensor<Scalar_, 2> res(dims);

    if (axis == 0) {
        for (int i = 0; i < dims[1]; ++i) {
            Eigen::Tensor<Scalar_, 1> new_lt = lt.chip(i, 1);
            Eigen::Tensor<Scalar_, 1> new_rt = rt.chip(i, 1);
            res.chip(i, 1)                   = cross1(new_lt, new_rt);
        }
    } else {
        for (int i = 0; i < dims[0]; ++i) {
            Eigen::Tensor<Scalar_, 1> new_lt = lt.chip(i, 0);
            Eigen::Tensor<Scalar_, 1> new_rt = rt.chip(i, 0);
            res.chip(i, 0)                   = cross1(new_lt, new_rt);
        }
    }

    return res;
}

template <typename Scalar_>
Eigen::Tensor<Scalar_, 3> cross3(const Eigen::Tensor<Scalar_, 3>& lt,
                                                const Eigen::Tensor<Scalar_, 3>& rt,
                                                const long axis) {
    eigen_assert(axis < 3 && axis >= 0);
    eigen_assert(lt.dimension(axis) == 2 || lt.dimension(axis) == 3);
    eigen_assert(rt.dimension(axis) == 2 || rt.dimension(axis) == 3);

    auto dims = lt.dimensions();
    Eigen::Tensor<Scalar_, 3> res(dims);

    std::array<long, 2> new_dims;
    if (axis == 0) {
        new_dims = {dims[1], dims[2]};
        for (int i = 0; i < dims[1]; ++i) {
            for (int j = 0; j < dims[2]; ++j) {
                Eigen::Tensor<Scalar_, 1> new_lt = lt.chip(i, 1).chip(j, 1);
                Eigen::Tensor<Scalar_, 1> new_rt = rt.chip(i, 1).chip(j, 1);
                res.chip(i, 1).chip(j, 1)        = cross1(new_lt, new_rt);
            }
        }
    } else if (axis == 2) {
        new_dims = {dims[0], dims[1]};
        for (int i = 0; i < dims[0]; ++i) {
            for (int j = 0; j < dims[0]; ++j) {
                Eigen::Tensor<Scalar_, 1> new_lt = lt.chip(i, 0).chip(j, 0);
                Eigen::Tensor<Scalar_, 1> new_rt = rt.chip(i, 0).chip(j, 0);
                res.chip(i, 0).chip(j, 0)        = cross1(new_lt, new_rt);
            }
        }
    } else {
        new_dims = {dims[0], dims[2]};
        for (int i = 0; i < dims[0]; ++i) {
            for (int j = 0; j < dims[2]; ++j) {
                Eigen::Tensor<Scalar_, 1> new_lt = lt.chip(i, 0).chip(j, 1);
                Eigen::Tensor<Scalar_, 1> new_rt = rt.chip(i, 0).chip(j, 1);
                res.chip(i, 0).chip(j, 1)        = cross1(new_lt, new_rt);
            }
        }
    }
    return res;
}

template <typename Scalar_, int NumIdxs_>
Eigen::Tensor<Scalar_, NumIdxs_> cross(const Eigen::Tensor<Scalar_, NumIdxs_>& lt,
                                                      const Eigen::Tensor<Scalar_, NumIdxs_>& rt,
                                                      const long axis) {
    eigen_assert(axis < static_cast<long>(NumIdxs_) && axis >= 0);
    eigen_assert(lt.dimension(axis) == 2 || lt.dimension(axis) == 3);
    eigen_assert(rt.dimension(axis) == 2 || rt.dimension(axis) == 3);

    // TODO 将多维矩阵转为2维度或者3维度
    if (axis == 0) {
        auto dims   = lt.dimensions();
        long t_dim1 = std::accumulate(dims.begin() + 1, dims.end(), 1, [](long x, long y) { return x * y; });

        std::array<long, 2> reshape_dims({dims[0], t_dim1});

        Eigen::Tensor<Scalar_, 2> new_lt = lt.reshape(reshape_dims);
        Eigen::Tensor<Scalar_, 2> new_rt = rt.reshape(reshape_dims);
        Eigen::Tensor<Scalar_, 2> res    = cross2(new_lt, new_rt, 0);
        return res.reshape(dims);
    } else if (axis == NumIdxs_ - 1) {
        // TODO axis == NumIdxs_ - 1
        auto dims   = lt.dimensions();
        long t_dim0 = std::accumulate(dims.begin(), dims.end() - 1, 1, [](long x, long y) { return x * y; });

        std::array<long, 2> reshape_dims({t_dim0, dims[NumIdxs_ - 1]});

        Eigen::Tensor<Scalar_, 2> new_lt = lt.reshape(reshape_dims);
        Eigen::Tensor<Scalar_, 2> new_rt = rt.reshape(reshape_dims);
        Eigen::Tensor<Scalar_, 2> res    = cross2(new_lt, new_rt, 1);
        return res.reshape(dims);
    } else {
        // TODO axis >0 && axis < NumIdxs_ - 1
        auto dims   = lt.dimensions();
        long t_dim0 = std::accumulate(dims.begin(), dims.end() - axis, 1, [](long x, long y) { return x * y; });
        long t_dim2 = std::accumulate(dims.begin() + axis + 1, dims.end(), 1, [](long x, long y) { return x * y; });

        std::array<long, 3> reshape_dims({t_dim0, dims[axis], t_dim2});

        Eigen::Tensor<Scalar_, 3> new_lt = lt.reshape(reshape_dims);
        Eigen::Tensor<Scalar_, 3> new_rt = rt.reshape(reshape_dims);
        Eigen::Tensor<Scalar_, 3> res    = cross3(new_lt, new_rt, 1);

        return res.reshape(dims);
    }
}

cast() 转换Tensor的类型为T

eval() 临时求Tensor的计算结果

8. 逻辑操作

逻辑运算返回一个布尔值的Tensor

操作语法示例
逐元素与 (bool 型 Tensor 对象)&&a && b
逐元素或 (bool 型 Tensor 对象)`
逐元素大于>a > b
逐元素不小于>=a >= b
逐元素小于<a < b
逐元素不大于<=a <= b
逐元素等于==a == b
逐元素不等于!=a != b
所有元素为 Trueall()a.all()
指定维度所有元素为 Trueall(const Dimensions& new_dims)
存在元素为 Trueany()a.any()
指定维度存在元素为 Trueany(const Dimensions& new_dims)

9. 约简操作(Reduction Operations)

Reduction操作会返回比原始Tensor更少维度的Tensor

缩减维度中的值的顺序不影响结果,但是如果你以递增的顺序列出维度,代码可能执行得更快。

操作语法示例
求和sum()沿着所有维度求和, 相当于所有元素求和
sum(const Dimensions& new_dims)沿着指定维度求和, 结果减少一个维度
求均值mean()
mean(const Dimensions& new_dims)
求最大值maximum()
maximum(const Dimensions& new_dims)
求最小值minimum()
minimum(const Dimensions& new_dims)
乘积prod()所有元素的乘积
prod(const Dimensions& new_dims)
判断是否全部真(不为0)all()结果为bool类型的Tensor, 没有短路元素规则,会计算全部内容
all(const Dimensions& new_dims)a.all(Eigen::array<int, 2>({0, 1}))
判断是否含有真(不为0)any()结果为bool类型的Tensor, 没有短路元素规则,会计算全部内容
any(const Dimensions& new_dims)a.any(Eigen::array<int, 2>({0, 1}))
用户定义的Reduction操作reduce(const Dimensions& new_dims, const Reducer& reducer)参考TensorFunctors.h 的 SumReducer操作
trace()所有维度的主对角线元素之和
trace(const Dimensions& new_dims)主对角线元素之和
指定维度求和cumsum(const Index& axis)沿着指定轴(维度)求和
指定维度求积cumprod(const Index& axis)沿着指定轴(维度)求积
卷积convolve(const Kernel& kernel, const Dimensions& dims)

12. 几何操作(Geometrical Operations)

这些操作返回的Tensor与原始Tensor的维数不同。它们可以用来访问Tensor的切片,以不同的维度查看它们,或者用附加数据填充张量

reshape(const Dimensions& new_dims)

新维数组中所有尺寸的乘积必须等于输入Tensor中的元素数

C++
// 通过引入新维度来增加输入张量的rank
// 大小为 1。
Tensor<float, 2> input(7, 11);
Eigen::array<int, 3> three_dims{{7, 11, 1}};
Tensor<float, 3> result = input.reshape(three_dims);

// 通过合并 2 个维度来降低输入张量的等级;
Eigen::array<int, 1> one_dim{{7 * 11}};
Tensor<float, 1> result = input.reshape(one_dim);

这个操作并没有移动输入Tensor中的任何数据,所以reshape Tensor的结果内容取决于原始Tensor的数据布局

C++
// 列优先二维Tensor reshape() 为一维时 按照列优先排列
Eigen::Tensor<float, 2, Eigen::ColMajor> a(2, 3);
a.setValues({{0.0f, 100.0f, 200.0f}, {300.0f, 400.0f, 500.0f}});
Eigen::array<Eigen::DenseIndex, 1> one_dim({3 * 2});
Eigen::Tensor<float, 1, Eigen::ColMajor> b = a.reshape(one_dim);
cout << "b" << endl << b << endl;
/**output**
b
  0
300
100
400
200
500
*/

如果Tensor是RowMajor时候

C++
Eigen::Tensor<float, 2, Eigen::RowMajor> a(2, 3);
a.setValues({{0.0f, 100.0f, 200.0f}, {300.0f, 400.0f, 500.0f}});
Eigen::array<Eigen::DenseIndex, 1> one_dim({3 * 2});
Eigen::Tensor<float, 1, Eigen::RowMajor> b = a.reshape(one_dim);
cout << "b" << endl << b << endl;

/**output**
b
  0
100
200
300
400
500
*/

reshape()的结果是一个左值

C++
Eigen::Tensor<float, 2, Eigen::ColMajor> a(2, 3);
a.setValues({{0.0f, 100.0f, 200.0f}, {300.0f, 400.0f, 500.0f}});
Eigen::array<Eigen::DenseIndex, 2> two_dim({2, 3});
Eigen::Tensor<float, 1, Eigen::ColMajor> b(6);
b.reshape(two_dim) = a;
cout << "b" << endl << b << endl;

// b本身没有reshape 只是对b的reshape视图进行分配
/**output**
b
  0
300
100
400
200
500
*/

shuffle(const Shuffle& shuffle)

相当于numpy的numpy.transpose(),尺寸已经按照指定的排列方式重新排序, 输出张量的第i维等于输入张量的第shuffle[i]维的大小,该操作也会得到一个左值

C++
// 将所有维度向左移动 1。
Tensor<float, 3> input(20, 30, 50);
// ... 在输入中设置一些值。
Tensor<float, 3> output = input.shuffle({1, 2, 0})

eigen_assert(output.dimension(0) == 30);
eigen_assert(output.dimension(1) == 50);
eigen_assert(output.dimension(2) == 20);

stride(const Strides& strides)

返回一个输入Tensor的视图,该视图沿着每个维度进行跨步(跳过stride-1元素)。参数strides是一个索引值的数组。返回的Tensor的尺寸是ceil(input_dimensions[i]/strides[i])

C++
Eigen::Tensor<int, 2> a(4, 3);
a.setValues({{0, 100, 200}, {300, 400, 500}, {600, 700, 800}, {900, 1000, 1100}});
Eigen::array<Eigen::DenseIndex, 2> strides({3, 2});
Eigen::Tensor<int, 2> b = a.stride(strides);
cout << "b" << endl << b << endl;

/**output**
b
   0   200
 900  1100
*/

slice(startIndices, sizes)

返回一个子Tensor, 每个维度都会得到一个offsets[i]和offsets[i] + extents[i]之间存储的值组成的Tensor

  • startIndices: 每个维度的起始位置,比如第一维从0开始,第二维从1开始
  • sizes: 需要切片的长度,比如第一维度切3个单位
C++
Eigen::Tensor<int, 2> a(4,3);
a.setValues({{0, 100, 200}, {300, 400, 500},
             {600, 700, 800}, {900, 1000, 1100}});
Eigen::array<Eigen::Index, 2> offsets = {1, 0};  // 从第1行,第0列开始
Eigen::array<Eigen::Index, 2> extents = {2, 2};  // 第一行切2行,第二列切2列
Eigen::Tensor<int, 2> slice = a.slice(offsets, extents);
cout << "a" << endl << a << endl;
/**output**
a
   0   100   200
 300   400   500
 600   700   800
 900  1000  1100
*/

cout << "slice" << endl << slice << endl;
/**output**
slice
 300   400
 600   700
*/

chip(offset, dim)

也可以写作chip<dim>(offset), 两者没啥区别

一个特殊的slice, 在维度(dim)给定偏移量(offset)处的sub Tensor, 返回的Tensor比输入少一个维度

offset:偏移量

dim: 切片的维度

C++
Eigen::Tensor<int, 2> a(4, 3);
a.setValues(
  {{0, 100, 200}, {300, 400, 500}, {600, 700, 800}, {900, 1000, 1100}});
Eigen::Tensor<int, 1> row_3 = a.chip(2, 0);
Eigen::Tensor<int, 1> col_2 = a.chip(1, 1);
std::cout << "a" << std::endl << a.format(Eigen::TensorIOFormat::Numpy()) << std::endl;
/**output**
a
[[   0  100  200]
[ 300  400  500]
[ 600  700  800]
[ 900 1000 1100]]
*/

std::cout << "row_3" << std::endl << row_3.format(Eigen::TensorIOFormat::Numpy()) << std::endl;
/**output**
row_3
[600 700 800]
*/

std::cout << "col_2" << std::endl << col_2.format(Eigen::TensorIOFormat::Numpy()) << std::endl;
/**output**
row_3
[600 700 800]
*/

chip得到的结果也是一个左值

C++
Eigen::Tensor<int, 1> a(3);
a.setValues({{100, 200, 300}});
Eigen::Tensor<int, 2> b(2, 3);
b.setZero();
b.chip(0, 0) = a;
cout << "a" << endl << a << endl;
/**output**
a
 100
 200
 300
*/
 
cout << "b" << endl << b << endl;
/**output**
b
   100   200   300
     0     0     0
*/

reverse(const ReverseDimensions& reverse)

C++
Eigen::Tensor<int, 2> a(4, 3);
a.setValues({{0, 100, 200}, {300, 400, 500},
            {600, 700, 800}, {900, 1000, 1100}});
// 第一维翻转 第二维不翻转
Eigen::array<bool, 2> reverse({true, false});
Eigen::Tensor<int, 2> b = a.reverse(reverse);
cout << "a" << endl << a << endl << "b" << endl << b << endl;
/**output**
a
   0   100   200
 300   400   500
 600   700   800
 900  1000  1100
b
 900  1000  1100
 600   700   800
 300   400   500
   0   100   200
*/

broadcast(const Broadcast& broadcast)

广播可以指定在某一维度复制多少次

C++
Eigen::Tensor<int, 2> a(2, 3);
a.setValues({{0, 100, 200}, {300, 400, 500}});
Eigen::array<int, 2> bcast({3, 2});
Eigen::Tensor<int, 2> b = a.broadcast(bcast);
cout << "a" << endl << a << endl << "b" << endl << b << endl;

/**output**
a
   0   100   200
 300   400   500
b
   0   100   200    0   100   200
 300   400   500  300   400   500
   0   100   200    0   100   200
 300   400   500  300   400   500
   0   100   200    0   100   200
 300   400   500  300   400   500
*/

concatenate(const OtherDerived& other, Axis axis)

pad(const PaddingDimensions& padding, const Scalar *padding_value = 0)

指定位置默认用0填充, 也可以自定义填充值

C++
Eigen::Tensor<int, 2> a(2, 3);
a.setValues({{0, 100, 200}, {300, 400, 500}});
Eigen::array<pair<int, int>, 2> paddings;
paddings[0] = make_pair(0, 1); // 第0维(行)/左右 前面填充0列 后面填充1列
paddings[1] = make_pair(2, 3); // 第1维(列)/上下 上面填充2行 下面填充3行
Eigen::Tensor<int, 2> b = a.pad(paddings);
cout << "a" << endl << a << endl << "b" << endl << b << endl;
/**output**
a
   0   100   200
 300   400   500
b
   0     0     0    0
   0     0     0    0
   0   100   200    0
 300   400   500    0
   0     0     0    0
   0     0     0    0
   0     0     0    0
*/

extract_patches

extract_image_patches

13. 打印操作(Tensor Printing)

C++
Eigen::Tensor<float, 3> tensor3d = {4, 3, 2};
tensor3d.setValues( {{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}} );
std::cout << tensor3d.format(Eigen::TensorIOFormat::Plain()) << std::endl;
/**output**
     1  2 
     3  4 
     5  6 

     7  8 
     9 10
    11 12

    13 14
    15 16
    17 18

    19 20
    21 22
    23 24
*/

/** format可选参数
Eigen::TensorIOFormat::Plain()
Eigen::TensorIOFormat::Numpy()
Eigen::TensorIOFormat::Native()
Eigen::TensorIOFormat::Legacy()
*/