Visualizza su TensorFlow.org | Esegui in Google Colab | Visualizza su GitHub | Scarica quaderno |
Quando si lavora con tensori che contengono molti valori zero, è importante archiviarli in modo efficiente in termini di spazio e tempo. I tensori sparsi consentono l'archiviazione e l'elaborazione efficienti di tensori che contengono molti valori zero. I tensori sparsi sono ampiamente utilizzati negli schemi di codifica come TF-IDF come parte della preelaborazione dei dati nelle applicazioni NLP e per la preelaborazione delle immagini con molti pixel scuri nelle applicazioni di visione artificiale.
Tensori sparsi in TensorFlow
TensorFlow rappresenta i tensori sparsi tramite l'oggetto tf.SparseTensor
. Attualmente, i tensori sparsi in TensorFlow sono codificati utilizzando il formato dell'elenco di coordinate (COO). Questo formato di codifica è ottimizzato per matrici ipersparse come gli incorporamenti.
La codifica COO per tensori sparsi comprende:
-
values
: Un tensore 1D con forma[N]
contenente tutti i valori diversi da zero. -
indices
: un tensore 2D con forma[N, rank]
, contenente gli indici dei valori diversi da zero. -
dense_shape
: un tensore 1D con shape[rank]
, che specifica la forma del tensore.
Un valore diverso da zero nel contesto di un tf.SparseTensor
è un valore non codificato in modo esplicito. È possibile includere esplicitamente valori zero nei values
di una matrice sparsa COO, ma questi "zeri espliciti" non sono generalmente inclusi quando si fa riferimento a valori diversi da zero in un tensore sparso.
Creazione di un tf.SparseTensor
Costruisci tensori sparsi specificando direttamente i loro 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 usi la funzione print()
per stampare un tensore sparso, mostra il contenuto dei tre componenti tensori:
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))
È più facile comprendere il contenuto di un tensore sparso se i values
diversi da zero sono allineati con i loro indices
corrispondenti. Definire una funzione di supporto per stampare abbastanza tensori sparsi in modo tale che ogni valore diverso da zero sia mostrato su una propria riga.
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}>
Puoi anche costruire tensori sparsi da tensori densi usando tf.sparse.from_dense
e riconvertirli in tensori densi 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)
Manipolazione dei tensori sparsi
Utilizzare le utilità nel pacchetto tf.sparse
per manipolare i tensori sparsi. Operazioni come tf.math.add
che puoi usare per la manipolazione aritmetica di tensori densi non funzionano con tensori sparsi.
Aggiungi tensori sparsi della stessa 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}>
Usa tf.sparse.sparse_dense_matmul
per moltiplicare tensori sparsi con matrici dense.
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)
Metti insieme i tensori sparsi usando tf.sparse.concat
e smontali 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 stai usando TensorFlow 2.4 o versioni successive, usa tf.sparse.map_values
per operazioni elementwise su valori diversi da zero in tensori sparsi.
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)
Si noti che solo i valori diversi da zero sono stati modificati: i valori zero rimangono zero.
Allo stesso modo, puoi seguire il modello di progettazione riportato di seguito per le versioni precedenti di 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)
Utilizzo di tf.SparseTensor
con altre API TensorFlow
I tensori sparsi funzionano in modo trasparente con queste API 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
Di seguito sono riportati degli esempi per alcune delle API di cui sopra.
tf.keras
Un sottoinsieme dell'API tf.keras
supporta tensori sparsi senza costose operazioni di cast o conversione. L'API Keras ti consente di passare tensori sparsi come input a un modello Keras. Imposta sparse=True
quando si chiama tf.keras.Input
o tf.keras.layers.InputLayer
. Puoi passare tensori sparsi tra livelli Keras e anche fare in modo che i modelli Keras li restituiscano come output. Se usi tensori sparsi nei livelli tf.keras.layers.Dense
nel tuo modello, produrranno tensori densi.
L'esempio seguente mostra come passare un tensore sparso come input a un modello Keras se si utilizzano solo livelli che supportano input sparsi.
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
L'API tf.data
consente di creare pipeline di input complesse da parti semplici e riutilizzabili. La sua struttura di dati di base è tf.data.Dataset
, che rappresenta una sequenza di elementi in cui ogni elemento è costituito da uno o più componenti.
Creazione di set di dati con tensori sparsi
Crea set di dati da tensori sparsi utilizzando gli stessi metodi utilizzati per compilarli da array tf.Tensor
NumPy, come tf.data.Dataset.from_tensor_slices
. Questa operazione preserva la scarsità (o la natura sparsa) dei dati.
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}>
Batching e unbatching di set di dati con tensori sparsi
È possibile eseguire in batch (combinare elementi consecutivi in un unico elemento) e rimuovere in batch set di dati con tensori sparsi utilizzando rispettivamente i metodi Dataset.batch
e Dataset.unbatch
.
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}>
Puoi anche utilizzare tf.data.experimental.dense_to_sparse_batch
per raggruppare elementi del set di dati di forme diverse in tensori sparsi.
Trasformazione di set di dati con tensori sparsi
Trasforma e crea tensori sparsi nei set di dati 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.treno.Esempio
tf.train.Example
è una codifica protobuf standard per i dati TensorFlow. Quando si utilizzano tensori sparsi con tf.train.Example
, è possibile:
Leggi dati a lunghezza variabile in un
tf.SparseTensor
usandotf.io.VarLenFeature
. Tuttavia, dovresti considerare di utilizzare invecetf.io.RaggedFeature
.Leggere dati sparsi arbitrari in un
tf.SparseTensor
utilizzandotf.io.SparseFeature
, che utilizza tre chiavi di funzionalità separate per memorizzare gliindices
, ivalues
edense_shape
.
tf.function
Il decoratore tf.function
precalcola i grafici TensorFlow per le funzioni Python, che possono migliorare sostanzialmente le prestazioni del codice TensorFlow. I tensori sparsi funzionano in modo trasparente sia con tf.function
che con le funzioni concrete .
@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)
Distinguere i valori mancanti dai valori zero
La maggior parte delle operazioni su tf.SparseTensor
tratta i valori mancanti e i valori zero espliciti in modo identico. Questo è in base alla progettazione: un tf.SparseTensor
dovrebbe agire proprio come un tensore denso.
Tuttavia, ci sono alcuni casi in cui può essere utile distinguere i valori zero dai valori mancanti. In particolare, ciò consente un modo per codificare i dati mancanti/sconosciuti nei dati di allenamento. Ad esempio, considera un caso d'uso in cui hai un tensore di punteggi (che può avere qualsiasi valore in virgola mobile da -Inf a +Inf), con alcuni punteggi mancanti. È possibile codificare questo tensore utilizzando un tensore sparso in cui gli zeri espliciti sono punteggi zero noti ma i valori zero impliciti in realtà rappresentano dati mancanti e non zero.
Nota che alcune operazioni come tf.sparse.reduce_max
non trattano i valori mancanti come se fossero zero. Ad esempio, quando si esegue il blocco di codice seguente, l'output previsto è 0
. Tuttavia, a causa di questa eccezione, l'output è -3
.
print(tf.sparse.reduce_max(tf.sparse.from_dense([-5, 0, -3])))
tf.Tensor(-3, shape=(), dtype=int32)
Al contrario, quando si applica tf.math.reduce_max
a un tensore denso, l'output è 0 come previsto.
print(tf.math.reduce_max([-5, 0, -3]))
tf.Tensor(0, shape=(), dtype=int32)
Ulteriori letture e risorse
- Fare riferimento alla guida ai tensori per ulteriori informazioni sui tensori.
- Leggi la guida ai tensori irregolari per imparare a lavorare con i tensori irregolari, un tipo di tensore che ti consente di lavorare con dati non uniformi.
- Dai un'occhiata a questo modello di rilevamento degli oggetti in TensorFlow Model Garden che utilizza tensori sparsi in un decodificatore di dati
tf.Example
.