快捷方式

TVTensors FAQ

注意

嘗試在 Colab 上執行,或 滾動到末尾 下載完整的示例程式碼。

TVTensors 是與 `torchvision.transforms.v2` 一起引入的 Tensor 子類。本示例展示了這些 TVTensors 是什麼以及它們的行為方式。

警告

目標讀者 除非您正在編寫自己的 transforms 或自己的 TVTensors,否則您可能不需要閱讀本指南。這是一個相當底層的主題,大多數使用者不需要擔心:您不需要理解 TVTensors 的內部機制就可以有效地依賴 `torchvision.transforms.v2`。但它可能對嘗試實現自己的資料集、transforms 或直接處理 TVTensors 的高階使用者有用。

import PIL.Image

import torch
from torchvision import tv_tensors

什麼是 TVTensors?

TVTensors 是零複製 Tensor 子類。

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 看起來和感覺就像普通的 tensors — 它們 **就是** tensors。所有在普通 `torch.Tensor` 上支援的操作,如 `.sum()` 或任何 `torch.*` 運算子,同樣適用於 TVTensors。有關一些注意事項,請參閱 我有一個 TVTensor,但現在我得到了一個 Tensor。怎麼辦?

如何構建 TVTensor?

使用建構函式

每個 TVTensor 類都接受任何可以轉換為 `Tensor` 的 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`)以及實際值。這些元資料對於正確變換邊界框是必需的。類似地,`KeyPoints` 也需要新增 `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)


keypoints = tv_tensors.KeyPoints(
    [[17, 16], [344, 495], [0, 10], [0, 10]],
    canvas_size=image.shape[-2:]
)
print(keypoints)
BoundingBoxes([[ 17,  16, 344, 495],
               [  0,  10,   0,  10]], format=BoundingBoxFormat.XYXY, canvas_size=torch.Size([512, 512]), clamping_mode=soft)
KeyPoints([[ 17,  16],
           [344, 495],
           [  0,  10],
           [  0,  10]], canvas_size=torch.Size([512, 512]))

使用 `tv_tensors.wrap()`

您還可以使用 `wrap()` 函式將 tensor 物件包裝成 TVTensor。當您已經擁有所需型別的物件時,這很有用,這通常發生在編寫 transforms 時:您只想像輸入一樣包裝輸出。

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` transforms 或 functionals,您將始終獲得與輸入相同型別的輸出(純 `Tensor` 或 `TVTensor`)。

但我想要一個 TVTensor 回來!

您可以將純 tensor 重新包裝成 TVTensor,只需呼叫 TVTensor 建構函式,或者使用 `wrap()` 函式(請參閱上方 如何構建 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` transforms 來說無關緊要,因為我們可以在那裡避免開銷,但對於您模型中的 `forward` 來說,這可能是一個問題。

另一方面,也不是好太多。 對於保留 `TVTensor` 型別有意義的每種運算,都有同樣多的運算返回純 Tensor 是更可取的:例如,`img.sum()` 還是 `Image` 嗎?如果我們一直保留 `TVTensor` 型別,那麼即使是模型的 logits 或損失函式的輸出也將是 `Image` 型別,這肯定是不理想的。

注意

這種行為是我們正在積極尋求反饋的。如果您覺得這令人驚訝,或者您對如何更好地支援您的用例有任何建議,請透過此 issue 與我們聯絡:https://github.com/pytorch/vision/issues/7319

例外

這個“解包”規則有幾個例外:`clone()`、`to()`、`torch.Tensor.detach()` 和 `requires_grad_()` 保留 TVTensor 型別。

TVTensors 上的原地操作(如 `obj.add_()`)將保留 `obj` 的型別。但是,原地操作的 **返回** 值將是一個純 tensor。

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.008 秒)

由 Sphinx-Gallery 生成的畫廊

文件

訪問全面的 PyTorch 開發者文件

檢視文件

教程

為初學者和高階開發者提供深入的教程

檢視教程

資源

查詢開發資源並讓您的問題得到解答

檢視資源