如何编写自己的v2转换¶
本指南解释了如何编写与torchvision transforms V2 API兼容的转换。
import torch
from torchvision import tv_tensors
from torchvision.transforms import v2
只需创建一个nn.Module
并重写forward
方法¶
在大多数情况下,只要您已经知道转换将期望的输入结构,这就是您所需要的。例如,如果您只是进行图像分类,您的转换通常会接受单个图像作为输入,或者一个(img, label)
输入。因此,您可以硬编码您的forward
方法以仅接受这些输入,例如。
class MyCustomTransform(torch.nn.Module):
def forward(self, img, label):
# Do some transformations
return new_img, new_label
注意
这意味着如果你有一个自定义的转换已经兼容V1转换(那些在torchvision.transforms
中的),它将仍然可以与V2转换一起工作,无需任何更改!
我们将在下面通过一个典型的检测案例更完整地说明这一点,其中我们的样本只是图像、边界框和标签:
class MyCustomTransform(torch.nn.Module):
def forward(self, img, bboxes, label): # we assume inputs are always structured like this
print(
f"I'm transforming an image of shape {img.shape} "
f"with bboxes = {bboxes}\n{label = }"
)
# Do some transformations. Here, we're just passing though the input
return img, bboxes, label
transforms = v2.Compose([
MyCustomTransform(),
v2.RandomResizedCrop((224, 224), antialias=True),
v2.RandomHorizontalFlip(p=1),
v2.Normalize(mean=[0, 0, 0], std=[1, 1, 1])
])
H, W = 256, 256
img = torch.rand(3, H, W)
bboxes = tv_tensors.BoundingBoxes(
torch.tensor([[0, 10, 10, 20], [50, 50, 70, 70]]),
format="XYXY",
canvas_size=(H, W)
)
label = 3
out_img, out_bboxes, out_label = transforms(img, bboxes, label)
I'm transforming an image of shape torch.Size([3, 256, 256]) with bboxes = BoundingBoxes([[ 0, 10, 10, 20],
[50, 50, 70, 70]], format=BoundingBoxFormat.XYXY, canvas_size=(256, 256))
label = 3
print(f"Output image shape: {out_img.shape}\nout_bboxes = {out_bboxes}\n{out_label = }")
Output image shape: torch.Size([3, 224, 224])
out_bboxes = BoundingBoxes([[224, 0, 224, 0],
[136, 0, 173, 0]], format=BoundingBoxFormat.XYXY, canvas_size=(224, 224))
out_label = 3
注意
在代码中使用TVTensor类时,请确保熟悉本节内容: 我有一个TVTensor但现在我有一个Tensor。救命!
支持任意输入结构¶
在上面的部分中,我们假设您已经知道输入的结构,并且您可以在代码中硬编码这个预期的结构。如果您希望您的自定义转换尽可能灵活,这可能会有些限制。
内置Torchvision V2转换的一个关键特性是它们可以接受任意输入结构并返回相同的结构作为输出(带有转换后的条目)。例如,转换可以接受单个图像,或一个(img, label)
元组,或任意嵌套的字典作为输入:
structured_input = {
"img": img,
"annotations": (bboxes, label),
"something_that_will_be_ignored": (1, "hello")
}
structured_output = v2.RandomHorizontalFlip(p=1)(structured_input)
assert isinstance(structured_output, dict)
assert structured_output["something_that_will_be_ignored"] == (1, "hello")
print(f"The transformed bboxes are:\n{structured_output['annotations'][0]}")
The transformed bboxes are:
BoundingBoxes([[246, 10, 256, 20],
[186, 50, 206, 70]], format=BoundingBoxFormat.XYXY, canvas_size=(256, 256))
如果你想在自己的转换中重现这种行为,我们邀请你查看我们的代码并根据你的需求进行调整。
简而言之,核心逻辑是使用pytree将输入解包成一个扁平列表,然后仅转换可以转换的条目(决定基于条目的类,因为所有TVTensors都是张量子类)以及一些超出此处范围的定制逻辑 - 详情请查看代码。然后(可能经过转换的)条目会被重新打包并以与输入相同的结构返回。
我们目前不提供面向开发者的公共工具来实现这一点,但如果这对您有价值,请通过在GitHub repo上提出问题来告知我们。
脚本总运行时间: (0 分钟 0.007 秒)