注意
点击此处下载完整示例代码
(原型) MaskedTensor 稀疏性¶
创建日期:2022 年 10 月 28 日 | 最后更新:2023 年 12 月 12 日 | 最后验证:未验证
在学习本教程之前,请务必回顾我们的MaskedTensor 概览教程 <https://pytorch.ac.cn/tutorials/prototype/maskedtensor_overview.html>。
引言¶
稀疏性一直是 PyTorch 内部快速发展且重要的领域;如果以下任何稀疏性术语令人困惑,请参阅稀疏性教程以获取更多详细信息。
稀疏存储格式已被证明在多种方面功能强大。首先,大多数实践者想到的第一个用例是当大多数元素等于零时(高度稀疏性),但即使在稀疏度较低的情况下,某些格式(例如 BSR)也可以利用矩阵内的子结构。
注意
目前,MaskedTensor 支持 COO 和 CSR 张量,未来计划支持更多格式(如 BSR 和 CSC)。如果您对更多格式有任何需求,请在此提交功能请求!
原则¶
创建带有稀疏张量的 MaskedTensor
时,必须遵循一些原则:
data
和mask
必须具有相同的存储格式,无论是torch.strided
、torch.sparse_coo
还是torch.sparse_csr
data
和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 秒)