Ver en TensorFlow.org | Ejecutar en Google Colab | Ver en GitHub | Descargar libreta |
Cuando se trabaja con tensores que contienen muchos valores cero, es importante almacenarlos de manera eficiente en cuanto a espacio y tiempo. Los tensores dispersos permiten el almacenamiento y procesamiento eficientes de tensores que contienen muchos valores cero. Los tensores dispersos se utilizan ampliamente en esquemas de codificación como TF-IDF como parte del procesamiento previo de datos en aplicaciones NLP y para el procesamiento previo de imágenes con una gran cantidad de píxeles oscuros en aplicaciones de visión artificial.
Tensores dispersos en TensorFlow
TensorFlow representa tensores dispersos a través del objeto tf.SparseTensor
. Actualmente, los tensores dispersos en TensorFlow se codifican con el formato de lista de coordenadas (COO). Este formato de codificación está optimizado para matrices hiperdispersas como incrustaciones.
La codificación COO para tensores dispersos se compone de:
-
values
: un tensor 1D con forma[N]
que contiene todos los valores distintos de cero. -
indices
: un tensor 2D con forma[N, rank]
, que contiene los índices de los valores distintos de cero. -
dense_shape
: un tensor 1D con forma[rank]
, que especifica la forma del tensor.
Un valor distinto de cero en el contexto de un tf.SparseTensor
es un valor que no está codificado explícitamente. Es posible incluir explícitamente valores cero en los values
de una matriz dispersa de COO, pero estos "ceros explícitos" generalmente no se incluyen cuando se hace referencia a valores distintos de cero en un tensor disperso.
Creando un tf.SparseTensor
Construya tensores dispersos especificando directamente sus values
, indices
y dense_shape
.
import tensorflow as tf
st1 = tf.SparseTensor(indices=[[0, 3], [2, 4]],
values=[10, 20],
dense_shape=[3, 10])
Cuando usa la función print()
para imprimir un tensor disperso, muestra el contenido de los tres tensores componentes:
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))
Es más fácil comprender el contenido de un tensor disperso si los values
distintos de cero están alineados con sus indices
correspondientes. Defina una función auxiliar para imprimir tensores dispersos de manera que cada valor distinto de cero se muestre en su propia línea.
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}>
También puede construir tensores dispersos a partir de tensores densos usando tf.sparse.from_dense
y volver a convertirlos en tensores densos usando 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)
Manipulación de tensores dispersos
Utilice las utilidades del paquete tf.sparse
para manipular tensores dispersos. Operaciones como tf.math.add
que puede usar para la manipulación aritmética de tensores densos no funcionan con tensores dispersos.
Agregue tensores dispersos de la misma forma usando tf.sparse.add
.
st_a = tf.SparseTensor(indices=[[0, 2], [3, 4]],
values=[31, 2],
dense_shape=[4, 10])
st_b = tf.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}>
Use tf.sparse.sparse_dense_matmul
para multiplicar tensores dispersos con matrices densas.
st_c = tf.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)
Junte los tensores dispersos usando tf.sparse.concat
y sepárelos usando tf.sparse.slice
.
sparse_pattern_A = tf.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.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.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)
Si usa TensorFlow 2.4 o superior, use tf.sparse.map_values
para operaciones elementales en valores distintos de cero en tensores dispersos.
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)
Tenga en cuenta que solo se modificaron los valores distintos de cero: los valores cero permanecen en cero.
De manera equivalente, puede seguir el patrón de diseño a continuación para versiones anteriores de TensorFlow:
st2_plus_5 = tf.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)
Usar tf.SparseTensor
con otras API de TensorFlow
Los tensores dispersos funcionan de manera transparente con estas API de TensorFlow:
-
tf.keras
-
tf.data
-
tf.Train.Example
de ejemplo -
tf.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
A continuación se muestran ejemplos de algunas de las API anteriores.
tf.keras
Un subconjunto de la API tf.keras
admite tensores dispersos sin costosas operaciones de conversión o conversión. La API de Keras le permite pasar tensores dispersos como entradas a un modelo de Keras. Establezca sparse=True
cuando llame a tf.keras.Input
o tf.keras.layers.InputLayer
. Puede pasar tensores dispersos entre capas de Keras y también hacer que los modelos de Keras los devuelvan como salidas. Si usa tensores dispersos en tf.keras.layers.Dense
capas en su modelo, generarán tensores densos.
El siguiente ejemplo muestra cómo pasar un tensor disperso como entrada a un modelo de Keras si usa solo capas que admiten entradas dispersas.
x = tf.keras.Input(shape=(4,), sparse=True)
y = tf.keras.layers.Dense(4)(x)
model = tf.keras.Model(x, y)
sparse_data = tf.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)
array([[-1.3111044 , -1.7598825 , 0.07225233, -0.44544357], [ 0. , 0. , 0. , 0. ], [ 0. , 0. , 0. , 0. ], [ 0. , 0. , 0. , 0. ], [ 0.8517609 , -0.16835624, 0.7307872 , -0.14531797], [-0.8916302 , -0.9417639 , 0.24563438, -0.9029659 ]], dtype=float32)
tf.data
La API tf.data
le permite crear canalizaciones de entrada complejas a partir de piezas simples y reutilizables. Su estructura de datos central es tf.data.Dataset
, que representa una secuencia de elementos en la que cada elemento consta de uno o más componentes.
Creación de conjuntos de datos con tensores dispersos
Cree conjuntos de datos a partir de tensores dispersos utilizando los mismos métodos que se utilizan para crearlos a partir de tf.Tensor
s o NumPy, como tf.data.Dataset.from_tensor_slices
. Esta operación preserva la escasez (o la naturaleza escasa) de los datos.
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}>
Conjuntos de datos por lotes y sin lotes con tensores dispersos
Puede agrupar (combinar elementos consecutivos en un solo elemento) y desagrupar conjuntos de datos con tensores dispersos utilizando los métodos Dataset.batch
y Dataset.unbatch
respectivamente.
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}>
También puede usar tf.data.experimental.dense_to_sparse_batch
para agrupar elementos de conjuntos de datos de diferentes formas en tensores dispersos.
Transformar conjuntos de datos con tensores dispersos
Transforme y cree tensores dispersos en conjuntos de datos usando 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.tren.Ejemplo
tf.train.Example
es una codificación protobuf estándar para datos de TensorFlow. Al usar tensores dispersos con tf.train.Example
, puede:
Lea datos de longitud variable en un
tf.SparseTensor
mediantetf.io.VarLenFeature
. Sin embargo, debería considerar usartf.io.RaggedFeature
en su lugar.Lea datos dispersos arbitrarios en un
tf.SparseTensor
mediantetf.io.SparseFeature
, que utiliza tres claves de función separadas para almacenar losindices
,values
ydense_shape
.
tf.function
El decorador tf.function
los gráficos de TensorFlow para las funciones de Python, lo que puede mejorar sustancialmente el rendimiento de su código de TensorFlow. Los tensores dispersos funcionan de forma transparente tanto con tf.function
como con funciones concretas .
@tf.function
def f(x,y):
return tf.sparse.sparse_dense_matmul(x,y)
a = tf.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)
Distinguir los valores perdidos de los valores cero
La mayoría de las operaciones en tf.SparseTensor
s tratan los valores faltantes y los valores cero explícitos de manera idéntica. Esto es por diseño: se supone que un tf.SparseTensor
actúa como un tensor denso.
Sin embargo, hay algunos casos en los que puede ser útil distinguir los valores cero de los valores perdidos. En particular, esto permite una forma de codificar datos faltantes/desconocidos en tus datos de entrenamiento. Por ejemplo, considere un caso de uso en el que tiene un tensor de puntajes (que puede tener cualquier valor de coma flotante desde -Inf hasta +Inf), con algunos puntajes faltantes. Puede codificar este tensor utilizando un tensor disperso donde los ceros explícitos son puntuaciones cero conocidas, pero los valores cero implícitos en realidad representan datos faltantes y no cero.
Tenga en cuenta que algunas operaciones como tf.sparse.reduce_max
no tratan los valores perdidos como si fueran cero. Por ejemplo, cuando ejecuta el bloque de código a continuación, el resultado esperado es 0
. Sin embargo, debido a esta excepción, la salida es -3
.
print(tf.sparse.reduce_max(tf.sparse.from_dense([-5, 0, -3])))
tf.Tensor(-3, shape=(), dtype=int32)
Por el contrario, cuando aplica tf.math.reduce_max
a un tensor denso, la salida es 0 como se esperaba.
print(tf.math.reduce_max([-5, 0, -3]))
tf.Tensor(0, shape=(), dtype=int32)
Más lecturas y recursos
- Consulte la guía de tensores para obtener información sobre los tensores.
- Lea la guía de tensores irregulares para aprender a trabajar con tensores irregulares, un tipo de tensor que le permite trabajar con datos no uniformes.
- Consulte este modelo de detección de objetos en TensorFlow Model Garden que usa tensores dispersos en un decodificador de datos
tf.Example
.