赞
踩
目录
1. 赋值给Tensor, TensorFixedSize, TensorMap
tensorflow 的C++ api 中采用了Eigen的Tensor ,因此本文仔细探究一下Eigen 库Tensor的始末。
Tensor(张量) 是多维数组,元素通常是标量,但也支持复杂的元素(如字符串)
见:https://eigen.tuxfamily.org/dox-devel/unsupported/eigen_tensors.html
- #include "./eigen/unsupported/Eigen/CXX11/Tensor"
-
- /*
- Eigen 不同类型 tensor 的构造
- */
- int Eigen_Construct()
- {
-
- //一 Tensor 类
-
- /*
- Tensor 是模板类,模板中一共有4个参数,前三个参数的含义分别如下
-
- template<typename Scalar_, int NumIndices_, int Options_, typename IndexType_>
- class Tensor : public TensorBase<Tensor<Scalar_, NumIndices_, Options_, IndexType_> >
- 1.float 代表Tensor 中存储的数据类型
- 2.NumIndices_ 代表维度,即多维数组的维度,如3代表三维数组
- 3. Options_ 可选参数,决定数据如何存储,如 Eigen::RowMajor
-
-
- */
- //1.创建了一个3维的向量,明确了各个维度的尺寸分别是2,3,4,该向量分配了24个float 空间(24 = 2*3*4)
- Eigen::Tensor<float, 3, Eigen::RowMajor> t_3d(2, 3, 4);
-
- // 重新设置t_3d 向量的尺寸为(3,4,3),可以把他的不同维度设置不同的尺寸,但是维度个数需要一致
- t_3d = Eigen::Tensor<float, 3, Eigen::RowMajor>(3, 4, 3);
-
- //2.创建一个2维的向量,不明确各个维度的尺寸,而是通过数组的形式给出,如下面的维度用{5,7}数组给出
-
- Eigen::Tensor<string, 2> t_2d({ 5, 7 });
-
-
-
- //二 TensorFixedSize类
- /*
- TensorFixedSize<data_type, Sizes<size0, size1, ...>>
- template<typename Scalar_, typename Dimensions_, int Options_, typename IndexType>
- class TensorFixedSize : public TensorBase<TensorFixedSize<Scalar_, Dimensions_, Options_, IndexType> >
- 1.float 代表Tensor 中存储的数据类型
- 2.Dimensions_ 代表各个维度的尺寸
- 3. Options_ 可选参数,决定数据如何存储,如 Eigen::RowMajor
-
- TensorFixedSize 需要在定义时明确各个维度的尺寸,因此运算速度较快
- */
-
- //创建一个4*3 的 float 类型的 Tensor
- Eigen::TensorFixedSize<float, Eigen::Sizes<4, 3>> t_4x3;
-
-
-
-
- //三 TensorMap 类
- /*
- TensorMap<Tensor<data_type, rank>>
-
- template<typename PlainObjectType, int Options_, template <class> class MakePointer_>
- class TensorMap : public TensorBase<TensorMap<PlainObjectType, Options_, MakePointer_> >
- 1.PlainObjectType
- 2.Options_
- 3. MakePointer_
- TensorMap用于在内存上创建一个张量,内存是由代码的另一部分分配和拥有的。它允许把任何一块分配的内存看作一个张量。
- 此类的实例不拥有存储数据的内存。
- 一句话总结:TensorMap 并不拥有内存,只是组织其他Tensor 。
- */
-
- //可以通过传入一块儿内存,不同的维度构造TensorMap
- int storage[128]; // 2 x 4 x 2 x 8 = 128
- Eigen::TensorMap<Eigen::Tensor<int, 4>> t_4d(storage, 2, 4, 2, 8);
- //同一块儿内存可以被看作不同的TensorMap
- Eigen::TensorMap<Eigen::Tensor<int, 2>> t_2d_2(storage, 16, 8);
- Eigen::TensorFixedSize<float, Eigen::Sizes<4, 3>> t_4x3_2;
- Eigen::TensorMap<Eigen::Tensor<float, 1>> t_12(t_4x3.data(), 12);
-
- return 1;
- }
- void visitTensorElement()
- {
- /*
- 1. 通过指定不同的下标来访问元素
- tensorName(index0, index1...)
- */
-
- 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) = rand();
- cout << t_3d(i, j, k) << " ";
- }
- cout << endl;
- }
- cout << endl;
- }
-
-
- // Print elements of a tensor.
- for (int i = 0; i < 2; ++i) {
- cout << t_3d(i, 0, 0) << endl;
- }
-
-
- }
输出结果如下
Tensor 库目前支持两种布局方式:列优先(默认),行优先。目前只有列有限是完全支持的,不推荐使用行优先
表达式的所有参数必须使用相同的布局。试图混合不同的布局将导致编译错误,可以使用swap_layout()方法更改张量或表达式的布局。注意,这也将颠倒维的顺序。
-
- void tensorLayout()
- {
- //如下面分别设置了列优先 和 行优先
- //Eigen::Tensor<float, 3, Eigen::ColMajor> col_major1; // equivalent to Tensor<float, 3>
- //float storage[128]; // 2 x 4 x 2 x 8 = 128
- //Eigen::TensorMap<Eigen::Tensor<float, 3, Eigen::ColMajor> > row_major1(storage, 2, 2, 4, 8);
-
-
- //
- 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; // 默认为colMajor ,layout方式相同,因此可以赋值
- //Eigen::Tensor<float, 2> col_major_result2 = row_major; // layout方式不同,编译出错,错误信息为 error C2338: YOU_MADE_A_PROGRAMMING_MISTAKE
-
- // Simple layout swap
- col_major_result = row_major.swap_layout();
- eigen_assert(col_major_result.dimension(0) == 4);
- eigen_assert(col_major_result.dimension(1) == 2);
-
- }
Eigen 的tensor 库提供了大量的运算操作(数值运算,几何运算),这些运算作为Tensor类的成员函数或者操作符重载。
例如下面的代码计算两个tensor的和
- void tensorCompute()
- {
- Eigen::Tensor<int, 2> t1(2, 2);
- Eigen::Tensor<int, 2> t2(2, 2);
- for (int i = 0;i < 2; i++)
- for (int j = 0; j < 2; j++)
- {
- t1(i, j) = 1;
- t2(i, j) = 2;
- }
-
- Eigen::Tensor<int, 2> t3 = t1 + t2;
- cout << "t1:" << endl << t1 << endl;
- cout << "t2:" << endl << t2 << endl;
-
- cout << "t3:" << endl << t3 << endl;
-
- }
因为张量运算产生张量运算符,c++ auto关键字没有它直观的意义。考虑这两行代码:
- Tensor<float, 3> t3 = t1 + t2;
- auto t4 = t1 + t2;
第一行分配了一个张量t3, 它保留t1 + t2 的结果,第二行,t4实际上是计算t1+t2的 "tree of tensor operators"(就是张量的操作树)
事实上t4 不是一个张量,它不能获取到相应的元素值
- Tensor<float, 3> t3 = t1 + t2;
- cout << t3(0, 0, 0); // OK prints the value of t1(0, 0, 0) + t2(0, 0, 0)
-
- auto t4 = t1 + t2;
- cout << t4(0, 0, 0); // Compilation error!
当使用auto的时候,得到的不是一个张量,而是一个无值的表达式,所以只能使用auto来推迟真正的计算
不幸的是,没有一个单独的底层具体类型来保存未计算的表达式,因此在需要保存未计算的表达式时,必须使用auto
当需要执行真正的计算,并获取表达式的结果时,需要新建一个tensor 来接收auto的值,如下所示
- auto t4 = t1 + t2;
-
- Tensor<float, 3> result = t4; // Could also be: result(t4);
- cout << result(0, 0, 0);
在获取结果之前,我们可以一直用auto来得到无值表达式,这个过程中不会有真正的计算发生(有点类似于tensorflow的静态图思想)
- // One way to compute exp((t1 + t2) * 0.2f);
- auto t3 = t1 + t2;
- auto t4 = t3 * 0.2f;
- auto t5 = t4.exp();
- Tensor<float, 3> result = t5;
-
- // Another way, exactly as efficient as the previous one:
- Tensor<float, 3> result = ((t1 + t2) * 0.2f).exp();
如下几种方法可以控制何时计算表达式:
最常用的方式就是将表达式 赋值给一个Tensor,如下所示
-
- void tensorCompute()
- {
- Eigen::Tensor<int, 2> t1(2, 2);
- Eigen::Tensor<int, 2> t2(2, 2);
- for (int i = 0;i < 2; i++)
- for (int j = 0; j < 2; j++)
- {
- t1(i, j) = 1;
- t2(i, j) = 2;
- }
-
- Eigen::Tensor<int, 2> t3 = t1 + t2;
- cout << "t1:" << endl << t1 << endl;
- cout << "t2:" << endl << t2 << endl;
-
- cout << "t3:" << endl << t3 << endl;
-
-
- auto t4 = t3 * 2; // t4 is an Operation.
- Eigen::Tensor<int, 2> result = t4; // The operations are evaluated
- cout << t4 << endl;
-
-
- }
在计算大型复合表达式时,有时需要告诉Eigen表达式树中的中间值需要提前计算。这是通过插入对表达式操作的eval()方法的调用来实现的
- void tensorCompute2()
- {
- // The previous example could have been written:
- Eigen::Tensor<float, 2> t1(2, 2);
- Eigen::Tensor<float, 2> t2(2, 2);
- for (int i = 0; i < 2; i++)
- for (int j = 0; j < 2; j++)
- {
- t1(i, j) = 1.0f;
- t2(i, j) = 2.0f;
- }
-
- Eigen::Tensor<float, 2> result = ((t1 + t2) * 0.2f).exp();
-
- // If you want to compute (t1 + t2) once ahead of time you can write:
- Eigen::Tensor<float, 2> result2 = ((t1 + t2).eval() * 0.2f).exp();
- cout << result2 << endl;
-
- }
如果你只需要从一个表达式的值中访问几个元素,你可以通过使用TensorRef来避免在整个张量中具体化这个值。
TensorRef是一个用于任何Eigen 操作的包装类,它重载了括号()运算符可以允许访问到表达式中的各个元素,TensorRef很方便,
因为操作表达式本身不提供访问单个元素的方法
- Eigen::TensorRef<Eigen::Tensor<float, 2> > 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);
- void testTensorInfo()
- {
- //1. NumDimensions 输出维度个数
- Eigen::Tensor<float, 2> a(3, 4);
- cout << "Dims " << a.NumDimensions <<endl; // 输出:Dims:2
-
- //2. dimensions() 输出不同维度的size
- //typedef DSizes<Index, NumIndices_> Dimensions;
- const Eigen::Tensor<float, 2>::Dimensions & d = a.dimensions();
-
- cout << "Dim size: " << d.size()<< ", dim 0: " << d[0]
- << ", dim 1: " << d[1] <<endl; //Dim size: 2, dim 0: 3, dim 1: 4
-
- //3. Index dimension(Index n) 输出第n 维的维度
- int dim1 = a.dimension(1);
- cout << "Dim 1: " << dim1 <<endl; // Dim 1: 4
-
- //4. Index size() 输出tensor的总元素个数
-
- cout << "Size: " << a.size() <<endl;
-
- }
相关测试代码见:https://github.com/Mayi-Keiji/EigenTest.git
未完待续~~
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。