捷徑

(原型) MaskedTensor 稀疏性

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

在開始本教學之前,請務必先檢閱我們的 MaskedTensor 概觀教學 <https://pytorch.dev.org.tw/tutorials/prototype/maskedtensor_overview.html>

簡介

稀疏性一直是 PyTorch 中快速成長且重要的領域;如果在以下內容中有任何稀疏性術語令人困惑,請參考稀疏性教學以獲取更多詳細資訊。

稀疏儲存格式已被證明在許多方面都非常強大。 作為入門,大多數從業人員首先想到的用例是當大多數元素等於零(高度稀疏)時,但即使在稀疏度較低的情況下,某些格式(例如 BSR)也可以利用矩陣內的子結構。

注意

目前,MaskedTensor 支援 COO 和 CSR 張量,並計畫在未來支援其他格式(例如 BSR 和 CSC)。如果您對其他格式有任何要求,請在此處提交功能請求這裡

原則

在建立具有稀疏張量的 MaskedTensor 時,必須遵守一些原則

  1. datamask 必須具有相同的儲存格式,無論是 torch.stridedtorch.sparse_coo 還是 torch.sparse_csr

  2. datamask 必須具有相同的大小,由 size() 指示

稀疏 COO 張量

依照原則 #1,稀疏 COO MaskedTensor 是透過傳入兩個稀疏 COO 張量來建立的,這些張量可以透過其任何建構函式來初始化,例如torch.sparse_coo_tensor()

作為稀疏 COO 張量的回顧,COO 格式代表「座標格式」,其中指定的元素儲存為其索引和相應值的元組。 也就是說,提供以下內容

  • indices:大小為 (ndim, nse) 且 dtype 為 torch.int64 的陣列

  • values:大小為 (nse,) 且具有任何整數或浮點 dtype 的陣列

其中 ndim 是張量的維度,nse 是指定元素的數量。

對於稀疏 COO 和 CSR 張量,您可以透過以下兩種方式建構 MaskedTensor

  1. masked_tensor(sparse_tensor_data, sparse_tensor_mask)

  2. 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(壓縮稀疏列)稀疏張量格式。 稀疏 CSR 張量不是像稀疏 COO 張量那樣儲存索引的元組,而是旨在透過儲存壓縮列索引來降低記憶體需求。 特別是,CSR 稀疏張量由三個 1-D 張量組成

  • crow_indices:壓縮列索引陣列,大小為 (size[0] + 1,)。此陣列指示 values 中給定項目所在的列。最後一個元素是指定的元素數量,而 crow_indices[i+1] - crow_indices[i] 指示第 i 列中指定的元素數量。

  • col_indices:大小為 (nnz,) 的陣列。指示每個值的欄索引。

  • values:大小為 (nnz,) 的陣列。包含 CSR 張量的值。

請注意,sparse 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)

支援的操作

一元運算

支援所有一元運算符,例如:

mt.sin()
MaskedTensor(
  [
    [      --,       --,   0.1411],
    [      --,       --,  -0.9589]
  ]
)

二元運算

也支援二元運算符,但來自兩個遮罩張量的輸入遮罩必須匹配。 有關為什麼做出此決定的更多信息,請參閱我們的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]
  ]
)
print("torch.div(mt2, mt1):\n", torch.div(mt2, mt1))
print("torch.mul(mt1, mt2):\n", torch.mul(mt1, mt2))
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 的佈局是否與任何支援的 sparse 佈局(目前為 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

附錄

Sparse COO 結構

回顧我們的 原始範例,我們建立了一個 MaskedTensor,然後使用 MaskedTensor.to_sparse_coo() 將其轉換為 sparse COO MaskedTensor。

或者,我們也可以透過傳入兩個 sparse COO 張量來直接構造一個 sparse 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() 之外,我們也可以直接建立 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]
  ]
)

請注意,mtmt2 在表面上看起來相同,並且在絕大多數操作中,會產生相同的結果。 但這會帶給我們一個有關實現的詳細資訊。

datamask – 僅對於 sparse MaskedTensors – 可以有不同數量的元素 (nnz()) **在建立時**,但 mask 的索引必須是 data 索引的子集。 在這種情況下,data 將採用 mask 的形狀,透過 data = data.sparse_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)

Sparse CSR 結構

我們也可以使用 sparse CSR 張量來構造 sparse 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)

結論

在本教程中,我們介紹了如何將 MaskedTensor 與 sparse COO 和 CSR 格式一起使用,並討論了底層的一些細微之處,以防使用者決定直接訪問底層資料結構。 Sparse 儲存格式和遮罩語義確實具有很強的協同作用,以至於它們有時會被用作彼此的代理(我們將在下一個教程中看到)。 在未來,我們肯定計劃在這個方向上投資並繼續發展。

延伸閱讀

要繼續學習更多,您可以找到我們的使用 MaskedTensor 為 Adagrad 有效率地編寫「sparse」語義教程,以查看 MaskedTensor 如何使用原生遮罩語義簡化現有工作流程的示例。

腳本總執行時間: ( 0 分鐘 0.046 秒)

由 Sphinx-Gallery 產生的圖庫


評價此教學

© Copyright 2024, PyTorch.

使用 Sphinx 建構,並採用由 theme 提供的主題,由 Read the Docs 提供。

文件

取得 PyTorch 的完整開發者文件

檢視文件

教學課程

取得適合初學者和進階開發者的深入教學

檢視教學

資源

尋找開發資源並取得問題解答

檢視資源