• Tutorials >
  • (Prototype) MaskedTensor Advanced Semantics
Shortcuts

(原型) 掩码张量高级语义

创建于:2022年10月28日 | 最后更新:2022年10月28日 | 最后验证:未验证

在开始本教程之前,请确保查看我们的MaskedTensor 概述教程

本教程的目的是帮助用户理解一些高级语义的工作原理及其由来。我们将重点关注以下两个特定的语义:

*. MaskedTensor 和 NumPy 的 MaskedArray 之间的区别 *. 归约语义

准备

import torch
from torch.masked import masked_tensor
import numpy as np
import warnings

# Disable prototype warnings and such
warnings.filterwarnings(action='ignore', category=UserWarning)

MaskedTensor 与 NumPy 的 MaskedArray 对比

NumPy的MaskedArray与MaskedTensor有一些基本的语义差异。

*. Their factory function and basic definition inverts the mask (similar to torch.nn.MHA); that is, MaskedTensor

使用 True 表示“已指定”,使用 False 表示“未指定”或“有效”/“无效”,而 NumPy 则相反。我们相信我们的掩码定义不仅更直观,而且更符合 PyTorch 整体的现有语义。

*. Intersection semantics. In NumPy, if one of two elements are masked out, the resulting element will be

也被屏蔽了——实际上,他们 应用逻辑或运算符

data = torch.arange(5.)
mask = torch.tensor([True, True, False, True, False])
npm0 = np.ma.masked_array(data.numpy(), (~mask).numpy())
npm1 = np.ma.masked_array(data.numpy(), (mask).numpy())

print("npm0:\n", npm0)
print("npm1:\n", npm1)
print("npm0 + npm1:\n", npm0 + npm1)
npm0:
 [0.0 1.0 -- 3.0 --]
npm1:
 [-- -- 2.0 -- 4.0]
npm0 + npm1:
 [-- -- -- -- --]

同时,MaskedTensor 不支持与不匹配的掩码进行加法或二元运算符操作——要了解原因,请参阅关于归约的部分

mt0 = masked_tensor(data, mask)
mt1 = masked_tensor(data, ~mask)
print("mt0:\n", mt0)
print("mt1:\n", mt1)

try:
    mt0 + mt1
except ValueError as e:
    print ("mt0 + mt1 failed. Error: ", e)
mt0:
 MaskedTensor(
  [  0.0000,   1.0000,       --,   3.0000,       --]
)
mt1:
 MaskedTensor(
  [      --,       --,   2.0000,       --,   4.0000]
)
mt0 + mt1 failed. Error:  Input masks must match. If you need support for this, please open an issue on Github.

然而,如果希望实现这种行为,MaskedTensor 确实支持这些语义,通过提供对数据和掩码的访问,并方便地将 MaskedTensor 转换为带有填充掩码值的 Tensor,使用 to_tensor()。例如:

t0 = mt0.to_tensor(0)
t1 = mt1.to_tensor(0)
mt2 = masked_tensor(t0 + t1, mt0.get_mask() & mt1.get_mask())

print("t0:\n", t0)
print("t1:\n", t1)
print("mt2 (t0 + t1):\n", mt2)
t0:
 tensor([0., 1., 0., 3., 0.])
t1:
 tensor([0., 0., 2., 0., 4.])
mt2 (t0 + t1):
 MaskedTensor(
  [      --,       --,       --,       --,       --]
)

请注意,掩码是 mt0.get_mask() & mt1.get_mask(),因为 MaskedTensor 的掩码是 NumPy 的反向。

归约语义

回顾在MaskedTensor的概述教程中,我们讨论了“实现缺失的torch.nan*操作”。这些是归约操作的例子——从张量中移除一个(或多个)维度然后聚合结果的运算符。在本节中,我们将使用归约语义来推动我们对上述匹配掩码的严格要求。

基本上,:class:`MaskedTensor`s 在执行相同的归约操作时会忽略被屏蔽的(未指定的)值。例如:

data = torch.arange(12, dtype=torch.float).reshape(3, 4)
mask = torch.randint(2, (3, 4), dtype=torch.bool)
mt = masked_tensor(data, mask)

print("data:\n", data)
print("mask:\n", mask)
print("mt:\n", mt)
data:
 tensor([[ 0.,  1.,  2.,  3.],
        [ 4.,  5.,  6.,  7.],
        [ 8.,  9., 10., 11.]])
mask:
 tensor([[False,  True, False, False],
        [False,  True, False, False],
        [False,  True, False, False]])
mt:
 MaskedTensor(
  [
    [      --,   1.0000,       --,       --],
    [      --,   5.0000,       --,       --],
    [      --,   9.0000,       --,       --]
  ]
)

现在,不同的归约操作(都在 dim=1 上):

print("torch.sum:\n", torch.sum(mt, 1))
print("torch.mean:\n", torch.mean(mt, 1))
print("torch.prod:\n", torch.prod(mt, 1))
print("torch.amin:\n", torch.amin(mt, 1))
print("torch.amax:\n", torch.amax(mt, 1))
torch.sum:
 MaskedTensor(
  [  1.0000,   5.0000,   9.0000]
)
torch.mean:
 MaskedTensor(
  [  1.0000,   5.0000,   9.0000]
)
torch.prod:
 MaskedTensor(
  [  1.0000,   5.0000,   9.0000]
)
torch.amin:
 MaskedTensor(
  [  1.0000,   5.0000,   9.0000]
)
torch.amax:
 MaskedTensor(
  [  1.0000,   5.0000,   9.0000]
)

值得注意的是,被屏蔽元素下的值不保证具有任何特定值,特别是如果行或列完全被屏蔽(对于归一化也是如此)。 有关屏蔽语义的更多详细信息,您可以查看此RFC

现在,我们可以重新审视这个问题:为什么我们要强制要求二进制运算符的掩码必须匹配? 换句话说,为什么我们不使用与np.ma.masked_array相同的语义?考虑以下示例:

data0 = torch.arange(10.).reshape(2, 5)
data1 = torch.arange(10.).reshape(2, 5) + 10
mask0 = torch.tensor([[True, True, False, False, False], [False, False, False, True, True]])
mask1 = torch.tensor([[False, False, False, True, True], [True, True, False, False, False]])
npm0 = np.ma.masked_array(data0.numpy(), (mask0).numpy())
npm1 = np.ma.masked_array(data1.numpy(), (mask1).numpy())

print("npm0:", npm0)
print("npm1:", npm1)
npm0: [[-- -- 2.0 3.0 4.0]
 [5.0 6.0 7.0 -- --]]
npm1: [[10.0 11.0 12.0 -- --]
 [-- -- 17.0 18.0 19.0]]

现在,让我们尝试加法:

print("(npm0 + npm1).sum(0):\n", (npm0 + npm1).sum(0))
print("npm0.sum(0) + npm1.sum(0):\n", npm0.sum(0) + npm1.sum(0))
(npm0 + npm1).sum(0):
 [-- -- 38.0 -- --]
npm0.sum(0) + npm1.sum(0):
 [15.0 17.0 38.0 21.0 23.0]

求和和加法显然应该是可结合的,但在NumPy的语义中,它们不是,这无疑会让用户感到困惑。

MaskedTensor,另一方面,将不允许此操作,因为mask0 != mask1。 话虽如此,如果用户希望,有办法绕过这个问题 (例如,使用to_tensor()将MaskedTensor的未定义元素填充为0值 如下所示),但用户现在必须更明确地表达他们的意图。

mt0 = masked_tensor(data0, ~mask0)
mt1 = masked_tensor(data1, ~mask1)

(mt0.to_tensor(0) + mt1.to_tensor(0)).sum(0)
tensor([15., 17., 38., 21., 23.])

结论

在本教程中,我们了解了MaskedTensor和NumPy的MaskedArray背后的不同设计决策,以及缩减语义。总的来说,MaskedTensor旨在避免歧义和混淆的语义(例如,我们尝试在二元操作中保持结合性),这有时可能需要用户更加有意地编写代码,但我们认为这是更好的选择。如果您对此有任何想法,请告诉我们

脚本总运行时间: ( 0 分钟 0.026 秒)

Gallery generated by Sphinx-Gallery

优云智算