注意
點選此處下載完整示例程式碼
(原型) MaskedTensor 稀疏性¶
建立日期:2022 年 10 月 28 日 | 最後更新:2023 年 12 月 12 日 | 最後驗證:未驗證
在學習本教程之前,請務必回顧我們的MaskedTensor 概覽教程 <https://pytorch.com.tw/tutorials/prototype/maskedtensor_overview.html>。
引言¶
稀疏性一直是 PyTorch 內部快速發展且重要的領域;如果以下任何稀疏性術語令人困惑,請參閱稀疏性教程以獲取更多詳細資訊。
稀疏儲存格式已被證明在多種方面功能強大。首先,大多數實踐者想到的第一個用例是當大多數元素等於零時(高度稀疏性),但即使在稀疏度較低的情況下,某些格式(例如 BSR)也可以利用矩陣內的子結構。
注意
目前,MaskedTensor 支援 COO 和 CSR 張量,未來計劃支援更多格式(如 BSR 和 CSC)。如果您對更多格式有任何需求,請在此提交功能請求!
原則¶
建立帶有稀疏張量的 MaskedTensor 時,必須遵循一些原則:
data和mask必須具有相同的儲存格式,無論是torch.strided、torch.sparse_coo還是torch.sparse_csrdata和mask必須具有相同的大小,由size()指示
稀疏 COO 張量¶
根據原則 #1,透過傳入兩個稀疏 COO 張量來建立一個稀疏 COO MaskedTensor,可以使用其任何建構函式進行初始化,例如 torch.sparse_coo_tensor()。
回顧稀疏 COO 張量,COO 格式代表“座標格式”,其中指定的元素作為其索引和對應值的元組儲存。也就是說,提供了以下內容:
indices:大小為(ndim, nse)、資料型別為torch.int64的陣列values:大小為 (nse,)、資料型別為任意整數或浮點數的陣列
其中 ndim 是張量的維度,nse 是指定元素的數量。
對於稀疏 COO 和 CSR 張量,你可以透過以下任一方式構建 MaskedTensor:
masked_tensor(sparse_tensor_data, sparse_tensor_mask)dense_masked_tensor.to_sparse_coo()或dense_masked_tensor.to_sparse_csr()
第二種方法更容易說明,所以我們在下面展示了它,但要了解更多關於第一種方法及其背後細微差別的資訊,請閱讀稀疏 COO 附錄。
import torch
from torch.masked import masked_tensor
import warnings
# Disable prototype warnings and such
warnings.filterwarnings(action='ignore', category=UserWarning)
values = torch.tensor([[0, 0, 3], [4, 0, 5]])
mask = torch.tensor([[False, False, True], [False, False, True]])
mt = masked_tensor(values, mask)
sparse_coo_mt = mt.to_sparse_coo()
print("mt:\n", mt)
print("mt (sparse coo):\n", sparse_coo_mt)
print("mt data (sparse coo):\n", sparse_coo_mt.get_data())
mt:
MaskedTensor(
[
[ --, --, 3],
[ --, --, 5]
]
)
mt (sparse coo):
MaskedTensor(
[
[ --, --, 3],
[ --, --, 5]
]
)
mt data (sparse coo):
tensor(indices=tensor([[0, 1],
[2, 2]]),
values=tensor([3, 5]),
size=(2, 3), nnz=2, layout=torch.sparse_coo)
稀疏 CSR 張量¶
類似地,MaskedTensor 也支援 CSR(Compressed Sparse Row,壓縮稀疏行)稀疏張量格式。稀疏 CSR 張量不是像稀疏 COO 張量那樣儲存索引元組,而是透過儲存壓縮的行索引來減少記憶體需求。特別是,CSR 稀疏張量由三個一維張量組成:
crow_indices:壓縮行索引陣列,大小為(size[0] + 1,)。此陣列指示 values 中給定條目所在的行。最後一個元素是指定元素的數量,而 crow_indices[i+1] - crow_indices[i] 表示第 i 行中的指定元素數量。col_indices:大小為(nnz,)的陣列。指示每個值的列索引。values:大小為(nnz,)的陣列。包含 CSR 張量的值。
值得注意的是,稀疏 COO 和 CSR 張量都處於 beta 狀態。
舉例如下
mt_sparse_csr = mt.to_sparse_csr()
print("mt (sparse csr):\n", mt_sparse_csr)
print("mt data (sparse csr):\n", mt_sparse_csr.get_data())
mt (sparse csr):
MaskedTensor(
[
[ --, --, 3],
[ --, --, 5]
]
)
mt data (sparse csr):
tensor(crow_indices=tensor([0, 1, 2]),
col_indices=tensor([2, 2]),
values=tensor([3, 5]), size=(2, 3), nnz=2, layout=torch.sparse_csr)
支援的操作¶
二元運算¶
二元運算元 也受支援,但兩個 MaskedTensor 的輸入掩碼必須匹配。有關做出此決定的更多資訊,請查閱我們的MaskedTensor:高階語義教程。
請看下面的示例
i = [[0, 1, 1],
[2, 0, 2]]
v1 = [3, 4, 5]
v2 = [20, 30, 40]
m = torch.tensor([True, False, True])
s1 = torch.sparse_coo_tensor(i, v1, (2, 3))
s2 = torch.sparse_coo_tensor(i, v2, (2, 3))
mask = torch.sparse_coo_tensor(i, m, (2, 3))
mt1 = masked_tensor(s1, mask)
mt2 = masked_tensor(s2, mask)
print("mt1:\n", mt1)
print("mt2:\n", mt2)
mt1:
MaskedTensor(
[
[ --, --, 3],
[ --, --, 5]
]
)
mt2:
MaskedTensor(
[
[ --, --, 20],
[ --, --, 40]
]
)
torch.div(mt2, mt1):
MaskedTensor(
[
[ --, --, 6.6667],
[ --, --, 8.0000]
]
)
torch.mul(mt1, mt2):
MaskedTensor(
[
[ --, --, 60],
[ --, --, 200]
]
)
歸約¶
最後,歸約 受支援
mt
MaskedTensor(
[
[ --, --, 3],
[ --, --, 5]
]
)
print("mt.sum():\n", mt.sum())
print("mt.sum(dim=1):\n", mt.sum(dim=1))
print("mt.amin():\n", mt.amin())
mt.sum():
MaskedTensor(8, True)
mt.sum(dim=1):
MaskedTensor(
[3, 5]
)
mt.amin():
MaskedTensor(3, True)
MaskedTensor 輔助方法¶
為方便起見,MaskedTensor 有許多輔助方法,用於在不同佈局之間進行轉換並識別當前佈局
設定
v = [[3, 0, 0],
[0, 4, 5]]
m = [[True, False, False],
[False, True, True]]
mt = masked_tensor(torch.tensor(v), torch.tensor(m))
mt
MaskedTensor(
[
[3, --, --],
[ --, 4, 5]
]
)
MaskedTensor.to_sparse_coo() / MaskedTensor.to_sparse_csr() / MaskedTensor.to_dense(),用於在不同佈局之間進行轉換。
mt_sparse_coo = mt.to_sparse_coo()
mt_sparse_csr = mt.to_sparse_csr()
mt_dense = mt_sparse_coo.to_dense()
MaskedTensor.is_sparse() – 這將檢查 MaskedTensor 的佈局是否與任何受支援的稀疏佈局(目前是 COO 和 CSR)匹配。
print("mt_dense.is_sparse: ", mt_dense.is_sparse)
print("mt_sparse_coo.is_sparse: ", mt_sparse_coo.is_sparse)
print("mt_sparse_csr.is_sparse: ", mt_sparse_csr.is_sparse)
mt_dense.is_sparse: False
mt_sparse_coo.is_sparse: True
mt_sparse_csr.is_sparse: True
MaskedTensor.is_sparse_coo()
print("mt_dense.is_sparse_coo(): ", mt_dense.is_sparse_coo())
print("mt_sparse_coo.is_sparse_coo: ", mt_sparse_coo.is_sparse_coo())
print("mt_sparse_csr.is_sparse_coo: ", mt_sparse_csr.is_sparse_coo())
mt_dense.is_sparse_coo(): False
mt_sparse_coo.is_sparse_coo: True
mt_sparse_csr.is_sparse_coo: False
MaskedTensor.is_sparse_csr()
print("mt_dense.is_sparse_csr(): ", mt_dense.is_sparse_csr())
print("mt_sparse_coo.is_sparse_csr: ", mt_sparse_coo.is_sparse_csr())
print("mt_sparse_csr.is_sparse_csr: ", mt_sparse_csr.is_sparse_csr())
mt_dense.is_sparse_csr(): False
mt_sparse_coo.is_sparse_csr: False
mt_sparse_csr.is_sparse_csr: True
附錄¶
稀疏 COO 構建¶
回想在我們的原始示例中,我們建立了一個 MaskedTensor,然後使用 MaskedTensor.to_sparse_coo() 將其轉換為稀疏 COO MaskedTensor。
或者,我們也可以透過直接傳入兩個稀疏 COO 張量來構建一個稀疏 COO MaskedTensor
values = torch.tensor([[0, 0, 3], [4, 0, 5]]).to_sparse()
mask = torch.tensor([[False, False, True], [False, False, True]]).to_sparse()
mt = masked_tensor(values, mask)
print("values:\n", values)
print("mask:\n", mask)
print("mt:\n", mt)
values:
tensor(indices=tensor([[0, 1, 1],
[2, 0, 2]]),
values=tensor([3, 4, 5]),
size=(2, 3), nnz=3, layout=torch.sparse_coo)
mask:
tensor(indices=tensor([[0, 1],
[2, 2]]),
values=tensor([True, True]),
size=(2, 3), nnz=2, layout=torch.sparse_coo)
mt:
MaskedTensor(
[
[ --, --, 3],
[ --, --, 5]
]
)
除了使用 torch.Tensor.to_sparse() 之外,我們還可以直接建立稀疏 COO 張量,這引出了一個警告:
警告
當使用類似 MaskedTensor.to_sparse_coo() 的函式(類似於 Tensor.to_sparse())時,如果使用者不指定索引,例如在上面的示例中,那麼預設情況下 0 值將被視為“未指定”。
下面,我們明確指定了 0 值
i = [[0, 1, 1],
[2, 0, 2]]
v = [3, 4, 5]
m = torch.tensor([True, False, True])
values = torch.sparse_coo_tensor(i, v, (2, 3))
mask = torch.sparse_coo_tensor(i, m, (2, 3))
mt2 = masked_tensor(values, mask)
print("values:\n", values)
print("mask:\n", mask)
print("mt2:\n", mt2)
values:
tensor(indices=tensor([[0, 1, 1],
[2, 0, 2]]),
values=tensor([3, 4, 5]),
size=(2, 3), nnz=3, layout=torch.sparse_coo)
mask:
tensor(indices=tensor([[0, 1, 1],
[2, 0, 2]]),
values=tensor([ True, False, True]),
size=(2, 3), nnz=3, layout=torch.sparse_coo)
mt2:
MaskedTensor(
[
[ --, --, 3],
[ --, --, 5]
]
)
注意,mt 和 mt2 表面看起來相同,並且在絕大多數操作中會產生相同的結果。但這引出了一個實現細節:
data 和 mask – 僅適用於稀疏 MaskedTensor – 在**建立時**可以有不同的元素數量 (nnz()),但 mask 的索引必須是 data 索引的子集。在這種情況下,data 將透過 data = data.sparse_mask(mask) 假設 mask 的形狀;換句話說,data 中在 mask 中不為 True(即未指定)的任何元素都將被丟棄。
因此,在底層,資料看起來略有不同;mt2 的值“4”被掩碼排除,而 mt 完全沒有它。它們的底層資料具有不同的形狀,這將導致像 mt + mt2 這樣的操作無效。
print("mt data:\n", mt.get_data())
print("mt2 data:\n", mt2.get_data())
mt data:
tensor(indices=tensor([[0, 1],
[2, 2]]),
values=tensor([3, 5]),
size=(2, 3), nnz=2, layout=torch.sparse_coo)
mt2 data:
tensor(indices=tensor([[0, 1, 1],
[2, 0, 2]]),
values=tensor([3, 4, 5]),
size=(2, 3), nnz=3, layout=torch.sparse_coo)
稀疏 CSR 構建¶
我們也可以使用稀疏 CSR 張量構建稀疏 CSR MaskedTensor,與上面的示例一樣,這在底層會產生類似的處理。
crow_indices = torch.tensor([0, 2, 4])
col_indices = torch.tensor([0, 1, 0, 1])
values = torch.tensor([1, 2, 3, 4])
mask_values = torch.tensor([True, False, False, True])
csr = torch.sparse_csr_tensor(crow_indices, col_indices, values, dtype=torch.double)
mask = torch.sparse_csr_tensor(crow_indices, col_indices, mask_values, dtype=torch.bool)
mt = masked_tensor(csr, mask)
print("mt:\n", mt)
print("mt data:\n", mt.get_data())
mt:
MaskedTensor(
[
[ 1.0000, --],
[ --, 4.0000]
]
)
mt data:
tensor(crow_indices=tensor([0, 2, 4]),
col_indices=tensor([0, 1, 0, 1]),
values=tensor([1., 2., 3., 4.]), size=(2, 2), nnz=4,
dtype=torch.float64, layout=torch.sparse_csr)
結論¶
在本教程中,我們介紹瞭如何在稀疏 COO 和 CSR 格式下使用 MaskedTensor,並討論了使用者決定直接訪問底層資料結構時的一些底層細節。稀疏儲存格式和掩碼語義確實具有很強的協同作用,甚至有時會互為代理(正如我們在下一個教程中將看到的那樣)。未來,我們必將在在此方向上投入並繼續發展。
延伸閱讀¶
要繼續學習更多,你可以查閱我們的使用 MaskedTensor 為 Adagrad 高效編寫“稀疏”語義教程,瞭解 MaskedTensor 如何透過原生掩碼語義簡化現有工作流程的示例。
指令碼總執行時間: ( 0 分鐘 0.028 秒)