前提
我们一直在使用torch的python 包(pytorch)进行相关项目的开发,但是有时候要用到torch的c++ 库(libtorch)进行开发,这里对libtorch中的有关tensor的API做个简单地介绍。libtorch中的函数和pytorch中的函数非常相近,几乎说是一摸一样,pytorch中有的函数,libtorch中也有(当然不全都有),所以在学习时我们要学会举一反三。
在下面的介绍中,我会直接贴出代码,并加以注释。
Libtorch
创建tensor
// 方式一, 指定数据创建,后面的是tensor的类型,{5,5}是数据
torch::Tensor tensor_a = torch::tensor({ 5, 5 }, torch::kFloat32);
// 方式二,利用API创建,{3,3}是大小,可以有torch::randn,torch::zeros,torch::randint等
torch::Tensor tensor_b = torch::randn({ 3,3 }, torch::kFloat16);
// 方式三,从指针创建
char vec[3] = { 1,2,3 };
torch::Tensor tensor_c = torch::from_blob(vec, { 3 }, torch::kInt8);
// 方式四,从文件创建
//torch::Tensor tensor_d = torch::from_file("data.bin");
// 方式5,创建一个空的tensor,大小为2x5
torch::Tensor tensor_e = torch::empty({2, 5})
std::cout << "tensor_a: " << tensor_a << std::endl;
std::cout << "tensor_b: " << tensor_b << std::endl;
std::cout << "tensor_c: " << tensor_c << std::endl;
可以看到,真的和pytorch非常相似。
这里要指出几个点:
- 可以直接使用
std::cout
进行输出查看tensor的值,但是无法在调试时查看到tensor的值,除非直接查看内存。 - pytorch中有的数据类型,这里都有。
{3, 3 }
会被解析为at::IntArrayRef size
这个类型,可以不用明白这个类型具体是如何定义的,但是当遇到一个函数的参数是这个类型时,直接通过花括号构造一个参数传入试试。
查看tensor的属性
torch::Tensor tensor_b = torch::randn({ 3,3 }, torch::kFloat16);
// 维度
int64_t dim = tensor_b.dim();
// shape,要打印的话转为vector
std::vector sizes = tensor_b.sizes().vec();
// 第0维的大小
int64_t size_0 = tensor_b.size(0);
// 元素总数
int64_t nums = tensor_b.numel();
// 类型
std::string tensor_dtype{ tensor_b.dtype().name()};
// device
std::string device = tensor_b.device().str();
// 数据的首地址
void *data_ptr = tensor_b.data_ptr();
std::cout << "tensor_b dim: " << dim << std::endl;
std::cout << "tensor_b sizes: " << sizes << std::endl;
std::cout << "tensor_b size_0: " << size_0 << std::endl;
std::cout << "tensor_b nums: " << nums << std::endl;
std::cout << "tensor_b tensor_dtype: " << tensor_dtype << std::endl;
std::cout << "tensor_b device: " << device << std::endl;
std::cout << "tensor_b data_ptr: " << data_ptr << std::endl;
这里只列出了一些平常我们感兴趣的属性,剩余的没有列出。最后一个是tensor数据的地址,这个我们是肯定要掌握的,因为C语言中指针是一个非常重要的东西。我们对tensor赋值等操作就是在修改这块地址指向的空间的值。
注意:这里我为了方便可视化打印,将Tensor原本的一些返回值进行了转换。
tensor的一些操作
torch::Tensor tensor_b = torch::randn({ 3,3 }, torch::kFloat16);
// 判断tensor类型
assert(tensor_b.dtype() == torch::kFloat);
// reshape
torch::Tensor tensor_c = tensor_b.reshape({ 1,9 });
// 展平
torch::Tensor tensor_d = tensor_b.flatten();
// 改变数据类型
torch::Tensor tensor_e = tensor_b.to(torch::kFloat32);
// 判断tensor是否已经被定义(已经为tensor申请了内存)
bool status = tensor_b.defined(); // status=false
// 将tensor类型转为C语言的基本数据类型
torch::Tensor tensor_f = torch::tensor(5, torch::kFloat32);
float f_value = tensor_f.item<float>();
这里列出的一些基本的tensor操作,当然还有更多的没有列出来。请记住:pytorch和libtorch基本一一对应。
tensor的索引
索引单个元素
// tensor_b 大小为2x3x3
torch::Tensor tensor_b = torch::randn({ 2,3,3 }, torch::kFloat16);
// python写法=tensor_b[1,1,0]
torch::Tensor tensor_c = tensor_b[1][1][0];
std::cout << "tensor_b: " << tensor_b << std::endl;
std::cout << "tensor_c: " << tensor_c << std::endl;
/*
tensor_b: (1,.,.) =
-0.2065 0.6694 -1.0439
0.0078 0.5747 -1.4287
0.2795 0.4495 -0.1368
(2,.,.) =
0.2091 0.0972 -1.2266
0.3740 -0.4724 -0.1450
-0.3074 -1.0127 0.8237
[ CPUHalfType{2,3,3} ]
tensor_c: 0.374023
[ CPUHalfType{} ]
*/
narrow
// tensor_b 大小为2x3x3
torch::Tensor tensor_b = torch::randn({2,3,3 }, torch::kFloat16);
// 取tensor_b的第1维度,起始行为0,长度为2行,python写法=tensor_b[:,0:0+2,:]
torch::Tensor tensor_c = torch::narrow(tensor_b, 1, 0, 2);
std::cout << "tensor_b: " << tensor_b << std::endl;
std::cout << "tensor_c: " << tensor_c << std::endl;
/*
tensor_b: (1,.,.) =
1.1953 0.2032 0.5410
0.5298 0.0441 0.7256
-1.1016 -0.4353 0.8569
(2,.,.) =
0.5645 -0.7280 -0.1956
0.0030 -0.1808 -1.2305
0.6509 0.6396 1.4004
[ CPUHalfType{2,3,3} ]
tensor_c: (1,.,.) =
1.1953 0.2032 0.5410
0.5298 0.0441 0.7256
(2,.,.) =
0.5645 -0.7280 -0.1956
0.0030 -0.1808 -1.2305
[ CPUHalfType{2,2,3} ]
*/
slice
// tensor_b 大小为2x3x3
torch::Tensor tensor_b = torch::randn({ 2,3,3 }, torch::kFloat16);
// 在tensor_b的第1维度(行维度),start为0,end为1,python写法=tensor_b[:,0:1,:]
torch::Tensor tensor_c = torch::slice(tensor_b, 1, 0, 1);
std::cout << "tensor_b: " << tensor_b << std::endl;
std::cout << "tensor_c: " << tensor_c << std::endl;
/*
tensor_b: (1,.,.) =
-0.9067 0.7256 0.0112
-0.8140 1.2236 1.0156
0.3337 -0.5962 0.6602
(2,.,.) =
-1.4971 0.3647 -0.5298
-0.9854 0.4995 -0.8325
-0.4475 1.0420 -0.0307
[ CPUHalfType{2,3,3} ]
tensor_c: (1,.,.) =
-0.9067 0.7256 0.0112
(2,.,.) =
-1.4971 0.3647 -0.5298
[ CPUHalfType{2,1,3} ]
*/
index
上面的narrow
和slice
现在已经很少使用了,似乎是在torch1.5版本后,提供了index
函数来进行tensor的索引。
// tensor_b 大小为2x3x3
torch::Tensor tensor_b = torch::randn({ 2,3,3 }, torch::kFloat16);
// python写法=tensor_b[1,0:2,:]
torch::Tensor tensor_c = tensor_b.index({1, torch::indexing::Slice(0,2), torch::indexing::Slice(torch::indexing::None)});
std::cout << "tensor_b: " << tensor_b << std::endl;
std::cout << "tensor_c: " << tensor_c << std::endl;
这里大概就可以说明index
的使用方式,更多的可以查看官方文档,一定要看这个官方文档,因为文档里规定了python对应C++的索引方式。
tensor的赋值
单个元素赋值
// tensor_b 大小为2x3x3
torch::Tensor tensor_b = torch::randn({ 2,3,3 }, torch::kFloat16);
std::cout << "tensor_b: " << tensor_b << std::endl;
tensor_b[0][0][0] = 5;
std::cout << "tensor_b: " << tensor_b << std::endl;
/*
tensor_b: (1,.,.) =
-0.6973 1.6992 0.7686
-0.1859 0.8291 -0.5312
1.1426 -0.7324 -0.8525
(2,.,.) =
-0.9258 -0.3289 -1.4180
-1.4277 -0.2761 -0.8911
-1.4834 -0.7388 -0.9951
[ CPUHalfType{2,3,3} ]
tensor_b: (1,.,.) =
5.0000 1.6992 0.7686
-0.1859 0.8291 -0.5312
1.1426 -0.7324 -0.8525
(2,.,.) =
-0.9258 -0.3289 -1.4180
-1.4277 -0.2761 -0.8911
-1.4834 -0.7388 -0.9951
[ CPUHalfType{2,3,3} ]
*/
index_put_索引赋值
// tensor_b 大小为2x3x3
torch::Tensor tensor_b = torch::randn({ 2,3,3 }, torch::kFloat32);
std::cout << "tensor_b: " << tensor_b << std::endl;
torch::Tensor tensor_c = torch::ones({ 2,2 });
// python写法:tensor_b[1,:2,:2] = tensor_c
tensor_b.index_put_({ 1, torch::indexing::Slice(torch::indexing::None, 2),torch::indexing::Slice(torch::indexing::None, 2) }, tensor_c);
std::cout << "tensor_b: " << tensor_b << std::endl;
/*
tensor_b: (1,.,.) =
1.1883 1.1363 0.8897
0.1595 0.9172 1.2951
0.1573 2.8680 -0.7158
(2,.,.) =
-0.1757 -0.5980 -0.0633
-2.0957 0.6987 -0.7565
0.4421 -0.0936 -1.5684
[ CPUFloatType{2,3,3} ]
tensor_b: (1,.,.) =
1.1883 1.1363 0.8897
0.1595 0.9172 1.2951
0.1573 2.8680 -0.7158
(2,.,.) =
1.0000 1.0000 -0.0633
1.0000 1.0000 -0.7565
0.4421 -0.0936 -1.5684
[ CPUFloatType{2,3,3} ]
*/
这里其实就是怎么索引,怎么赋值,自己再体会体会。
split和concatenate
torch::Tensor tensor_b = torch::randn({ 2,3,3 }, torch::kFloat32);
std::cout << "tensor_b: " << tensor_b << std::endl;
// 第0个维度拆分,每一份的大小是1,因此可以拆分出两个tensor
std::vector<torch::Tensor> tensor_list = torch::split(tensor_b, 1, 0);
std::cout << "tensor_list_0: " << tensor_list[0] << std::endl;
std::cout << "tensor_list_1: " << tensor_list[1] << std::endl;
// 在第0个维度拼起来
torch::Tensor tensor_c = torch::concatenate(torch::TensorList(tensor_list), 0);
std::cout << "tensor_c: " << tensor_c << std::endl;
/*
tensor_b: (1,.,.) =
-0.4057 0.0450 -0.3379
0.3363 0.2346 0.3055
-1.6528 0.0728 -0.5779
(2,.,.) =
-1.2155 0.0216 0.5399
-1.1364 0.8286 -0.7170
-0.4326 -1.7718 -1.7367
[ CPUFloatType{2,3,3} ]
tensor_list_0: (1,.,.) =
-0.4057 0.0450 -0.3379
0.3363 0.2346 0.3055
-1.6528 0.0728 -0.5779
[ CPUFloatType{1,3,3} ]
tensor_list_1: (1,.,.) =
-1.2155 0.0216 0.5399
-1.1364 0.8286 -0.7170
-0.4326 -1.7718 -1.7367
[ CPUFloatType{1,3,3} ]
tensor_c: (1,.,.) =
-0.4057 0.0450 -0.3379
0.3363 0.2346 0.3055
-1.6528 0.0728 -0.5779
(2,.,.) =
-1.2155 0.0216 0.5399
-1.1364 0.8286 -0.7170
-0.4326 -1.7718 -1.7367
[ CPUFloatType{2,3,3} ]
这里要注意的是:拆分的时候得用一个vector
来保存,拼接的时候要将它转换为torch::TensorList
类型。
总结
这篇博客我们介绍了一些libtorch中tensor的相关操作,请记住:libtorch和pytorch基本上是一一对应,要学会举一反三。