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.Exampleprotobuf -
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.SparseTensorusandotf.io.VarLenFeature. Tuttavia, dovresti considerare di utilizzare invecetf.io.RaggedFeature.Leggere dati sparsi arbitrari in un
tf.SparseTensorutilizzandotf.io.SparseFeature, che utilizza tre chiavi di funzionalità separate per memorizzare gliindices, ivaluesedense_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.
Visualizza su TensorFlow.org
Esegui in Google Colab
Visualizza su GitHub
Scarica quaderno