目录
1. 赋值给Tensor, TensorFixedSize, TensorMap
tensorflow 的C++ api 中采用了Eigen的Tensor ,因此本文仔细探究一下Eigen 库Tensor的始末。
Tensor(张量) 是多维数组,元素通常是标量,但也支持复杂的元素(如字符串)
见:https://eigen.tuxfamily.org/dox-devel/unsupported/eigen_tensors.html
一 不同类型Tensor的构造方式
#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;
}
二 访问Tensor元素
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 布局
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);
}
四 Tensor 运算
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;
}
五 Tensor 运算和C++ “auto” 关键字
因为张量运算产生张量运算符,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, TensorFixedSize, TensorMap
- 调用eval()方法
- 赋值给TensorRef
1. 赋值给Tensor, TensorFixedSize, TensorMap
最常用的方式就是将表达式 赋值给一个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;
}
2.调用eval()函数
在计算大型复合表达式时,有时需要告诉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;
}
3.赋值给TensorRef
如果你只需要从一个表达式的值中访问几个元素,你可以通过使用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);
七 相关API说明
1.Tensor 属性的查询
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
未完待续~~