Libtorch中的Tensor与Pytorch中的Tensor相对应,是库中最核心的数据结构,是可以在不同设备上运行且支持梯度的张量。使用方式上与Pytorch中的Tensor也很类似,只是在一些Python支持但C++不支持的语法上会有些不同,例如Slice操作。
使用Libtorch前需要包含Libtorch的头文件torch/torch.h
:
#include
用到的很多函数都在此头文件中有声明,而且这些函数的namespace都是torch
,因此都可以以torch::xxx
的形式来调用。
Tensor的创建方式有多种,比如通过字面量创建,通过C++数组创建,通过vector创建,或者通过Libtorch中的函数创建。
通过字面量创建Tensor:
#include
#include
int main(){
torch::Tensor x = torch::tensor({1, 2, 3, 4});
std::cout << x << std::endl;
system("pause");
return 0;
}
---------
1
2
3
4
[ CPULongType{4} ]
通过数组创建Tensor,torch::from_blob()
函数在将数组转成Tensor时会进行reshape:
#include
#include
int main(){
float arr[5] = {1, 2, 3, 4, 5};
torch::Tensor x = torch::from_blob(arr, {1, 5}); // 第一个参数为数组,第二个参数为创建的Tensor shape
std::cout << x << std::endl;
system("pause");
return 0;
}
---------
1 2 3 4 5
[ CPUFloatType{1,5} ]
通过vector创建Tensor:
#include
#include
int main(){
std::vector<float> v = {1.0, 2, 3, 4.0};
torch::Tensor x = torch::from_blob(v.data(), {2, 2});
std::cout << x << std::endl;
system("pause");
return 0;
}
---------
1 2
3 4
[ CPUFloatType{2,2} ]
通过Libtorch中的函数创建Tensor:
#include
#include
int main(){
torch::Tensor x1 = torch::arange(4);
torch::Tensor x2 = torch::arange(12);
x2 = torch::reshape(x2, {3, 4}); // C++中没有tuple类型,因此参数不能用小括号,否则会出错
torch::Tensor x3 = torch::ones(3);
torch::Tensor x4 = torch::ones({3, 3});
torch::Tensor x5 = torch::zeros(4);
torch::Tensor x6 = torch::eye(3);
torch::Tensor x7 = torch::rand({2, 3});
torch::Tensor x8 = torch::randn({1, 5});
std::cout << x1 << std::endl;
std::cout << x2 << std::endl;
std::cout << x3 << std::endl;
std::cout << x4 << std::endl;
std::cout << x5 << std::endl;
std::cout << x6 << std::endl;
std::cout << x7 << std::endl;
std::cout << x8 << std::endl;
system("pause");
return 0;
}
---------
0
1
2
3
[ CPULongType{4} ]
0 1 2 3
4 5 6 7
8 9 10 11
[ CPULongType{3,4} ]
1
1
1
[ CPUFloatType{3} ]
1 1 1
1 1 1
1 1 1
[ CPUFloatType{3,3} ]
0
0
0
0
[ CPUFloatType{4} ]
1 0 0
0 1 0
0 0 1
[ CPUFloatType{3,3} ]
0.1380 0.1055 0.2361
0.4628 0.0153 0.0506
[ CPUFloatType{2,3} ]
0.2509 0.4332 -1.6624 -0.5655 1.4356
[ CPUFloatType{1,5} ]
创建Tensor后,我们经常需要查看它们的一些属性,以判断是否跟预期相符。Libtorch中的Tensor是没有公开可访问的属性的,Tensor的属性信息需要通过属性函数来获取。常见的属性函数如下所示:
#include
#include
int main(){
torch::Tensor x = torch::tensor({{1, 2, 3}, {4, 5, 6}});
auto dim = x.dim(); // 2
auto sizes = x.sizes(); // [2, 3]
auto size_0 = x.size(0); // 2
auto numel = x.numel(); // 6
auto dtype = x.dtype(); // int64
auto device = x.device(); // cpu
std::cout << "dim: \n" << dim << std::endl;
std::cout << "sizes: \n" << sizes << std::endl;
std::cout << "size_0: \n" << size_0 << std::endl;
std::cout << "numel: \n" << numel << std::endl;
std::cout << "dtype: \n" << dtype << std::endl;
std::cout << "device: \n" << device << std::endl;
system("pause");
return 0;
}
---------
dim:
2
sizes:
[2, 3]
size_0:
2
numel:
6
dtype:
__int64
device:
cpu
Libtorch中的Tensor也能像Pytorch中的Tensor一样,通过下标获取元素:
#include
#include
int main(){
torch::Tensor x = torch::tensor({{1, 2, 3}, {4, 5, 6}});
auto ele = x[0][2];
std::cout << ele << std::endl;
system("pause");
return 0;
}
---------
3
[ CPULongType{} ]
除了上述方式,还可以使用Tensor对象的index
函数来获取元素,它的一个优势是支持Slice
。对于单个元素的获取,可以直接使用index({dim_0, dim_1, dim_2})
的方式来索引:
#include
#include
int main(){
torch::Tensor x = torch::rand({1, 3, 4, 4}); // 生成一个4维的Tensor
auto ele1 = x.index({0, 1, 2, 2}); // 通过index方式获取单个元素
auto ele2 = x[0][1][2][2]; // 通过下标方式获取单个元素
std::cout << ele1 << std::endl;
std::cout << ele2 << std::endl;
system("pause");
return 0;
}
---------
0.627059
[ CPUFloatType{} ]
0.627059
[ CPUFloatType{} ]
如果想像Python一样获取x[:, 0:1, 2:, :-1]这样的切片,该怎么在Libtorch中表示呢?这里就需要用到torch::indexing::Slice
对象,来实现Python中的Slice,示例如下:
#include
#include
using namespace torch::indexing; // 使用该命名空间后,在使用Slice时便可直接写成Slice,而无需写成torch::indexing::Slice
int main(){
torch::Tensor x = torch::rand({1, 3, 4, 4}); // 生成一个4维的Tensor
auto ele = x.index({Slice(), Slice(0, 1), Slice(2, None), Slice(None, -1)}); // 等效于Python中的x[:, 0:1, 2:, :-1]
std::cout << ele << std::endl;
system("pause");
return 0;
}
---------
(1,1,.,.) =
0.1014 0.1333 0.8851
0.1299 0.1242 0.1836
[ CPUFloatType{1,1,2,3} ]
Tensor是一个Libtorch的对象,那怎么把它里面的数据拿出来保存到文件中或传给别的函数呢?使用data_ptr
函数就可以:
#include
#include
int main(){
torch::Tensor x = torch::rand({2, 3});
float* data = x.data_prt<float>();
std::cout << data << std::endl;
system("pause");
return 0;
}
---------
00000192CDA7A580
对于单个元素的Tensor,还可以使用item
函数得到具体的数值:
#include
#include
int main(){
torch::Tensor x = torch::tensor({{23.27}});
float value = x.item<float>();
std::cout << value << std::endl;
system("pause");
return 0;
}
---------
23.27
Libtorch中支持float16,float32,float64,int8,int16,int32,int64,uint8这几类的Tensor数据类型,可以用to
函数来进行类型转换:
bar = foo.to(torch::kF16);
bar = foo.to(torch::kF32);
bar = foo.to(torch::kF64);
bar = foo.to(torch::kFloat16);
bar = foo.to(torch::kFloat32);
bar = foo.to(torch::kFloat64);
bar = foo.to(torch::kI8);
bar = foo.to(torch::kI16);
bar = foo.to(torch::kI32);
bar = foo.to(torch::kI64);
bar = foo.to(torch::kInt8);
bar = foo.to(torch::kInt16);
bar = foo.to(torch::kInt32);
bar = foo.to(torch::kInt64);
bar = foo.to(torch::kU8);
bar = foo.to(torch::kUInt8);
设备类型是保存Tensor的设备的种类,由于Libtorch不仅仅支持CPU,还支持各种类型的GPU,因此有很多的设备类型。
Libtorch支持的所有设备类型如下所示:
Variable c10::kCPU
Variable c10::kCUDA
Variable c10::kFPGA
Variable c10::kHIP
Variable c10::kHPU
Variable c10::kIPU
Variable c10::kLazy
Variable c10::kMeta
Variable c10::kMetal
Variable c10::kMPS
Variable c10::kMTIA
Variable c10::kORT
Variable c10::kPrivateUse1
Variable c10::kVE
Variable c10::kVulkan
Variable c10::kXLA
Variable c10::kXPU
需要注意的是,设备与编译时的配置、机器是否支持等情况是强相关的,有些设备的支持也并不好。
很多时候我们需要将Tensor进行形状的修改,Libtorch在这方面也有较多的支持,具体的支持如下:
而且支持torch::reshape
这种静态函数和tensor.reshape
这种对象函数。
#include
#include
int main(){
torch::Tensor x = torch::arange(12);
auto x1 = x.reshape({3, 4}); // 也可以写成torch::reshape({3, 4});
auto x2 = x1.flatten(); // 所有维度展平成一维向量
auto x3 = x1.unsqueeze(0); // 在第0维增加一个维度
auto x4 = x3.squeeze(); // 压缩维度,将所有数值为1的维度删除
auto x5 = x1.transpose(1, 0); // 转置,将x1的第一维放到x5的第0维,将x1的第0维放到x5的第一维
auto x6 = torch::cat({x1, x1}, 1); // 沿着第一维进行拼接
std::cout << x1 << std::endl;
std::cout << x2 << std::endl;
std::cout << x3 << std::endl;
std::cout << x4 << std::endl;
std::cout << x5 << std::endl;
std::cout << x6 << std::endl;
system("pause");
return 0;
}
---------
0 1 2 3
4 5 6 7
8 9 10 11
[ CPULongType{3,4} ]
0
1
2
3
4
5
6
7
8
9
10
11
[ CPULongType{12} ]
(1,.,.) =
0 1 2 3
4 5 6 7
8 9 10 11
[ CPULongType{1,3,4} ]
0 1 2 3
4 5 6 7
8 9 10 11
[ CPULongType{3,4} ]
0 4 8
1 5 9
2 6 10
3 7 11
[ CPULongType{4,3} ]
0 1 2 3 0 1 2 3
4 5 6 7 4 5 6 7
8 9 10 11 8 9 10 11
[ CPULongType{3,8} ]
一个比较特殊的地方是transpose只支持两个轴的交换,多个轴的交换需要调用多次来实现。
Tensor库中,Tensor和Tensor之间的操作是很常见的,比如矩阵的相乘、计算内积、计算外积等,有内置的函数支持能避免很多额外的开发工作。
#include
#include
int main(){
torch::Tensor x = torch::tensor({{1, 2, 3}, {4, 5, 6}});
torch::Tensor y = torch::tensor({{10, 20}, {30, 40}, {50, 60}});
torch::Tensor z = torch::tensor({{10, 20, 30}, {40, 50, 60}});
auto z1 = torch::matmul(x, y); // 矩阵乘法
auto z2 = torch::mul(x, z); // 矩阵点乘,两个矩阵的维度需一致
std::cout << z1 << std::endl;
std::cout << z2 << std::endl;
system("pause");
return 0;
}
---------
220 280
490 640
[ CPULongType{2,2} ]
10 40 90
160 250 360
[ CPULongType{2,3} ]
神经网络是torch的核心模块,常见的一些卷积层、全连接层都可以以函数的形式作用在Tensor上,这里写几个简单的例子:
bar = torch::softmax(foo, -1);
bar = torch::sigmoid(foo);
bar = torch::relu(foo);
bar = torch::gelu(foo);