Voir sur TensorFlow.org | Exécuter dans Google Colab | Afficher sur GitHub | Télécharger le cahier |
Lorsque vous travaillez avec des tenseurs contenant de nombreuses valeurs nulles, il est important de les stocker de manière efficace en termes d'espace et de temps. Les tenseurs clairsemés permettent un stockage et un traitement efficaces des tenseurs qui contiennent beaucoup de valeurs nulles. Les tenseurs clairsemés sont largement utilisés dans les schémas de codage tels que TF-IDF dans le cadre du prétraitement des données dans les applications NLP et pour le prétraitement des images avec beaucoup de pixels sombres dans les applications de vision par ordinateur.
Tenseurs clairsemés dans TensorFlow
TensorFlow représente des tenseurs clairsemés via l'objet tf.SparseTensor
. Actuellement, les tenseurs clairsemés dans TensorFlow sont encodés à l'aide du format de liste de coordonnées (COO). Ce format d'encodage est optimisé pour les matrices hyper clairsemées telles que les représentations vectorielles continues.
Le codage COO pour les tenseurs creux est composé de :
-
values
: Un tenseur 1D de forme[N]
contenant toutes les valeurs non nulles. -
indices
: Un tenseur 2D de forme[N, rank]
, contenant les indices des valeurs non nulles. -
dense_shape
: Un tenseur 1D avec shape[rank]
, spécifiant la forme du tenseur.
Une valeur différente de zéro dans le contexte d'un tf.SparseTensor
est une valeur qui n'est pas explicitement encodée. Il est possible d'inclure explicitement des valeurs nulles dans les values
d'une matrice creuse COO, mais ces "zéros explicites" ne sont généralement pas inclus lorsqu'ils se réfèrent à des valeurs non nulles dans un tenseur clairsemé.
Création d'un tf.SparseTensor
Construisez des tenseurs creux en spécifiant directement leurs values
, indices
et dense_shape
.
import tensorflow as tf
st1 = tf.SparseTensor(indices=[[0, 3], [2, 4]],
values=[10, 20],
dense_shape=[3, 10])
Lorsque vous utilisez la fonction print()
pour imprimer un tenseur creux, elle affiche le contenu des trois composants tenseurs :
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))
Il est plus facile de comprendre le contenu d'un tenseur creux si les values
non nulles sont alignées avec leurs indices
correspondants. Définissez une fonction d'assistance pour imprimer joliment des tenseurs clairsemés de sorte que chaque valeur différente de zéro soit affichée sur sa propre ligne.
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}>
Vous pouvez également construire des tenseurs clairsemés à partir de tenseurs denses en utilisant tf.sparse.from_dense
et les reconvertir en tenseurs denses en utilisant 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)
Manipulation de tenseurs creux
Utilisez les utilitaires du package tf.sparse
pour manipuler les tenseurs creux. Les opérations comme tf.math.add
que vous pouvez utiliser pour la manipulation arithmétique des tenseurs denses ne fonctionnent pas avec les tenseurs clairsemés.
Ajoutez des tenseurs clairsemés de la même forme en utilisant 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}>
Utilisez tf.sparse.sparse_dense_matmul
pour multiplier les tenseurs clairsemés avec des matrices denses.
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)
Assemblez des tenseurs clairsemés en utilisant tf.sparse.concat
et séparez-les en utilisant 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 vous utilisez TensorFlow 2.4 ou une version ultérieure, utilisez tf.sparse.map_values
pour les opérations élément par élément sur des valeurs non nulles dans des tenseurs clairsemés.
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)
Notez que seules les valeurs non nulles ont été modifiées – les valeurs nulles restent nulles.
De manière équivalente, vous pouvez suivre le modèle de conception ci-dessous pour les versions antérieures 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)
Utilisation de tf.SparseTensor
avec d'autres API TensorFlow
Les Tensors clairsemés fonctionnent de manière transparente avec ces 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
Des exemples sont présentés ci-dessous pour quelques-unes des API ci-dessus.
tf.keras
Un sous-ensemble de l'API tf.keras
prend en charge les tenseurs clairsemés sans opérations de diffusion ou de conversion coûteuses. L'API Keras vous permet de transmettre des tenseurs clairsemés en tant qu'entrées à un modèle Keras. Définissez sparse=True
lors de l'appel tf.keras.Input
ou tf.keras.layers.InputLayer
. Vous pouvez passer des tenseurs clairsemés entre les couches Keras, et également faire en sorte que les modèles Keras les renvoient en tant que sorties. Si vous utilisez des tenseurs clairsemés dans les couches tf.keras.layers.Dense
de votre modèle, ils produiront des tenseurs denses.
L'exemple ci-dessous vous montre comment transmettre un tenseur clairsemé comme entrée à un modèle Keras si vous utilisez uniquement des couches qui prennent en charge les entrées clairsemées.
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
vous permet de créer des pipelines d'entrée complexes à partir de pièces simples et réutilisables. Sa structure de données de base est tf.data.Dataset
, qui représente une séquence d'éléments dans laquelle chaque élément est constitué d'un ou plusieurs composants.
Construire des ensembles de données avec des tenseurs clairsemés
Créez des ensembles de données à partir de tenseurs clairsemés en utilisant les mêmes méthodes que celles utilisées pour les créer à partir de tf.Tensor
s ou NumPy, tels que tf.data.Dataset.from_tensor_slices
. Cette opération préserve la parcimonie (ou la nature parcimonieuse) des données.
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}>
Regrouper et dégrouper des ensembles de données avec des tenseurs clairsemés
Vous pouvez regrouper (combiner des éléments consécutifs en un seul élément) et dégrouper des ensembles de données avec des tenseurs clairsemés à l'aide des méthodes Dataset.batch
et Dataset.unbatch
respectivement.
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}>
Vous pouvez également utiliser tf.data.experimental.dense_to_sparse_batch
pour grouper des éléments d'ensemble de données de différentes formes dans des tenseurs clairsemés.
Transformer des ensembles de données avec des tenseurs clairsemés
Transformez et créez des tenseurs clairsemés dans des ensembles de données à l'aide 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.Exemple
tf.train.Example
est un encodage protobuf standard pour les données TensorFlow. Lorsque vous utilisez des tenseurs creux avec tf.train.Example
, vous pouvez :
Lire des données de longueur variable dans un
tf.SparseTensor
à l'aidetf.io.VarLenFeature
. Cependant, vous devriez envisager d'utilisertf.io.RaggedFeature
à la place.Lisez des données dispersées arbitraires dans un
tf.SparseTensor
à l'aidetf.io.SparseFeature
, qui utilise trois clés de fonctionnalité distinctes pour stocker lesindices
, lesvalues
etdense_shape
.
tf.function
Le décorateur tf.function
précalcule les graphiques TensorFlow pour les fonctions Python, ce qui peut considérablement améliorer les performances de votre code TensorFlow. Les tenseurs creux fonctionnent de manière transparente avec tf.function
et les fonctions concrètes .
@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)
Distinguer les valeurs manquantes des valeurs nulles
La plupart des opérations sur tf.SparseTensor
s traitent les valeurs manquantes et les valeurs zéro explicites de la même manière. C'est par conception - un tf.SparseTensor
est censé agir comme un tenseur dense.
Cependant, il existe quelques cas où il peut être utile de distinguer les valeurs nulles des valeurs manquantes. En particulier, cela permet d'encoder les données manquantes/inconnues dans vos données d'entraînement. Par exemple, considérez un cas d'utilisation où vous avez un tenseur de scores (qui peut avoir n'importe quelle valeur à virgule flottante de -Inf à +Inf), avec quelques scores manquants. Vous pouvez encoder ce tenseur à l'aide d'un tenseur clairsemé où les zéros explicites sont des scores nuls connus, mais les valeurs nulles implicites représentent en fait des données manquantes et non nulles.
Notez que certaines opérations comme tf.sparse.reduce_max
ne traitent pas les valeurs manquantes comme si elles étaient nulles. Par exemple, lorsque vous exécutez le bloc de code ci-dessous, la sortie attendue est 0
. Cependant, à cause de cette exception, la sortie est -3
.
print(tf.sparse.reduce_max(tf.sparse.from_dense([-5, 0, -3])))
tf.Tensor(-3, shape=(), dtype=int32)
En revanche, lorsque vous appliquez tf.math.reduce_max
à un tenseur dense, la sortie est 0 comme prévu.
print(tf.math.reduce_max([-5, 0, -3]))
tf.Tensor(0, shape=(), dtype=int32)
Lectures complémentaires et ressources
- Reportez-vous au guide des tenseurs pour en savoir plus sur les tenseurs.
- Lisez le guide des tenseurs irréguliers pour apprendre à travailler avec des tenseurs irréguliers, un type de tenseur qui vous permet de travailler avec des données non uniformes.
- Découvrez ce modèle de détection d'objet dans le TensorFlow Model Garden qui utilise des tenseurs clairsemés dans un décodeur de données
tf.Example
.