最近要用C++ 实现一下CRN网络的前向推理过程,了解了一下相关的库,决定使用Eigen提供的Tensor作为数据结构,并使用一些Eigen的常规的库函数。(纯C++实现嵌套的循环太多层了,容易写迷糊了,先降低难度写一波)
Eigen是可以用来进行线性代数、矩阵、向量操作等运算的C++库,它里面包含了很多算法。它的License是MPL2。它支持多平台。
Eigen采用源码的方式提供给用户使用,在使用时只需要包含Eigen的头文件即可进行使用。之所以采用这种方式,是因为Eigen采用模板方式实现,由于模板函数不支持分离编译,所以只能提供源码而不是动态库的方式供用户使用。
在找资料的时候发现了Eigen 矩阵操作与Matlab的对应关系,虽然在这个项目里没有用到,但后面可能会用到,先码住,链接在这。
Eigen下载地址官网在这,直接去官网下载解压包,解压到本地。
我这路径为E:\eigen-3.4.0,版本为3.4.0。
int main() {
Eigen::Vector<double, 3> point(2, 1, 1);
cout << point << endl;
return 0;
}
>>>>>
2
1
1
如果还是不行,可以尝试重启一下编译器,或者File->Reload CMake Project这里只讲Tensor的用法,Matrix 和Vector的教程网上很多。
Tensor数据类型不在Eigen/Core 和Eigen/Dense中,而是需要额外引用unsupported。
#include
简单声明:Tensor声明格式如Eigen::Tensor
Eigen::Tensor<double, 2> a;
std::cout << a << std::endl;
(blank)
在看指定具体大小的情况↓,可以看到为矩阵随机分配了初始值,矩阵只有在指定大小(即为其分配空间时才会产生内容)。通过resize方式,也可以进行初始化
Eigen::Tensor<float, 2> var(2, 3);
cout << var << endl;
-1.83108e+23 -1.82998e+23 0
7.00649e-43 7.00649e-43 0
Eigen::Tensor<float, 2> var;
var.resize(2, 3);
cout << var << endl;
-2.6161e+31 -2.61461e+31 0
5.73131e-43 5.73131e-43 0
几种初始化函数:
常数初始化
var.setConstant(12.3f);
cout << "Constant: " << endl << var << endl << endl;
=>
Constant:
12.3 12.3 12.3
12.3 12.3 12.3
置零初始化
Eigen::Tensor<float, 2> var(2, 3);
var.setZero();
cout << "Constant: " << endl << var << endl << endl;
=>
Constant:
0 0 0
0 0 0
赋值初始化
Eigen::Tensor<float, 2> var(2, 3);
var.setValues({{0.0f, 1.0f, 2.0f},
{3.0f, 4.0f, 5.0f}});
cout << "Constant: " << endl << var << endl << endl;
=>
Constant:
0 1 2
3 4 5
随机初始化
Eigen::Tensor<float, 2> var(2, 3);
var.setRandom();
cout << "Constant: " << endl << var << endl << endl;
Constant:
0.896227 0.872269 0.605188
0.290171 0.24641 0.251816
Eigen::Tensor<float, 2> var(2, 3);
var.setRandom();
cout << "Constant: " << endl << var << endl << endl;
Eigen::array<int, 2> new_shape{3, 2};
auto vat_T = var.reshape(new_shape);
cout << "Constant: " << endl << vat_T << endl << endl;
Constant:
0.896227 0.872269 0.605188
0.290171 0.24641 0.251816
Constant:
0.896227 0.24641
0.290171 0.605188
0.872269 0.251816
Pytorch.reshpe:
0.896227 0.872269
0.605188 0.290171
0.24641 0.251816
auto d0 = std::make_pair(1, 2);
auto d1 = std::make_pair(2, 1);
// auto dims = std::experimental::make_array(d0, d1); // 便利的写法
Eigen::array<std::pair<int, int>, 2> dims{d0, d1};
auto padded = var.pad(dims, -1);
// Eigen::Tensor padded;
// padded = var.pad(dims,-1);
std::cout << padded << std::endl;
=>
Constant:
0.896227 0.872269 0.605188
0.290171 0.24641 0.251816
-1 -1 -1 -1 -1 -1
-1 -1 0.896227 0.872269 0.605188 -1
-1 -1 0.290171 0.24641 0.251816 -1
-1 -1 -1 -1 -1 -1
-1 -1 -1 -1 -1 -1
pad函数不知道能不能扩展到多维,官方示例只给出了二维的样例,我自己在尝试的时候三维就报错了,有没有大佬指点一下。~
Eigen::Tensor<float, 3> var(2, 2, 3);
var.setRandom();
cout << "Constant: " << endl << var << endl << endl;
auto d0 = std::make_pair(0, 1);
auto d1 = std::make_pair(1, 2);
auto d2 = std::make_pair(2, 3);
// auto dims = std::experimental::make_array(d0, d1); // 便利的写法
Eigen::array<std::pair<int, int>, 3> dims{d0, d1, d2};
Eigen::Tensor<float, 3> padded;
**padded = var.pad(dims, -1);** //这边的赋值操作已经报错了,编译不通过
std::cout << padded << std::endl;
error: no matching function for call to 'Eigen::DSizes
::DSizes(const Dimensions&)'128 | m_dimensions = (DSizes ) (DSizes ) static_cast >(m_impl.dimensions());
var.chip<0>(1)
,另一种是参数var.chip(offset=1, dim=0)
。 Eigen::Tensor<float, 2> var(3, 3);
var.setRandom();
cout << "Constant: " << endl << var << endl << endl;
auto c1 = var.chip(1, /*dim=*/0);
std::cout << "dim=0,offset=1: \n" << c1 << std::endl;
auto c2 = var.chip<0>(2);
std::cout << "dim=0,offset=2: \n" << c2 << std::endl;
auto c3 = var.chip(1, 1);
std::cout << "dim=1,offset=1: \n" << c3 << std::endl;
=>
Constant:
0.896227 0.24641 0.259925
0.290171 0.605188 0.354927
0.872269 0.251816 0.827175
dim=0,offset=1:
0.290171
0.605188
0.354927
dim=0,offset=2:
0.872269
0.251816
0.827175
dim=1,offset=1:
0.24641
0.605188
0.251816
chip切片是会降维的,在原来(3,3)上切出第一行,会降维成(3,),不能用(1,3)Tensor来接收
Eigen::Tensor<float, 2> row(1, 3);
**row = var.chip(1, /*dim=*/0);**
=>
报错error: no matching function for call to 'Eigen::DSizes<long long int, 2>::DSizes(const Dimensions&)'
Eigen::Tensor<float, 1> row(3);
row = var.chip(1, /*dim=*/0);
std::cout << "dim=0,offset=1: \n" << row << std::endl;
=>
dim=0,offset=1:
0.290171
0.605188
0.354927
扩展到多维看看,也是能切出来的,只不过这个Col-Major是真难看。
Eigen::Tensor<float, 3> var(2, 3, 3);
var.setRandom();
cout << "Constant: " << endl << var << endl << endl;
auto c1 = var.chip(1, /*dim=*/0);
std::cout << "dim=0,offset=1: \n" << c1 << std::endl;
auto c2 = var.chip(1, /*dim=*/1);
std::cout << "dim=1,offset=1: \n" << c2 << std::endl;
=>
Constant:
0.896227 0.872269 0.605188 0.259925 0.827175 0.224867 0.713551 0.032015 0.0700275
0.290171 0.24641 0.251816 0.354927 0.828251 0.990138 0.0767354 0.757985 0.31538
dim=0,offset=1:
0.290171 0.354927 0.0767354
0.24641 0.828251 0.757985
0.251816 0.990138 0.31538
dim=1,offset=1:
0.872269 0.827175 0.032015
0.24641 0.828251 0.757985
var.slice(offsets, extents)
实现。 Eigen::Tensor<float, 2> var(3, 3);
var.setRandom();
cout << "Constant: " << endl << var << endl << endl;
Eigen::array<int, 2> offstes{1, 1};
Eigen::array<int, 2> extents{2, 2};
auto sliced = var.slice(offstes, extents);
std::cout << "sliced: " << sliced << std::endl;
=>
Constant:
0.896227 0.24641 0.259925
0.290171 0.605188 0.354927
0.872269 0.251816 0.827175
sliced:
0.605188 0.354927
0.251816 0.827175
在来看多维切片,其实是一样的,参数上要指定多维的起始位置与patch。为了方便看,我手动调整了一下输出的格式。
可以看出,slice的切片是不会降维的,chip和slice类似于在pytorch中的a[1,:[]和a[1:2,:]的区别。
Eigen::Tensor<float, 3> var(2, 3, 3);
var.setRandom();
cout << "Constant: " << endl << var << endl << endl;
Eigen::array<int, 3> offstes{1, 1, 1};
Eigen::array<int, 3> extents{1, 2, 2};
auto sliced = var.slice(offstes, extents);
std::cout << "sliced: " << endl << sliced << std::endl;
=>
Constant:
[[[0.896227 0.259925 0.713551]
[0.872269 0.827175 0.032015]
[0.605188 0.224867 0.0700275]]
[[0.290171 0.354927 0.0767354]
[0.24641 0.828251 0.757985]
[0.251816 0.990138 0.31538]]]
sliced:
[[[0.828251 0.757985]]
[[0.990138 0.31538]]]
var.stride(strides);
调用,strides是一个N维的array类型,指定在每一维要跳步的大小。以下是一个简单的二维跳步 Eigen::Tensor<float, 2> var(5, 5);
var.setRandom();
cout << "Constant: " << endl << var << endl << endl;
Eigen::array<int, 2> strides{2, 2};
Eigen::Tensor<float, 2> stride = var.stride(strides);
std::cout << "Stride: " << endl << stride << std::endl;
=>
Constant:
0.896227 0.251816 0.224867 0.757985 0.186436
0.290171 0.259925 0.990138 0.0700275 0.730255
0.872269 0.354927 0.713551 0.31538 0.943635
0.24641 0.827175 0.0767354 0.517878 0.946459
0.605188 0.828251 0.032015 0.694123 0.279912
Stride:
0.896227 0.224867 0.186436
0.872269 0.713551 0.943635
0.605188 0.032015 0.279912
扩展到多维情况,stride函数也不会降维,可以看做是整个多维矩阵按stride进行slice的结果
Eigen::Tensor<float, 3> var(3, 3, 3);
var.setRandom();
cout << "Constant: " << endl << var << endl << endl;
Eigen::array<int, 3> strides{2, 2, 2};
Eigen::Tensor<float, 3> stride = var.stride(strides);
std::cout << "Stride: " << endl << stride << std::endl;
=>
Constant:
[[[0.896227 0.828251 0.517878]
[ 0.24641 0.713551 0.730255]
[0.259925 0.757985 0.279912]]
[[0.290171 0.224867 0.694123]
[ 0.605188 0.0767354 0.943635]
[ 0.354927 0.0700275 0.446424]]
[[0.872269 0.990138 0.186436]
[0.251816 0.032015 0.946459]
[0.827175 0.31538 0.767464]]]
Stride:
[[[0.896227 0.517878]
[0.259925 0.279912]]
[[0.872269 0.186436]
[0.827175 0.767464]]]
跳步切片-sliceStride
下面这个切片就是多维矩阵的跳步切片,其结果与stride的结果是一致的。
跳步切片通过var.stridedSlice(starts, stops, strides)
实现,其中
start是进行切片的起始位置,是一个Eigen::array
stop是切片的终止位置,类型与start一致,不是切片的结果个数,与slice中的patch不同。slice中的patch是输出结果的大小(因为没有stride)。而sliceStride中,若从[start=0,stop=5,stride=2]中切片,输出大小为3.
stride是各维度的跳步大小,类型与start一致
Eigen::Tensor<float, 3> var(3, 3, 3);
var.setRandom();
cout << "Constant: " << endl << var << endl << endl;
Eigen::array<int64_t, 3> starts{0, 0, 0}; // 必须用 long, 不能用 int, 为什么呢7302
Eigen::array<int64_t, 3> stops{3, 3, 3};
Eigen::array<int64_t, 3> strides{2, 2, 2};
auto region = var.stridedSlice(starts, stops, strides);
std::cout << region << std::endl;
a.contract(b, dims)
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, 6},
{5, 6}});
cout << "a:" << endl << a << endl;
cout << "b:" << endl << b << endl;
// 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);
cout << "AB:" << endl << AB << endl;
// Compute the product of the transpose of the matrices
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 << AtBt << endl;
=>
a:
1 2 3
6 5 4
b:
1 2
4 6
5 6
AB:
24 32
46 66
AtBt:
13 40 41
12 38 40
11 36 39
AdoubleContractedA:
91
扩展到三维情况(不知道能不能行),试了一下,不行!。。
Eigen::Tensor<int, 3> m(2, 3, 3);
m.setValues(
{
{{1, 2, 3},
{4, 5, 6},
{7, 8, 9}},
{{10, 11, 12},
{13, 14, 15},
{16, 17, 18}}
});
Eigen::array<int, 3> shuffling({ 2, 1, 0});
Eigen::Tensor<int, 3> transposed = m.shuffle(shuffling);
=> transposed
1,10
4,13
7,16
2,11
5,14
8,17
3,12
6,15
9,18
void print(Eigen::Tensor<float_t, 4> input) {
const Eigen::Tensor<size_t, 4>::Dimensions &dim_inp = input.dimensions();
cout << "Variable:" << endl;
cout << "[";
for (int i = 0; i < dim_inp[0]; i++) {
if (i > 0) {
cout << " ";
}
cout << "[";
for (int j = 0; j < dim_inp[1]; j++) {
if (j > 0) {
cout << " ";
}
cout << "[";
for (int k = 0; k < dim_inp[2]; k++) {
if (k > 0) {
cout << " ";
}
cout << "[";
for (int l = 0; l < dim_inp[3]; l++) {
cout << input(i, j, k, l);
if (l < dim_inp[3] - 1) {
cout << "\t";
}
}
cout << "]";
if (k < dim_inp[2] - 1) {
cout << "," << endl;
}
}
cout << "]";
if (j < dim_inp[1] - 1) {
cout << endl << endl;
}
}
cout << "]";
if (i < dim_inp[0] - 1) {
cout << endl;
}
}
cout << "]";
}
在网上发现了Tensor.transpose()在Eigen里是叫做Tensor.suffle,在上一节进行了更新
我的目的是想实现与pytorch中的tensor.transpose的效果,主要用于在输入LSTM之前,对C和T维度进行置换
如果用循环的话,其实就是在幅值的时候把要置换的维度下标换一下就可以了。
在函数中,我用trans_idx存储了变换后的维度位置,如要从a(1,2,3,4)变换为b(1,3,2,4),即置换1,2维,则传入[0,2,1,3],表示原来维度变换后所在的维度。(有点绕,可以按照torch.permute的参数进行理解,只不过我这里只能置换2维,理论上多维置换应该是可以通过嵌套实现的,我这里就不列出了)
Eigen::Tensor<float_t, 4> transpose(Eigen::Tensor<float_t, 4> &input, Eigen::array<int64_t, 4> trans_idx) {
const Eigen::Tensor<size_t, 4>::Dimensions &dim_inp = input.dimensions();
Eigen::Tensor<size_t, 4>::Dimensions dim_out;
for (int i = 0; i < 4; i++) {
dim_out[i] = dim_inp[trans_idx[i]];
cout << dim_out[i] << endl;
}
Eigen::Tensor<float_t, 4> output(dim_out[0], dim_out[1], dim_out[2], dim_out[3]);
for (int64_t i = 0; i < dim_out[0]; i++) {
for (int64_t j = 0; j < dim_out[1]; j++) {
for (int64_t k = 0; k < dim_out[2]; k++) {
for (int64_t l = 0; l < dim_out[3]; l++) {
int64_t idx_inp[4] = {i, j, k, l};
output(i, j, k, l) = input(idx_inp[trans_idx[0]], idx_inp[trans_idx[1]], idx_inp[trans_idx[2]],
idx_inp[trans_idx[3]]);
}
}
}
}
return output;
}
这里和pytorch的transpose对比了一下,结果一致,这里放上pytorch的结果。
tensor([[[[ 1, 2, 3, 4],
[ 5, 6, 7, 8],
[ 9, 10, 11, 12]],
[[13, 14, 15, 16],
[17, 18, 19, 20],
[21, 22, 23, 24]]]])
tensor([[[[ 1, 2, 3, 4],
[13, 14, 15, 16]],
[[ 5, 6, 7, 8],
[17, 18, 19, 20]],
[[ 9, 10, 11, 12],
[21, 22, 23, 24]]]])
print(output1);
Eigen::array<Eigen::DenseIndex, 3> one_dim({1, 3, 4 * 2});
Eigen::Tensor<float, 3, Eigen::ColMajor> reshaped;
reshaped = output1.reshape(one_dim);
cout << endl;
for (int i = 0; i < 1; i++) {
for (int j = 0; j < 3; j++) {
for (int k = 0; k < 8; k++) {
cout << reshaped(i, j, k) << " ";
}
cout << endl;
}
}
Output1:
[[[[1 2 3 4],
[13 14 15 16]]
[[5 6 7 8],
[17 18 19 20]]
[[9 10 11 12],
[21 22 23 24]]]]
reshaped:
1 13 2 14 3 15 4 16
5 17 6 18 7 19 8 20
9 21 10 22 11 23 12 24
看样子是不行了,那手写个
参考链接:
[1] Eigen库下载与使用
[2] Eigen Tenosr的库函数基础使用