• 教程 >
  • (原型) MaskedTensor 高階語義
快捷方式

(原型) MaskedTensor 高階語義

建立日期:2022 年 10 月 28 日 | 最後更新:2022 年 10 月 28 日 | 最後驗證:未驗證

在學習本教程之前,請務必查閱我們的MaskedTensor 概述教程 <https://pytorch.com.tw/tutorials/prototype/maskedtensor_overview.html>

本教程的目的是幫助使用者理解一些高階語義的工作原理及其形成原因。我們將重點關注以下兩個方面:

*. 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 有一些根本的語義差異。

*. 它們的工廠函式和基本定義反轉了掩碼(類似於 torch.nn.MHA);也就是說,MaskedTensor

使用 True 表示“指定”和 False 表示“未指定”,或“有效”/“無效”,而 NumPy 則相反。我們認為我們的掩碼定義不僅更直觀,而且與 PyTorch 整體現有的語義更一致。

*. 交集語義。在 NumPy 中,如果兩個元素中的一個被掩碼掉,結果元素也將被

掩碼掉——實際上,它們應用的是 邏輯或 (logical_or) 運算元

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 透過提供資料和掩碼訪問,並方便地使用 to_tensor() 將 MaskedTensor 轉換為填充了掩碼值的 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* 運算元”。這些都是歸約的例子——從 Tensor 中移除一個(或多個)維度然後聚合結果的運算元。在本節中,我們將使用歸約語義來闡釋我們對上述掩碼匹配的嚴格要求。

從根本上說,:class:`MaskedTensor` 執行相同的歸約操作,同時忽略被掩碼掉(未指定)的值。例如:

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 minutes 0.017 seconds)

畫廊由 Sphinx-Gallery 生成


評價本教程

© 版權所有 2024, PyTorch.

使用 Sphinx 構建,主題由 Read the Docs 提供。

文件

訪問 PyTorch 的全面開發者文件

檢視文件

教程

獲取面向初學者和高階開發者的深度教程

檢視教程

資源

查詢開發資源並獲得問題解答

檢視資源