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对象)
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对象
// 通过 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, 如果固定尺寸足够下,就会在栈上分配, 不会在堆上分配和释放内存
// 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
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是数据类型
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})
通过初始化列表赋值 初始化全部元素
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,则按照顺序填充
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)的随机值
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
*/也可以给自己设置随机数生成方式
a.setRandom<RandomGenerator>();Tensor内置了两个随机数生成器
UniformRandomGenerator
NormalRandomGenerator3. 内置数据类型(Built-in Datatypes)
3.1 Dimensions
类似一个整数数组, 可以想数据一样访问单个值,用于表示Tensor的维度
3.1 Index
沿着某个维度索引Tensor
3.1 Scalar
表示Tensor单个元素的数据类型
4. 基本数据访问(Data Access)
4.1 访问数据
通过重载的operator()可以访问指定位置的元素,operator()可以拿到元素的引用,所以可以通过()访问符赋值
// 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)
矩阵的相关运算和赋值必须使用相同的布局,不同布局之间操作会导致无法编译
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计算更快
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实际上用算子的树的形式表示
TensorCwiseBinaryOp<scalar_sum_op>(t1, TensorCwiseUnaryOp<scalar_mul_op>(t2, 0.3f))6.1. C++ “auto” 和 Tensor 操作
Tensor的auto关键字不会返回Tensor计算的结果, 只会返回计算的表达式
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()方法则可
// 对于前面的例子 如果想要提前计算出 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();// 比如这个例子 会更好的理解eval()
// 此时t3还是操作符结果 没有真的计算
auto t3 = (t1 + t2).eval();
// 此时还没有被计算
auto t4 = (t3 * 0.2f).exp();
// 这时候 t3才会被计算为一个中间值 然后继续计算后面的结果
Tensor<float, 3> result = t4;上面的例子不能对性能产生多大影响,下面这个例子, 将会因为多次计算无意义的中间值
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值混乱, 产生错误的结果
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获取值
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计算会更快
// 为表达式创建一个 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() | |
| 反平方根 | rsqrt() | 相当于开平方并取倒数 a.rsqrt()==> |
| 平方 | square() | |
| 逆 | inverse() | 求逆矩阵 |
| 自然指数 | exp() | |
| 对数 | log()/log1p()/log2() | // |
| 绝对值 | abs() | |
| 幂 | pow(ScalarExponent exponent) | Tensor类型是整数,没法执行小数次幂 ==> {a为float/double类型} |
| 数乘 | operator*(Scalar scale) | |
| 最大值 | cwiseMax(Scalar threshold) | 逐元素和threshold比较, 取最大值 |
| 最小值 | cwiseMin(Scalar threshold) | 逐元素和threshold比较, 取最小值 |
| 自定义的一元运算 | unaryExpr(const CustomUnaryOp& func) | CustomUnaryOp是类似于仿函数功能的函数 |
operator-()
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()
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()
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*()
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()
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 生成和当前矩阵一样大的常数矩阵
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 返回和当前矩阵一样大的随机数矩阵
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运算
// 三个矩阵必须相同维度和大小, 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维需要外层循环,内层矩阵乘计算矩阵乘法
// 使用秩为 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
// 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相同
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 = <
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相同
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
支持高维矩阵的叉乘运算
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 |
| 所有元素为 True | all() | a.all() |
| 指定维度所有元素为 True | all(const Dimensions& new_dims) | |
| 存在元素为 True | any() | a.any() |
| 指定维度存在元素为 True | any(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中的元素数
// 通过引入新维度来增加输入张量的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的数据布局
// 列优先二维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时候
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()的结果是一个左值
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]维的大小,该操作也会得到一个左值
// 将所有维度向左移动 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])
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个单位
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: 切片的维度
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得到的结果也是一个左值
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)
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)
广播可以指定在某一维度复制多少次
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填充, 也可以自定义填充值
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)
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()
*/