Veja no TensorFlow.org | Executar no Google Colab | Ver no GitHub | Baixar caderno |
Ao trabalhar com tensores que contêm muitos valores zero, é importante armazená-los de maneira eficiente em termos de espaço e tempo. Os tensores esparsos permitem armazenamento e processamento eficientes de tensores que contêm muitos valores zero. Os tensores esparsos são usados extensivamente em esquemas de codificação como TF-IDF como parte do pré-processamento de dados em aplicativos NLP e para pré-processamento de imagens com muitos pixels escuros em aplicativos de visão computacional.
Tensores esparsos no TensorFlow
TensorFlow representa tensores esparsos por meio do objeto tf.SparseTensor
. Atualmente, tensores esparsos no TensorFlow são codificados usando o formato de lista de coordenadas (COO). Esse formato de codificação é otimizado para matrizes hiper-esparsas, como embeddings.
A codificação COO para tensores esparsos é composta por:
-
values
: Um tensor 1D com forma[N]
contendo todos os valores diferentes de zero. -
indices
: Um tensor 2D com forma[N, rank]
, contendo os índices dos valores diferentes de zero. -
dense_shape
: Um tensor 1D com forma[rank]
, especificando a forma do tensor.
Um valor diferente de zero no contexto de um tf.SparseTensor
é um valor que não é codificado explicitamente. É possível incluir explicitamente valores zero nos values
de uma matriz esparsa COO, mas esses "zeros explícitos" geralmente não são incluídos ao se referir a valores diferentes de zero em um tensor esparso.
Criando um tf.SparseTensor
Construa tensores esparsos especificando diretamente seus values
, indices
e dense_shape
.
import tensorflow as tf
st1 = tf.SparseTensor(indices=[[0, 3], [2, 4]],
values=[10, 20],
dense_shape=[3, 10])
Quando você usa a função print()
para imprimir um tensor esparso, ela mostra o conteúdo dos três tensores de 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))
É mais fácil entender o conteúdo de um tensor esparso se os values
diferentes de zero estiverem alinhados com seus indices
correspondentes. Defina uma função auxiliar para imprimir tensores esparsos de modo que cada valor diferente de zero seja mostrado em sua própria linha.
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}>
Você também pode construir tensores esparsos a partir de tensores densos usando tf.sparse.from_dense
e convertê-los novamente em 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)
Manipulando tensores esparsos
Use os utilitários no pacote tf.sparse
para manipular tensores esparsos. Ops como tf.math.add
que você pode usar para manipulação aritmética de tensores densos não funcionam com tensores esparsos.
Adicione tensores esparsos da mesma 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 esparsos com matrizes 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 tensores esparsos usando tf.sparse.concat
e separe-os 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)
Se você estiver usando o TensorFlow 2.4 ou superior, use tf.sparse.map_values
para operações elementares em valores diferentes de zero em tensores esparsos.
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)
Observe que apenas os valores diferentes de zero foram modificados – os valores zero permanecem zero.
De forma equivalente, você pode seguir o padrão de design abaixo para versões anteriores do 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)
Como usar tf.SparseTensor
com outras APIs do TensorFlow
Os tensores esparsos funcionam de forma transparente com estas APIs do TensorFlow:
-
tf.keras
-
tf.data
-
tf.Train.Example
protobuf -
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
Exemplos são mostrados abaixo para algumas das APIs acima.
tf.keras
Um subconjunto da API tf.keras
oferece suporte a tensores esparsos sem operações caras de conversão ou conversão. A API Keras permite passar tensores esparsos como entradas para um modelo Keras. Defina sparse=True
ao chamar tf.keras.Input
ou tf.keras.layers.InputLayer
. Você pode passar tensores esparsos entre camadas Keras e também fazer com que os modelos Keras os retornem como saídas. Se você usar tensores esparsos em camadas tf.keras.layers.Dense
em seu modelo, eles produzirão tensores densos.
O exemplo abaixo mostra como passar um tensor esparso como uma entrada para um modelo Keras se você usar apenas camadas que suportam entradas esparsas.
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
A API tf.data
permite que você crie pipelines de entrada complexos a partir de peças simples e reutilizáveis. Sua estrutura de dados principal é tf.data.Dataset
, que representa uma sequência de elementos em que cada elemento consiste em um ou mais componentes.
Como criar conjuntos de dados com tensores esparsos
Crie conjuntos de dados a partir de tensores esparsos usando os mesmos métodos usados para construí-los a partir de tf.Tensor
s ou NumPy, como tf.data.Dataset.from_tensor_slices
. Essa operação preserva a escassez (ou natureza esparsa) dos dados.
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 dados em lote e desagrupamento com tensores esparsos
Você pode agrupar (combinar elementos consecutivos em um único elemento) e desagrupar conjuntos de dados com tensores esparsos usando os métodos Dataset.batch
e 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}>
Você também pode usar tf.data.experimental.dense_to_sparse_batch
para agrupar elementos do conjunto de dados de formas variadas em tensores esparsos.
Transformando conjuntos de dados com tensores esparsos
Transforme e crie tensores esparsos em Datasets 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.train.Exemplo
tf.train.Example
é uma codificação protobuf padrão para dados do TensorFlow. Ao usar tensores esparsos com tf.train.Example
, você pode:
Leia dados de comprimento variável em um
tf.SparseTensor
usandotf.io.VarLenFeature
. No entanto, você deve considerar usartf.io.RaggedFeature
em vez disso.Leia dados esparsos arbitrários em um
tf.SparseTensor
usandotf.io.SparseFeature
, que usa três chaves de recurso separadas para armazenar osindices
,values
edense_shape
.
tf.function
O decorador tf.function
pré-computa gráficos do TensorFlow para funções Python, o que pode melhorar substancialmente o desempenho do seu código do TensorFlow. Os tensores esparsos funcionam de forma transparente com funções tf.function
e 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)
Distinguindo valores ausentes de valores zero
A maioria das operações em tf.SparseTensor
s tratam valores ausentes e valores zero explícitos de forma idêntica. Isso ocorre por design - um tf.SparseTensor
deve agir exatamente como um tensor denso.
No entanto, existem alguns casos em que pode ser útil distinguir valores zero de valores ausentes. Em particular, isso permite uma maneira de codificar dados ausentes/desconhecidos em seus dados de treinamento. Por exemplo, considere um caso de uso em que você tem um tensor de pontuações (que pode ter qualquer valor de ponto flutuante de -Inf a +Inf), com algumas pontuações ausentes. Você pode codificar esse tensor usando um tensor esparso onde os zeros explícitos são pontuações zero conhecidas, mas os valores zero implícitos na verdade representam dados ausentes e não zero.
Observe que algumas operações como tf.sparse.reduce_max
não tratam valores ausentes como se fossem zero. Por exemplo, quando você executa o bloco de código abaixo, a saída esperada é 0
. No entanto, devido a essa exceção, a saída é -3
.
print(tf.sparse.reduce_max(tf.sparse.from_dense([-5, 0, -3])))
tf.Tensor(-3, shape=(), dtype=int32)
Em contraste, quando você aplica tf.math.reduce_max
a um tensor denso, a saída é 0 conforme o esperado.
print(tf.math.reduce_max([-5, 0, -3]))
tf.Tensor(0, shape=(), dtype=int32)
Leitura adicional e recursos
- Consulte o guia de tensores para aprender sobre tensores.
- Leia o guia de tensor irregular para aprender a trabalhar com tensores irregulares, um tipo de tensor que permite trabalhar com dados não uniformes.
- Confira este modelo de detecção de objetos no TensorFlow Model Garden que usa tensores esparsos em um decodificador de dados
tf.Example
.