在 TensorFlow.org 上查看 | 在 Google Colab 中运行 | 在 GitHub 上查看 | 下载笔记本 |
在处理包含大量零值的张量时,以空间和时间高效的方式存储它们非常重要。稀疏张量可以有效地存储和处理包含大量零值的张量。稀疏张量广泛用于像 TF-IDF 这样的编码方案中,作为 NLP 应用程序中数据预处理的一部分,以及用于在计算机视觉应用程序中预处理包含大量暗像素的图像。
TensorFlow 中的稀疏张量
TensorFlow 通过 tf.sparse.SparseTensor
对象表示稀疏张量。目前,TensorFlow 中的稀疏张量使用坐标列表 (COO) 格式进行编码。这种编码格式针对像嵌入这样的超稀疏矩阵进行了优化。
稀疏张量的 COO 编码由以下部分组成:
values
:形状为[N]
的一维张量,包含所有非零值。indices
:形状为[N, rank]
的二维张量,包含非零值的索引。dense_shape
:形状为[rank]
的一维张量,指定张量的形状。
在 tf.sparse.SparseTensor
的上下文中,非零 值是指未显式编码的值。可以在 COO 稀疏矩阵的 values
中显式包含零值,但这些“显式零”通常在引用稀疏张量中的非零值时不包括在内。
创建 tf.sparse.SparseTensor
通过直接指定它们的 values
、indices
和 dense_shape
来构建稀疏张量。
import tensorflow as tf
2024-02-14 02:25:26.445043: E external/local_xla/xla/stream_executor/cuda/cuda_dnn.cc:9261] Unable to register cuDNN factory: Attempting to register factory for plugin cuDNN when one has already been registered 2024-02-14 02:25:26.445086: E external/local_xla/xla/stream_executor/cuda/cuda_fft.cc:607] Unable to register cuFFT factory: Attempting to register factory for plugin cuFFT when one has already been registered 2024-02-14 02:25:26.446594: E external/local_xla/xla/stream_executor/cuda/cuda_blas.cc:1515] Unable to register cuBLAS factory: Attempting to register factory for plugin cuBLAS when one has already been registered
st1 = tf.sparse.SparseTensor(indices=[[0, 3], [2, 4]],
values=[10, 20],
dense_shape=[3, 10])
当您使用 print()
函数打印稀疏张量时,它会显示三个组件张量的内容。
print(st1)
SparseTensor(indices=tf.Tensor( [[0 3] [2 4]], shape=(2, 2), dtype=int64), values=tf.Tensor([10 20], shape=(2,), dtype=int32), dense_shape=tf.Tensor([ 3 10], shape=(2,), dtype=int64))
如果非零 values
与其对应的 indices
对齐,则更容易理解稀疏张量的内容。定义一个辅助函数来美化打印稀疏张量,以便每个非零值都显示在单独的行上。
def pprint_sparse_tensor(st):
s = "<SparseTensor shape=%s \n values={" % (st.dense_shape.numpy().tolist(),)
for (index, value) in zip(st.indices, st.values):
s += f"\n %s: %s" % (index.numpy().tolist(), value.numpy().tolist())
return s + "}>"
print(pprint_sparse_tensor(st1))
<SparseTensor shape=[3, 10] values={ [0, 3]: 10 [2, 4]: 20}>
您还可以使用 tf.sparse.from_dense
从密集张量构建稀疏张量,并使用 tf.sparse.to_dense
将其转换回密集张量。
st2 = tf.sparse.from_dense([[1, 0, 0, 8], [0, 0, 0, 0], [0, 0, 3, 0]])
print(pprint_sparse_tensor(st2))
<SparseTensor shape=[3, 4] values={ [0, 0]: 1 [0, 3]: 8 [2, 2]: 3}>
st3 = tf.sparse.to_dense(st2)
print(st3)
tf.Tensor( [[1 0 0 8] [0 0 0 0] [0 0 3 0]], shape=(3, 4), dtype=int32)
操作稀疏张量
使用 tf.sparse
包中的实用程序来操作稀疏张量。您可用于密集张量的算术操作(如 tf.math.add
)不适用于稀疏张量。
使用 tf.sparse.add
添加形状相同的稀疏张量。
st_a = tf.sparse.SparseTensor(indices=[[0, 2], [3, 4]],
values=[31, 2],
dense_shape=[4, 10])
st_b = tf.sparse.SparseTensor(indices=[[0, 2], [7, 0]],
values=[56, 38],
dense_shape=[4, 10])
st_sum = tf.sparse.add(st_a, st_b)
print(pprint_sparse_tensor(st_sum))
<SparseTensor shape=[4, 10] values={ [0, 2]: 87 [3, 4]: 2 [7, 0]: 38}>
使用 tf.sparse.sparse_dense_matmul
将稀疏张量与密集矩阵相乘。
st_c = tf.sparse.SparseTensor(indices=([0, 1], [1, 0], [1, 1]),
values=[13, 15, 17],
dense_shape=(2,2))
mb = tf.constant([[4], [6]])
product = tf.sparse.sparse_dense_matmul(st_c, mb)
print(product)
tf.Tensor( [[ 78] [162]], shape=(2, 1), dtype=int32)
使用 tf.sparse.concat
将稀疏张量组合在一起,并使用 tf.sparse.slice
将其拆开。
sparse_pattern_A = tf.sparse.SparseTensor(indices = [[2,4], [3,3], [3,4], [4,3], [4,4], [5,4]],
values = [1,1,1,1,1,1],
dense_shape = [8,5])
sparse_pattern_B = tf.sparse.SparseTensor(indices = [[0,2], [1,1], [1,3], [2,0], [2,4], [2,5], [3,5],
[4,5], [5,0], [5,4], [5,5], [6,1], [6,3], [7,2]],
values = [1,1,1,1,1,1,1,1,1,1,1,1,1,1],
dense_shape = [8,6])
sparse_pattern_C = tf.sparse.SparseTensor(indices = [[3,0], [4,0]],
values = [1,1],
dense_shape = [8,6])
sparse_patterns_list = [sparse_pattern_A, sparse_pattern_B, sparse_pattern_C]
sparse_pattern = tf.sparse.concat(axis=1, sp_inputs=sparse_patterns_list)
print(tf.sparse.to_dense(sparse_pattern))
tf.Tensor( [[0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0] [0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0] [0 0 0 1 1 0 0 0 0 0 1 1 0 0 0 0 0] [0 0 0 1 1 0 0 0 0 0 1 1 0 0 0 0 0] [0 0 0 0 1 1 0 0 0 1 1 0 0 0 0 0 0] [0 0 0 0 0 0 1 0 1 0 0 0 0 0 0 0 0] [0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0]], shape=(8, 17), dtype=int32)
sparse_slice_A = tf.sparse.slice(sparse_pattern_A, start = [0,0], size = [8,5])
sparse_slice_B = tf.sparse.slice(sparse_pattern_B, start = [0,5], size = [8,6])
sparse_slice_C = tf.sparse.slice(sparse_pattern_C, start = [0,10], size = [8,6])
print(tf.sparse.to_dense(sparse_slice_A))
print(tf.sparse.to_dense(sparse_slice_B))
print(tf.sparse.to_dense(sparse_slice_C))
tf.Tensor( [[0 0 0 0 0] [0 0 0 0 0] [0 0 0 0 1] [0 0 0 1 1] [0 0 0 1 1] [0 0 0 0 1] [0 0 0 0 0] [0 0 0 0 0]], shape=(8, 5), dtype=int32) tf.Tensor( [[0] [0] [1] [1] [1] [1] [0] [0]], shape=(8, 1), dtype=int32) tf.Tensor([], shape=(8, 0), dtype=int32)
如果您使用的是 TensorFlow 2.4 或更高版本,请使用 tf.sparse.map_values
对稀疏张量中的非零值执行逐元素操作。
st2_plus_5 = tf.sparse.map_values(tf.add, st2, 5)
print(tf.sparse.to_dense(st2_plus_5))
tf.Tensor( [[ 6 0 0 13] [ 0 0 0 0] [ 0 0 8 0]], shape=(3, 4), dtype=int32)
请注意,只有非零值被修改 - 零值保持为零。
等效地,您可以遵循以下设计模式来使用早期版本的 TensorFlow。
st2_plus_5 = tf.sparse.SparseTensor(
st2.indices,
st2.values + 5,
st2.dense_shape)
print(tf.sparse.to_dense(st2_plus_5))
tf.Tensor( [[ 6 0 0 13] [ 0 0 0 0] [ 0 0 8 0]], shape=(3, 4), dtype=int32)
将 tf.sparse.SparseTensor
与其他 TensorFlow API 一起使用
稀疏张量可以与这些 TensorFlow API 透明地配合使用。
tf.keras
tf.data
tf.Train.Example
protobuftf.function
tf.while_loop
tf.cond
tf.identity
tf.cast
tf.print
tf.saved_model
tf.io.serialize_sparse
tf.io.serialize_many_sparse
tf.io.deserialize_many_sparse
tf.math.abs
tf.math.negative
tf.math.sign
tf.math.square
tf.math.sqrt
tf.math.erf
tf.math.tanh
tf.math.bessel_i0e
tf.math.bessel_i1e
以下是一些上述 API 的示例。
tf.keras
tf.keras
API 的一个子集支持稀疏张量,而无需昂贵的强制转换或转换操作。Keras API 允许您将稀疏张量作为输入传递给 Keras 模型。在调用 tf.keras.Input
或 tf.keras.layers.InputLayer
时,设置 sparse=True
。您可以在 Keras 层之间传递稀疏张量,也可以让 Keras 模型将其作为输出返回。如果您在模型中的 tf.keras.layers.Dense
层中使用稀疏张量,它们将输出密集张量。
以下示例展示了如何在仅使用支持稀疏输入的层的条件下,将稀疏张量作为输入传递给 Keras 模型。
x = tf.keras.Input(shape=(4,), sparse=True)
y = tf.keras.layers.Dense(4)(x)
model = tf.keras.Model(x, y)
sparse_data = tf.sparse.SparseTensor(
indices = [(0,0),(0,1),(0,2),
(4,3),(5,0),(5,1)],
values = [1,1,1,1,1,1],
dense_shape = (6,4)
)
model(sparse_data)
model.predict(sparse_data)
1/1 [==============================] - 0s 96ms/step array([[-1.9626052 , 1.9385288 , -0.3864261 , -1.4728891 ], [ 0. , 0. , 0. , 0. ], [ 0. , 0. , 0. , 0. ], [ 0. , 0. , 0. , 0. ], [ 0.2567069 , 0.1808129 , 0.01684809, 0.69576544], [-1.2277668 , 1.4192976 , 0.0884105 , -0.865898 ]], dtype=float32)
tf.data
tf.data
API 使您能够从简单、可重用的部分构建复杂的输入管道。其核心数据结构是 tf.data.Dataset
,它表示一个元素序列,其中每个元素都包含一个或多个组件。
使用稀疏张量构建数据集
使用与从 tf.Tensor
或 NumPy 数组构建数据集相同的方法从稀疏张量构建数据集,例如 tf.data.Dataset.from_tensor_slices
。此操作保留数据的稀疏性(或稀疏性质)。
dataset = tf.data.Dataset.from_tensor_slices(sparse_data)
for element in dataset:
print(pprint_sparse_tensor(element))
<SparseTensor shape=[4] values={ [0]: 1 [1]: 1 [2]: 1}> <SparseTensor shape=[4] values={}> <SparseTensor shape=[4] values={}> <SparseTensor shape=[4] values={}> <SparseTensor shape=[4] values={ [3]: 1}> <SparseTensor shape=[4] values={ [0]: 1 [1]: 1}>
使用稀疏张量对数据集进行批处理和取消批处理
您可以分别使用 Dataset.batch
和 Dataset.unbatch
方法对具有稀疏张量的 dataset 进行批处理(将连续元素组合成单个元素)和取消批处理。
batched_dataset = dataset.batch(2)
for element in batched_dataset:
print (pprint_sparse_tensor(element))
<SparseTensor shape=[2, 4] values={ [0, 0]: 1 [0, 1]: 1 [0, 2]: 1}> <SparseTensor shape=[2, 4] values={}> <SparseTensor shape=[2, 4] values={ [0, 3]: 1 [1, 0]: 1 [1, 1]: 1}>
unbatched_dataset = batched_dataset.unbatch()
for element in unbatched_dataset:
print (pprint_sparse_tensor(element))
<SparseTensor shape=[4] values={ [0]: 1 [1]: 1 [2]: 1}> <SparseTensor shape=[4] values={}> <SparseTensor shape=[4] values={}> <SparseTensor shape=[4] values={}> <SparseTensor shape=[4] values={ [3]: 1}> <SparseTensor shape=[4] values={ [0]: 1 [1]: 1}>
您还可以使用 tf.data.experimental.dense_to_sparse_batch
将形状不同的数据集元素批处理成稀疏张量。
使用稀疏张量转换数据集
使用 Dataset.map
在数据集内转换和创建稀疏张量。
transform_dataset = dataset.map(lambda x: x*2)
for i in transform_dataset:
print(pprint_sparse_tensor(i))
<SparseTensor shape=[4] values={ [0]: 2 [1]: 2 [2]: 2}> <SparseTensor shape=[4] values={}> <SparseTensor shape=[4] values={}> <SparseTensor shape=[4] values={}> <SparseTensor shape=[4] values={ [3]: 2}> <SparseTensor shape=[4] values={ [0]: 2 [1]: 2}>
tf.train.Example
tf.train.Example
是 TensorFlow 数据的标准 protobuf 编码。在将稀疏张量与 tf.train.Example
一起使用时,您可以
使用
tf.io.VarLenFeature
将可变长度数据读入tf.sparse.SparseTensor
。但是,您应该考虑改用tf.io.RaggedFeature
。使用
tf.io.SparseFeature
将任意稀疏数据读入tf.sparse.SparseTensor
,它使用三个单独的特征键来存储indices
、values
和dense_shape
。
tf.function
tf.function
装饰器为 Python 函数预先计算 TensorFlow 图,这可以大幅提高 TensorFlow 代码的性能。稀疏张量可以与 tf.function
和 具体函数 透明地配合使用。
@tf.function
def f(x,y):
return tf.sparse.sparse_dense_matmul(x,y)
a = tf.sparse.SparseTensor(indices=[[0, 3], [2, 4]],
values=[15, 25],
dense_shape=[3, 10])
b = tf.sparse.to_dense(tf.sparse.transpose(a))
c = f(a,b)
print(c)
tf.Tensor( [[225 0 0] [ 0 0 0] [ 0 0 625]], shape=(3, 3), dtype=int32)
区分缺失值和零值
大多数针对 tf.sparse.SparseTensor
的操作会将缺失值和显式零值视为相同。这是设计使然 - tf.sparse.SparseTensor
应该像密集张量一样工作。
但是,在某些情况下,区分零值和缺失值可能很有用。特别是,这允许一种在训练数据中编码缺失/未知数据的方法。例如,考虑一个用例,您有一个分数张量(可以具有从 -Inf 到 +Inf 的任何浮点值),其中一些分数缺失。您可以使用稀疏张量对该张量进行编码,其中显式零是已知的零分数,但隐式零值实际上表示缺失数据而不是零。
请注意,某些操作(如 tf.sparse.reduce_max
)不会将缺失值视为零。例如,当您运行下面的代码块时,预期输出是 0
。但是,由于此异常,输出是 -3
。
print(tf.sparse.reduce_max(tf.sparse.from_dense([-5, 0, -3])))
tf.Tensor(-3, shape=(), dtype=int32)
相反,当您将 tf.math.reduce_max
应用于密集张量时,输出如预期的那样为 0。
print(tf.math.reduce_max([-5, 0, -3]))
tf.Tensor(0, shape=(), dtype=int32)
进一步阅读和资源
- 请参阅 张量指南,了解有关张量的更多信息。
- 阅读 不规则张量指南,了解如何使用不规则张量,这是一种允许您处理非均匀数据的张量类型。
- 在 TensorFlow 模型花园 中查看此对象检测模型,该模型在
tf.Example
数据解码器 中使用稀疏张量。