Tensor 建立 API¶
此筆記描述如何在 PyTorch C++ API 中建立 Tensor。它重點介紹可用的工廠函式,這些函式會根據某種演算法來填入新的 Tensor,並列出可用的選項來配置新 Tensor 的形狀、資料類型、裝置和其他屬性。
工廠函式¶
工廠函式 是產生新 Tensor 的函式。PyTorch (Python 和 C++ 中) 中有很多工廠函式可用,它們在初始化新 Tensor 然後傳回它的方式上有所不同。所有工廠函式都遵循以下一般「架構」
torch::<function-name>(<function-specific-options>, <sizes>, <tensor-options>)
讓我們剖析這個「架構」的各個部分
<function-name>
是您想要呼叫的函式名稱,<functions-specific-options>
是特定工廠函式接受的任何必要或選用參數,<sizes>
是IntArrayRef
類型的物件,並指定結果 Tensor 的形狀,<tensor-options>
是TensorOptions
的一個實例,並配置結果 Tensor 的資料類型、裝置、佈局和其他屬性。
選擇工廠函式¶
在撰寫本文時,以下工廠函式可用(超連結會指向相應的 Python 函式,因為它們通常具有更雄辯的文件 - C++ 中的選項相同)
arange:傳回具有一系列整數的 Tensor,
empty:傳回具有未初始化值的 Tensor,
eye:傳回一個單位矩陣,
full:傳回一個充滿單一值的 Tensor,
linspace:傳回一個在某個間隔中線性間隔值的 Tensor,
logspace:傳回一個在某個間隔中對數間隔值的 Tensor,
ones:傳回一個充滿所有 1 的 Tensor,
rand:傳回一個充滿從
[0, 1)
上均勻分佈中抽取的值的 Tensor。randint:傳回一個具有從間隔中隨機抽取的整數的 Tensor,
randn:回傳一個張量,其值從單位常態分佈中抽取,
randperm:回傳一個張量,其值為某個區間內整數的隨機排列,
zeros:回傳一個張量,其值皆為零。
指定大小¶
由於填充張量的方式的本質,不需要特定參數的函數可以僅使用大小來調用。 例如,以下程式碼會建立一個具有 5 個分量的向量,最初全部設定為 1
torch::Tensor tensor = torch::ones(5);
如果我們想要改為建立一個 3 x 5
矩陣,或是一個 2 x 3 x 4
張量呢? 一般來說,IntArrayRef
- 工廠函數的大小參數類型 - 是透過在大括號中指定每個維度的大小來建構的。 例如,{2, 3}
代表一個具有兩列和三欄的張量(在此情況下為矩陣),{3, 4, 5}
代表一個三維張量,而 {2}
代表一個具有兩個分量的一維張量。 在一維的情況下,您可以省略大括號,直接傳遞單一整數,就像我們上面所做的那樣。 請注意,大括號只是建構 IntArrayRef
的一種方式。 您還可以傳遞 std::vector<int64_t>
和一些其他類型。 無論哪種方式,這都意味著我們可以透過以下方式建構一個填充單位常態分佈值的三維張量:
torch::Tensor tensor = torch::randn({3, 4, 5});
assert(tensor.sizes() == std::vector<int64_t>{3, 4, 5});
tensor.sizes()
回傳一個 IntArrayRef
,可以與 std::vector<int64_t>
進行比較,我們可以發現它包含我們傳遞給張量的大小。 您也可以寫 tensor.size(i)
來存取單一維度,這相當於但優先於 tensor.sizes()[i]
。
傳遞函數特定的參數¶
ones
和 randn
都不接受任何其他參數來更改其行為。 一個確實需要進一步配置的函數是 randint
,它接受要生成的整數值的上限,以及一個可選的下限,預設為零。 在這裡,我們建立一個 5 x 5
的方形矩陣,其整數介於 0 和 10 之間
torch::Tensor tensor = torch::randint(/*high=*/10, {5, 5});
在這裡,我們將下限提高到 3
torch::Tensor tensor = torch::randint(/*low=*/3, /*high=*/10, {5, 5});
當然,內嵌註解 /*low=*/
和 /*high=*/
不是必需的,但就像 Python 中的關鍵字參數一樣,有助於提高可讀性。
提示
主要的重點是大小始終位於函數特定參數之後。
注意
有時函數根本不需要大小。 例如,arange
回傳的張量大小完全由其函數特定的參數指定 - 整數範圍的下限和上限。 在這種情況下,該函數不接受 size
參數。
配置張量的屬性¶
上一節討論了函數特定的參數。 函數特定的參數只能更改張量填充的值,有時更改張量的大小。 它們永遠不會更改諸如張量的資料類型(例如,float32
或 int64
),或它位於 CPU 還是 GPU 記憶體中之類的內容。 這些屬性的指定留給每個工廠函數的最後一個參數:TensorOptions
物件,如下所述。
TensorOptions
是一個封裝張量的建構軸的類別。 透過建構軸,我們指的是張量的一個特定屬性,可以在其建構之前(有時在之後更改)進行配置。 這些建構軸為
dtype
(之前稱為“純量類型”),它控制儲存在張量中的元素的資料類型,layout
,它可以是 strided (dense) 或 sparse,device
,它代表儲存張量的計算裝置(例如 CPU 或 CUDA GPU),requires_grad
布林值,用於啟用或停用張量的梯度記錄,
如果您習慣使用 Python 中的 PyTorch,那麼這些軸聽起來會非常熟悉。 目前這些軸允許的值為
對於
dtype
:kUInt8
、kInt8
、kInt16
、kInt32
、kInt64
、kFloat32
和kFloat64
,對於
layout
:kStrided
和kSparse
,對於
device
:kCPU
或kCUDA
(它接受一個可選的裝置索引),對於
requires_grad
:可以是true
或false
。
提示
存在 "Rust 風格" 的 dtype 簡寫,例如 kF32
而不是 kFloat32
。 請參閱此處 以獲取完整列表。
TensorOptions
的實例為每個軸儲存一個具體值。以下是一個建立 TensorOptions
物件的範例,該物件代表一個 64 位元浮點數、跨步張量(strided tensor),需要梯度,並且位於 CUDA 裝置 1 上。
auto options =
torch::TensorOptions()
.dtype(torch::kFloat32)
.layout(torch::kStrided)
.device(torch::kCUDA, 1)
.requires_grad(true);
請注意我們如何使用 TensorOptions
的 "builder" 風格方法來逐步建構物件。如果我們將此物件作為最後一個參數傳遞給工廠函數,則新建立的張量將具有這些屬性。
torch::Tensor tensor = torch::full({3, 4}, /*value=*/123, options);
assert(tensor.dtype() == torch::kFloat32);
assert(tensor.layout() == torch::kStrided);
assert(tensor.device().type() == torch::kCUDA); // or device().is_cuda()
assert(tensor.device().index() == 1);
assert(tensor.requires_grad());
現在,您可能在想:我真的需要為我建立的每個新張量指定每個軸嗎? 幸運的是,答案是“否”,因為**每個軸都有預設值**。 這些預設值是
對於 dtype,使用
kFloat32
,對於 layout,使用
kStrided
,對於 device,使用
kCPU
,對於
requires_grad
,使用false
。
這意味著在建構 TensorOptions
物件期間省略的任何軸都將採用其預設值。例如,這是我們之前的 TensorOptions
物件,但 dtype
和 layout
使用預設值。
auto options = torch::TensorOptions().device(torch::kCUDA, 1).requires_grad(true);
實際上,我們甚至可以省略所有軸以獲得完全預設的 TensorOptions
物件。
auto options = torch::TensorOptions(); // or `torch::TensorOptions options;`
這樣做的一個好處是,我們剛才談論的 TensorOptions
物件可以完全從任何張量工廠呼叫中省略。
// A 32-bit float, strided, CPU tensor that does not require a gradient.
torch::Tensor tensor = torch::randn({3, 4});
torch::Tensor range = torch::arange(5, 10);
但更棒的是:在到目前為止提供的 API 中,您可能已經注意到,初始的 torch::TensorOptions()
寫起來相當冗長。好消息是,對於每個建構軸(dtype、layout、device 和 requires_grad
),在 torch::
命名空間中都有一個*自由函數*,您可以為該軸傳遞一個值。然後,每個函數都會傳回一個預先使用該軸配置的 TensorOptions
物件,但允許透過上面顯示的 builder 風格方法進行更多修改。例如,
torch::ones(10, torch::TensorOptions().dtype(torch::kFloat32))
等效於
torch::ones(10, torch::dtype(torch::kFloat32))
此外,與其寫成
torch::ones(10, torch::TensorOptions().dtype(torch::kFloat32).layout(torch::kStrided))
我們可以寫成
torch::ones(10, torch::dtype(torch::kFloat32).layout(torch::kStrided))
這為我們節省了大量的輸入。這意味著在實務中,您幾乎不必寫出 torch::TensorOptions
。而是使用 torch::dtype()
、torch::device()
、torch::layout()
和 torch::requires_grad()
函數。
最後一個便利之處是,TensorOptions
可以從個別值隱式建構。這表示只要函數具有 TensorOptions
類型的參數(就像所有工廠函數一樣),我們就可以直接傳遞像 torch::kFloat32
或 torch::kStrided
這樣的值來代替完整的物件。因此,當只有一個軸與其預設值相比需要變更時,我們只能傳遞該值。因此,原來的
torch::ones(10, torch::TensorOptions().dtype(torch::kFloat32))
變成了
torch::ones(10, torch::dtype(torch::kFloat32))
最終可以縮短為
torch::ones(10, torch::kFloat32)
當然,使用這種簡短語法不可能修改 TensorOptions
實例的更多屬性,但如果我們只需要變更一個屬性,這非常實用。
總之,我們現在可以比較 TensorOptions
預設值,以及使用自由函數建立 TensorOptions
的簡寫 API,如何使 C++ 中的張量建立具有與 Python 相同的便利性。比較 Python 中的這個呼叫
torch.randn(3, 4, dtype=torch.float32, device=torch.device('cuda', 1), requires_grad=True)
與 C++ 中的等效呼叫
torch::randn({3, 4}, torch::dtype(torch::kFloat32).device(torch::kCUDA, 1).requires_grad(true))
非常接近!
轉換¶
正如我們可以使用 TensorOptions
來配置應如何建立新張量一樣,我們也可以使用 TensorOptions
將張量從一組屬性轉換為一組新的屬性。這種轉換通常會建立一個新張量,而不是就地發生。例如,如果我們有一個使用以下方法建立的 source_tensor
torch::Tensor source_tensor = torch::randn({2, 3}, torch::kInt64);
我們可以將其從 int64
轉換為 float32
torch::Tensor float_tensor = source_tensor.to(torch::kFloat32);
注意
轉換的結果 float_tensor
是一個指向新記憶體的新張量,與來源 source_tensor
無關。
然後,我們可以將其從 CPU 記憶體移至 GPU 記憶體
torch::Tensor gpu_tensor = float_tensor.to(torch::kCUDA);
如果您有多個可用的 CUDA 裝置,則上述程式碼會將張量複製到*預設* CUDA 裝置,您可以使用 torch::DeviceGuard
進行配置。如果沒有 DeviceGuard
存在,則這將是 GPU 1。如果您想指定不同的 GPU 索引,您可以將其傳遞給 Device
建構函式
torch::Tensor gpu_two_tensor = float_tensor.to(torch::Device(torch::kCUDA, 1));
在 CPU 到 GPU 複製和反向複製的情況下,我們也可以透過將 /*non_blocking=*/false
作為最後一個參數傳遞給 to()
來將記憶體複製配置為非同步。
torch::Tensor async_cpu_tensor = gpu_tensor.to(torch::kCPU, /*non_blocking=*/true);
結論¶
希望這份筆記能幫助你充分了解如何使用 PyTorch C++ API 以慣用的方式建立和轉換張量。如果您有任何其他問題或建議,請使用我們的論壇或 GitHub issue 與我們聯繫。