本章講述張量的常見操作,可以先初步了解,具體在設計損失函數等任務中可以用到。隨時翻閱
張量初始化
libtorch(pytorch c++)的大多數api和pytorch保持一致,因此,libtorch中張量的初始化也和pytorch中的類似。本文介紹四種深度圖像編程需要的初始化方法。
第一種,固定尺寸和值的初始化。
//常見固定值的初始化方式
auto b = torch::zeros({3,4});
b = torch::ones({3,4});
b= torch::eye(4);
b = torch::full({3,4},10);
b = torch::tensor({33,22,11});
pytorch中用[]表示尺寸,而cpp中用{}表示。zeros產生值全為0的張量。ones產生值全為1的張量。eye產生單位矩陣張量。full產生指定值和尺寸的張量。torch::tensor({})也可以產生張量,效果和pytorch的torch.Tensor([])或者torch.tensor([])一樣。
第二種,固定尺寸,隨機值的初始化方法
//隨機初始化
auto r = torch::rand({3,4});
r = torch::randn({3, 4});
r = torch::randint(0, 4,{3,3});
rand產生0-1之間的隨機值,randn取正態分布N(0,1)的隨機值,randint取[min,max)的隨機整型數值。
第三種,從c++的其他數據類型轉換而來
int aa[10] = {3,4,6};
std::vector<float> aaaa = {3,4,6};
auto aaaaa = torch::from_blob(aa,{3},torch::kFloat);
auto aaa = torch::from_blob(aaaa.data(),{3},torch::kFloat);
pytorch可以接受從其他數據類型如numpy和list的數據轉化成張量。libtorch同樣可以接受其他數據指針,通過from_blob函數即可轉換。這個方式在部署中經常用到,如果圖像是opencv加載的,那么可以通過from_blob將圖像指針轉成張量。
第四種,根據已有張量初始化
auto b = torch::zeros({3,4});
auto d = torch::Tensor(b);
d = torch::zeros_like(b);
d = torch::ones_like(b);
d = torch::rand_like(b,torch::kFloat);
d = b.clone();
這里,auto d = torch::Tensor(b)等價於auto d = b,兩者初始化的張量d均受原張量b的影響,b中的值發生改變,d也將發生改變,但是b如果只是張量變形,d卻不會跟着變形,仍舊保持初始化時的形狀,這種表現稱為淺拷貝。zeros_like和ones_like顧名思義將產生和原張量b相同形狀的0張量和1張量,randlike同理。最后一個clone函數則是完全拷貝成一個新的張量,原張量b的變化不會影響d,這被稱作深拷貝。
張量變形
torch改變張量形狀,不改變張量存儲的data指針指向的內容,只改變張量的取數方式。libtorch的變形方式和pytorch一致,有view,transpose,reshape,permute等常用變形。
auto b = torch::full({10},3);
b.view({1, 2,-1});
std::cout<<b;
b = b.view({1, 2,-1});
std::cout<<b;
auto c = b.transpose(0,1);
std::cout<<c;
auto d = b.reshape({1,1,-1});
std::cout<<d;
auto e = b.permute({1,0,2});
std::cout<<e;
.view不是inplace操作,需要加=。變形操作沒太多要說的,和pytorch一樣。還有squeeze和unsqueeze操作,也與pytorch相同。
張量截取
通過索引截取張量,代碼如下
auto b = torch::rand({10,3,28,28});
std::cout<<b[0].sizes();//第0張照片
std::cout<<b[0][0].sizes();//第0張照片的第0個通道
std::cout<<b[0][0][0].sizes();//第0張照片的第0個通道的第0行像素 dim為1
std::cout<<b[0][0][0][0].sizes();//第0張照片的第0個通道的第0行的第0個像素 dim為0
除了索引,還有其他操作是常用的,如narrow,select,index,index_select。
std::cout<<b.index_select(0,torch::tensor({0, 3, 3})).sizes();//選擇第0維的0,3,3組成新張量[3,3,28,28]
std::cout<<b.index_select(1,torch::tensor({0,2})).sizes(); //選擇第1維的第0和第2的組成新張量[10, 2, 28, 28]
std::cout<<b.index_select(2,torch::arange(0,8)).sizes(); //選擇十張圖片每個通道的前8列的所有像素[10, 3, 8, 28]
std::cout<<b.narrow(1,0,2).sizes();//選擇第1維,從0開始,截取長度為2的部分張量[10, 2, 28, 28]
std::cout<<b.select(3,2).sizes();//選擇第3維度的第二個張量,即所有圖片的第2行組成的張量[10, 3, 28]
index需要單獨說明用途。在pytorch中,通過掩碼Mask對張量進行篩選是容易的直接Tensor[Mask]即可。但是c++中無法直接這樣使用,需要index函數實現,代碼如下:
auto c = torch::randn({3,4});
auto mask = torch::zeros({3,4});
mask[0][0] = 1;
std::cout<<c;
std::cout<<c.index({mask.to(torch::kBool)});
張量間操作
拼接和堆疊
auto b = torch::ones({3,4});
auto c = torch::zeros({3,4});
auto cat = torch::cat({b,c},1);//1表示第1維,輸出張量[3,8]
auto stack = torch::stack({b,c},1);//1表示第1維,輸出[3,2,4]
std::cout<<b<<c<<cat<<stack;
到這讀者會發現,從pytorch到libtorch,掌握了[]到{}的變化就簡單很多,大部分操作可以直接遷移。
四則運算操作同理,像對應元素乘除直接用*和/即可,也可以用.mul和.div。矩陣乘法用.mm,加入批次就是.bmm。
auto b = torch::rand({3,4});
auto c = torch::rand({3,4});
std::cout<<b<<c<<b*c<<b/c<<b.mm(c.t());
其他一些操作像clamp,min,max這種都和pytorch類似,仿照上述方法可以自行探索。
分享不易,如果有用請不吝給我一個👍,轉載注明出處:https://allentdan.github.io/
代碼見LibtorchTutorials
