Eigen 高维矩阵运算

2023-01-16 10:27:44 浏览数 (3)

Eigen 官方代码仅支持二维矩阵,但其他贡献值提供了高维矩阵处理类 Tensor

Tensor 类

  • MatrixArray 表示二维矩阵,对于任意维度的矩阵可以使用 Tensor 类(当前最高支持 250 维)
  • 注意:这部分代码是用户提供的,没有获得 Eigen 官方支持,不在官方文档支持的代码包里
  • 官方文档(注明了 unsupported):https://eigen.tuxfamily.org/dox/unsupported/eigen_tensors.html#title15
  • 仓库链接:https://gitlab.com/libeigen/eigen
引用方法
  • Tensor 在 eigenunsupportedEigenCXX11 文件夹下,使用时引用:
代码语言:javascript复制
#include <unsupported/Eigen/CXX11/Tensor>

  • 之后可以使用 Tensor 类的相关部分代码。

创建 Tensor 对象

  • Tensor 也有静态、动态之分,用法和 MatrixArray 不同
动态、静态对象
  • 动态 Tensor 语法:
代码语言:javascript复制
Tensor<data_type, rank>(size0, size1, ...)
Tensor<data_type, rank>(size_array)

  • 静态 Tensor 语法:
代码语言:javascript复制
TensorFixedSize<data_type, Sizes<size0, size1, ...>>

  • 示例代码:
代码语言:javascript复制
int a = 2;
Eigen::Tensor<int, 3> t(a, a, 3);
t.setRandom();

cout << t << endl << endl;

Eigen::TensorFixedSize<float, Sizes<2, 2, 2>> f;
f.setConstant(3);
cout << f << endl << endl;

-->
  -84756595  -556960047  -680927012
 2146412165 -1365162692 -1063466897

  -98229169  -283100286 -1721105372
   52398681 -1246925249 -1728135974

3 3
3 3

3 3
3 3

  • 也可以创建 string 类型的 Tensor 对象:
代码语言:javascript复制
// Create a tensor of strings of rank 2 with sizes 2, 2.
Tensor<string, 2> t_2d({ 2, 2 });
t_2d(0, 0) = "hello";
t_2d(0, 1) = "eigen";
t_2d(1, 0) = "tensor";
t_2d(1, 1) = "world!";
cout << t_2d << endl << endl;

-->
hello  eigen
tensor world!

TensorMap
  • TensorMap 可以从已经分配内存的数据生成 Tensor 对象
  • 语法:
代码语言:javascript复制
TensorMap<Tensor<data_type, rank>>(data, size0, size1, ...)

  • 示例:
代码语言:javascript复制
int storage[36];
TensorMap<Tensor<int, 4>> t_4d(storage, 2, 3, 2, 3);

-->
1219955604 1258332037  304939008
 343989211  347455365  347499254

1219958005 1219958049 1219955516
         2          0          0

 348393272          0 1219965800
         2          0          0

     32758      22260   67094713
     32761      32761      32761

     32758      32758      32758
     32758          0          0

     32761          0      32758
     32761          0          0

初始化

  • Tensor 初始化和 Martix 差不多

方法

语法

示例

指定常数初始化

setConstant(const Scalar& val)

a.setConstant(12.3f);a.setConstant("yolo");

0 值初始化

setZero()

a.setZero();

赋值初始化

setValues({..initializer_list})

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

随机初始化

setRandom()

a.setRandom();

索引数据

单个数据
  • 语法:
代码语言:javascript复制
<data_type> tensor(index0, index1...)

  • 示例:
代码语言:javascript复制
int a = 2;
Eigen::Tensor<int, 3> t(a, a, 3);
t.setRandom();

cout << t(1, 2, 0) << endl << endl;

-->
-283100286

切片
  • 当需要引入成块数据时, Tensor 没有 Matrix 类有那么方便的 block 函数,但是支持切片操作
  • 切片需要设置 offsetextents 两个变量,offset 表示块左上角坐标,extents 表示块维度
  • 语法:
代码语言:javascript复制
slice(const StartIndices& offsets, const Sizes& extents)

  • 示例:
代码语言:javascript复制
int a = 2;
Eigen::Tensor<int, 3> t(a, a, 3);
t.setConstant(2);
cout << t << endl << endl;

const Eigen::Tensor<int, 1>::Dimensions d(2);
Eigen::array<Eigen::Index, 3> offsets = { 0, 0, 0};
Eigen::array<Eigen::Index, 3> extents = { 2, 2, 2};

t.slice(offsets, extents).setZero();

cout << t;

-->
2 2 2
2 2 2

2 2 2
2 2 2

0 0 2
0 0 2

0 0 2
0 0 2

  • 可以看到,原始数据以 [0,0,0]起始的 2 times2times2的区域内都被切片设置成了0,说明切片起了作用,而且切片的数据是引用。
  • 另外一种特殊的切片叫做 chip
  • 语法:
代码语言:javascript复制
slice(const StartIndices& offsets, const Sizes& extents)

  • 示例:
代码语言:javascript复制
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};
Eigen::array<Eigen::Index, 2> extents = {2, 2};
Eigen::Tensor<int, 2> slice = a.slice(offsets, extents);
cout << "a" << endl << a << endl;

-->
a
   0   100   200
 300   400   500
 600   700   800
 900  1000  1100
cout << "slice" << endl << slice << endl;

-->
slice
 300   400
 600   700

引用
  • 如果只需要从表达式的值中访问几个元素,那么可以通过使用 TensorRef 避免在完整张量中具体化该值。
  • TensorRef 是任何特征操作的小包装类。它为()操作符提供重载,允许您访问表达式中的各个值。TensorRef 很方便,因为 Operation 本身不提供访问单个元素的方法。
  • 只有在需要表达式值的子集时才使用 TensorRefTensorRef 只计算您访问的值。但是请注意,如果你要访问所有的值,Tensor 计算将会更快一些。
代码语言:javascript复制
// Create a TensorRef for the expression.  The expression is not
// evaluated yet.
TensorRef<Tensor<float, 3> > ref = ((t1   t2) * 0.2f).exp();

// Use "ref" to access individual elements.  The expression is evaluated
// on the fly.
float at_0 = ref(0, 0, 0);
cout << ref(0, 1, 0);

数组表示
  • Tensor 可以将数据部分以数组指针形式传出来,在数组中修改数据
代码语言:javascript复制
Eigen::Tensor<float, 2> a(3, 4);
float* a_data = a.data();
a_data[0] = 123.45f;
cout << "a(0, 0): " << a(0, 0);
=> a(0, 0): 123.45

表达式计算规则

  • Tensor 的表达式可以仅以操作符而不是计算的结果的形式存在
  • 计算表达式最常用的方法是将其赋给张量,而使用 auto 则可以保留运算过程而不计算结果。
auto 保留计算
  • 在下面的示例中,auto 声明使中间值为 Operations,而不是 Tensors,并且不会导致计算表达式。对张量结果的赋值将导致对所有操作的计算。
代码语言:javascript复制
auto t3 = t1   t2;             // t3 is an Operation.
auto t4 = t3 * 0.2f;           // t4 is an Operation.
auto t5 = t4.exp();            // t5 is an Operation.
Tensor<float, 3> result = t5;  // The operations are evaluated.

  • 如果知道 Operation 值的等级和大小,可以将 Operation 赋值为 TensorFixedSize 而不是张量,这样计算效率能高一点。
代码语言:javascript复制
// We know that the result is a 4x4x2 tensor!
TensorFixedSize<float, Sizes<4, 4, 2>> result = t5;

eval 强制计算
  • 在计算大型复合表达式时,有时需要告诉 Eigen 表达式树中的一个中间值值得提前计算。这是通过插入对表达式 Operationeval()方法的调用来完成的。
代码语言:javascript复制
// The previous example could have been written:
Tensor<float, 3> result = ((t1   t2) * 0.2f).exp();

// If you want to compute (t1   t2) once ahead of time you can write:
Tensor<float, 3> result = ((t1   t2).eval() * 0.2f).exp();

  • 可以避免重复计算: Broadcasting ()表达式导致对 X.max ()表达式进行多次计算:
代码语言:javascript复制
Tensor<...> X ...;
Tensor<...> Y = ((X - X.maximum(depth_dim).reshape(dims2d).broadcast(bcast))
                 * beta).exp();

  • maximum()reshape() 调用之间插入 eval () 调用可以保证maximum() 只计算一次,从而大大加快执行速度。
控制计算设备
  • 张量库提供了诸如收缩和卷积等各种运算的几种实现。这些实现针对不同的环境进行了优化: CPU 上的单线程,CPU 上的多线程,或者使用 Cuda 的 GPU。
  • 可以在指定设备上运行计算功能:
代码语言:javascript复制
Eigen::Tensor<float, 2> c(30, 40);
c.device(...) = a   b;

多线程计算
  • 使用线程池进行计算
代码语言:javascript复制
// Create the Eigen ThreadPool
Eigen::ThreadPool pool(8 /* number of threads in pool */)

// Create the Eigen ThreadPoolDevice.
Eigen::ThreadPoolDevice my_device(&pool, 4 /* number of threads to use */);

// Now just use the device when evaluating expressions.
Eigen::Tensor<float, 2> c(30, 50);
c.device(my_device) = a.contract(b, dot_product_dims);

属性获取

  • 示例数据:
代码语言:javascript复制
Eigen::Tensor<int, 3> t(3, 3, 3);
t.setConstant(2);

属性

语法

示例

所有维度尺寸

.dimensions()

t.dimensions() --> [3, 3, 3]

维度数

.NumDimensions

t.NumDimensions --> 3

指定维度指定

.dimension(Index n)

t.dimension(1) --> 3

数据数量

.size()

t.size() --> 27

  • 正经的 Tensor 对象是可以获取上述属性的,但是 Operation 就不一定了
  • 比较好的办法是用 TensorRef 指向Tensor 对象,以在没有计算时获取其属性。

常用操作

矩阵运算

操作

语法

示例

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

constant(const Scalar& val)

a.constant(2.0f);

生成和当前矩阵一样大的随机数矩阵

random()

a.random();

逐元素加、减、乘、除、负号

, -, *, /, -

a b; -a

逐元素开方

sqrt()

a.sqrt()

逐元素开方取倒数

rsqrt()

a.rsqrt()

逐元素平方

square()

a.square()

逐元素取倒数

inverse()

a.inverse()

逐元素自然指数

exp()

a.exp()

逐元素自然对数

log()

a.log()

逐元素幂运算

pow(p)

a.pow(2)

两个矩阵中逐元素取最大值

cwiseMax(q)

a.cwiseMax(q)

两个矩阵中逐元素取最小值

cwiseMin(q)

a.cwiseMin(q)

根据真假选择矩阵

select(const ThenDerived& thenTensor, const ElseDerived& elseTensor)

f.select(then, else);

所有元素求和

sum()

a.sum()

按指定维度求和

sum(const Dimensions& new_dims)

a.sum(Eigen::array<int, 2>({0, 1}))

所有元素求均值

mean()

a.mean()

按指定维度求均值

mean(const Dimensions& new_dims)

a.mean(Eigen::array<int, 2>({0, 1}))

所有元素最大值

maximum()

a.maximum()

按指定维度求最大值

maximum(const Dimensions& new_dims)

a.maximum(Eigen::array<int, 2>({0, 1}))

所有元素最小值

minimum()

a.minimum()

按指定维度求最小值

minimum(const Dimensions& new_dims)

a.minimum(Eigen::array<int, 2>({0, 1}))

trace()

a.trace()

指定维度迹

trace(const Dimensions& new_dims)

a.trace(Eigen::array<int, 2>({0, 1}))

指定维度积分图

cumsum(const Index& axis)

a.cumsum(1)

指定维度乘积图

cumprod(const Index& axis)

a.cumprod(1)

矩阵反向

reverse()

a.reverse()

矩阵复制

broadcast(const Broadcast& broadcast)

a.broadcast(bcast);

pad 0

pad()

a.pad(paddings)

constant
  • 可以相当加、减、乘、除常数操作
代码语言:javascript复制
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;

-->
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
代码语言:javascript复制
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;
=>
a
1 1 1
1 1 1

b
1.68038   1.5662  1.82329
0.788766  1.59688 0.395103

sum
  • 指定维度求和,两种写法
代码语言:javascript复制
Eigen::Tensor<float, 3> t(3, 3, 3);
t.setConstant(2);

const Eigen::Tensor<int, 2>::Dimensions d(0, 1);
cout << t.sum(d) << endl;

cout << t.sum(Eigen::array<int, 2>({ 0, 1 }));

-->
18 18 18
18 18 18

cumsum
代码语言:javascript复制
// Create a tensor of 2 dimensions
Eigen::Tensor<int, 2> a(2, 3);
a.setValues({{1, 2, 3}, {4, 5, 6}});
// Scan it along the second dimension (1) using summation
Eigen::Tensor<int, 2> b = a.cumsum(1);
// The result is a tensor with the same size as the input
cout << "a" << endl << a << endl << endl;
cout << "b" << endl << b << endl << endl;
=>
a
1 2 3
4 5 6

b
1  3  6
4  9 15

broadcast
代码语言:javascript复制
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;
=>
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

pad
代码语言:javascript复制
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);
paddings[1] = make_pair(2, 3);
Eigen::Tensor<int, 2> b = a.pad(paddings);
cout << "a" << endl << a << endl << "b" << endl << b << endl;
=>
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

逻辑、比较运算

操作

语法

示例

逐元素与 (bool 型 Tensor 对象)

&&

a && b

逐元素或 (bool 型 Tensor 对象)

`

逐元素大于

>

a > b

逐元素不小于

>=

a >= b

逐元素小于

<

a < b

逐元素不大于

<=

a <= b

逐元素等于

==

a == b

逐元素不等于

!=

a != b

所有元素为 True

all()

a.all()

指定维度所有元素为 True

all(const Dimensions& new_dims)

a.all(Eigen::array<int, 2>({0, 1}))

存在元素为 True

any()

a.any()

指定维度存在元素为 True

any(const Dimensions& new_dims)

a.any(Eigen::array<int, 2>({0, 1}))

其他操作

卷积
代码语言:javascript复制
// Compute convolution along the second and third dimension.
Tensor<float, 4, DataLayout> input(3, 3, 7, 11);
Tensor<float, 2, DataLayout> kernel(2, 2);
Tensor<float, 4, DataLayout> output(3, 2, 6, 11);
input.setRandom();
kernel.setRandom();

Eigen::array<ptrdiff_t, 2> dims({1, 2});  // Specify second and third dimension for convolution.
output = input.convolve(kernel, dims);

for (int i = 0; i < 3;   i) {
  for (int j = 0; j < 2;   j) {
    for (int k = 0; k < 6;   k) {
      for (int l = 0; l < 11;   l) {
        const float result = output(i,j,k,l);
        const float expected = input(i,j 0,k 0,l) * kernel(0,0)  
                               input(i,j 1,k 0,l) * kernel(1,0)  
                               input(i,j 0,k 1,l) * kernel(0,1)  
                               input(i,j 1,k 1,l) * kernel(1,1);
        VERIFY_IS_APPROX(result, expected);
      }
    }
  }
}

reshape
  • 语法:
代码语言:javascript复制
reshape(const Dimensions& new_dims)

  • 示例:
代码语言:javascript复制
// Increase the rank of the input tensor by introducing a new dimension
// of size 1.
Tensor<float, 2> input(7, 11);
array<int, 3> three_dims{{7, 11, 1}};
Tensor<float, 3> result = input.reshape(three_dims);

// Decrease the rank of the input tensor by merging 2 dimensions;
array<int, 1> one_dim{{7 * 11}};
Tensor<float, 1> result = input.reshape(one_dim);

shuffle

我运行时报错,怀疑文档和代码不匹配

  • 语法:
代码语言:javascript复制
shuffle(const Shuffle& shuffle)

  • 示例:
代码语言:javascript复制
// Shuffle all dimensions to the left by 1.
Tensor<float, 3> input(20, 30, 50);
// ... set some values in input.
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
  • 语法:
代码语言:javascript复制
stride(const Strides& strides)

  • 示例:
代码语言:javascript复制
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;
=>
b
   0   200
 900  1100

printing
代码语言:javascript复制
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;
==>
    1  2 
    3  4 
    5  6 

    7  8 
    9 10
    11 12

    13 14
    15 16
    17 18

    19 20
    21 22
    23 24

参考资料

  • https://eigen.tuxfamily.org/dox/unsupported/eigen_tensors.html#title15
  • https://gitlab.com/libeigen/eigen

0 人点赞