2.Eigen Tensor详解【一】

目录

一 不同类型Tensor的构造方式

二 访问Tensor元素

三 Tensor 布局

四 Tensor 运算

五 Tensor 运算和C++ “auto” 关键字

六.控制何时执行表达式的真正计算

1. 赋值给Tensor, TensorFixedSize, TensorMap

2.调用eval()函数

3.赋值给TensorRef

 七 相关API说明

1.Tensor 属性的查询


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

未完待续~~

 

  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值