TVTensors 常見問題¶
TVTensors 是與 torchvision.transforms.v2
一起引入的 Tensor 子類別。此範例展示了這些 TVTensors 是什麼以及它們的行為方式。
警告
目標受眾 除非您正在編寫自己的轉換或自己的 TVTensors,否則您可能不需要閱讀本指南。這是一個相當底層的主題,大多數使用者不需要擔心:您不需要了解 TVTensors 的內部運作方式,即可有效率地依賴 torchvision.transforms.v2
。然而,對於嘗試實作自己的資料集、轉換或直接使用 TVTensors 的進階使用者來說,這可能會很有用。
import PIL.Image
import torch
from torchvision import tv_tensors
什麼是 TVTensors?¶
TVTensors 是零複製張量子類別
tensor = torch.rand(3, 256, 256)
image = tv_tensors.Image(tensor)
assert isinstance(image, torch.Tensor)
assert image.data_ptr() == tensor.data_ptr()
在底層,torchvision.transforms.v2
中需要它們來正確地為輸入資料調度到適當的函式。
torchvision.tv_tensors
支援四種類型的 TVTensors
我可以使用 TVTensor 做什麼?¶
TVTensors 看起來和感覺起來就像一般的張量 - 它們就是張量。在普通的 torch.Tensor
上支援的所有功能,例如 .sum()
或任何 torch.*
運算子,也適用於 TVTensors。請參閱 我原本有一個 TVTensor,但現在變成 Tensor 了。救命啊! 以了解一些注意事項。
我該如何建構 TVTensor?¶
使用建構函式¶
每個 TVTensor 類別都接受任何可以轉換為 Tensor
的類張量資料
image = tv_tensors.Image([[[[0, 1], [1, 0]]]])
print(image)
Image([[[[0, 1],
[1, 0]]]], )
與其他 PyTorch 建立運算類似,建構函式也接受 dtype
、device
和 requires_grad
參數。
float_image = tv_tensors.Image([[[0, 1], [1, 0]]], dtype=torch.float32, requires_grad=True)
print(float_image)
Image([[[0., 1.],
[1., 0.]]], grad_fn=<AliasBackward0>, )
此外,Image
和 Mask
也可以直接接受 PIL.Image.Image
image = tv_tensors.Image(PIL.Image.open("../assets/astronaut.jpg"))
print(image.shape, image.dtype)
torch.Size([3, 512, 512]) torch.uint8
有些 TVTensors 需要傳入額外的中繼資料才能建構。例如,BoundingBoxes
除了實際值之外,還需要座標格式以及對應影像的大小 (canvas_size
)。這些中繼資料是正確轉換邊界框所必需的。
bboxes = tv_tensors.BoundingBoxes(
[[17, 16, 344, 495], [0, 10, 0, 10]],
format=tv_tensors.BoundingBoxFormat.XYXY,
canvas_size=image.shape[-2:]
)
print(bboxes)
BoundingBoxes([[ 17, 16, 344, 495],
[ 0, 10, 0, 10]], format=BoundingBoxFormat.XYXY, canvas_size=torch.Size([512, 512]))
使用 tv_tensors.wrap()
¶
您也可以使用 wrap()
函式將張量物件包裝成 TVTensor。當您已經擁有所需類型的物件時,這非常有用,這通常發生在編寫轉換時:您只是想將輸出包裝得像輸入一樣。
new_bboxes = torch.tensor([0, 20, 30, 40])
new_bboxes = tv_tensors.wrap(new_bboxes, like=bboxes)
assert isinstance(new_bboxes, tv_tensors.BoundingBoxes)
assert new_bboxes.canvas_size == bboxes.canvas_size
new_bboxes
的中繼資料與 bboxes
相同,但您可以將其作為參數傳遞以覆寫它。
我原本有一個 TVTensor,但現在變成 Tensor 了。救命啊!¶
預設情況下,對 TVTensor
物件的操作將傳回純 Tensor
assert isinstance(bboxes, tv_tensors.BoundingBoxes)
# Shift bboxes by 3 pixels in both H and W
new_bboxes = bboxes + 3
assert isinstance(new_bboxes, torch.Tensor)
assert not isinstance(new_bboxes, tv_tensors.BoundingBoxes)
注意
此行為僅影響原生 torch
運算。如果您使用的是內建 torchvision
轉換或函式,您將始終獲得與您傳遞的輸入類型相同的輸出(純 Tensor
或 TVTensor
)。
但我想要回傳 TVTensor!¶
您可以透過呼叫 TVTensor 建構函式,或使用 wrap()
函式(請參閱上方 我該如何建構 TVTensor? 中的更多詳細資訊)將純張量重新包裝成 TVTensor
new_bboxes = bboxes + 3
new_bboxes = tv_tensors.wrap(new_bboxes, like=bboxes)
assert isinstance(new_bboxes, tv_tensors.BoundingBoxes)
或者,您可以使用 set_return_type()
作為整個程式的全域組態設定,或作為上下文管理器(閱讀其文件以了解更多關於注意事項的資訊)
with tv_tensors.set_return_type("TVTensor"):
new_bboxes = bboxes + 3
assert isinstance(new_bboxes, tv_tensors.BoundingBoxes)
為什麼會這樣?¶
為了效能考量。TVTensor
類別是 Tensor 子類別,因此任何涉及 TVTensor
物件的操作都將通過 __torch_function__ 協定。這會產生少量額外負擔,我們希望盡可能避免這種情況。這對於內建 torchvision
轉換來說沒問題,因為我們可以在那裡避免額外負擔,但在您模型的 forward
中可能會成為問題。
無論如何,替代方案也好不到哪裡去。 對於每個保留 TVTensor
類型有意義的操作,也有許多操作更適合傳回純 Tensor:例如,img.sum()
仍然是 Image
嗎?如果我們一直保留 TVTensor
類型,即使模型的 logits 或損失函數的輸出最終也會是 Image
類型,而這肯定不是我們所希望的。
注意
這種行為是我們積極尋求回饋的內容。如果您覺得這令人驚訝,或者您對如何更好地支援您的使用案例有任何建議,請透過此問題與我們聯繫:https://github.com/pytorch/vision/issues/7319
例外情況¶
此「解包裝」規則有一些例外情況:clone()
、to()
、torch.Tensor.detach()
和 requires_grad_()
保留 TVTensor 類型。
對 TVTensors 進行原地操作(例如 obj.add_()
)將保留 obj
的類型。但是,原地操作的回傳值將是純張量
image = tv_tensors.Image([[[0, 1], [1, 0]]])
new_image = image.add_(1).mul_(2)
# image got transformed in-place and is still a TVTensor Image, but new_image
# is a Tensor. They share the same underlying data and they're equal, just
# different classes.
assert isinstance(image, tv_tensors.Image)
print(image)
assert isinstance(new_image, torch.Tensor) and not isinstance(new_image, tv_tensors.Image)
assert (new_image == image).all()
assert new_image.data_ptr() == image.data_ptr()
Image([[[2, 4],
[4, 2]]], )
腳本總執行時間: (0 分鐘 0.009 秒)