Zobacz na TensorFlow.org | Uruchom w Google Colab | Zobacz na GitHub | Pobierz notatnik |
Podczas pracy z tensorami, które zawierają wiele wartości zerowych, ważne jest, aby przechowywać je w sposób efektywny czasowo i przestrzennie. Rzadkie tensory umożliwiają wydajne przechowywanie i przetwarzanie tensorów zawierających wiele wartości zerowych. Rzadkie tensory są szeroko stosowane w schematach kodowania, takich jak TF-IDF , jako część wstępnego przetwarzania danych w aplikacjach NLP oraz do wstępnego przetwarzania obrazów z dużą ilością ciemnych pikseli w aplikacjach widzenia komputerowego.
Rzadkie tensory w TensorFlow
TensorFlow reprezentuje rzadkie tensory za pośrednictwem obiektu tf.SparseTensor . Obecnie rzadkie tensory w TensorFlow są kodowane przy użyciu formatu listy współrzędnych (COO). Ten format kodowania jest zoptymalizowany pod kątem bardzo rzadkich macierzy, takich jak osadzania.
Kodowanie COO dla rzadkich tensorów składa się z:
-
values: tensor jednowymiarowy o kształcie[N]zawierający wszystkie wartości niezerowe. -
indices: tensor 2D o kształcie[N, rank]zawierający indeksy o wartościach niezerowych. -
dense_shape: tensor 1D z kształtem[rank], określający kształt tensora.
Wartość niezerowa w kontekście tf.SparseTensor to wartość, która nie jest jawnie zakodowana. Możliwe jest jawne uwzględnienie wartości zerowych w values rzadkiej macierzy COO, ale te „wyraźne zera” na ogół nie są uwzględniane w odniesieniu do wartości niezerowych w tensorze rzadkim.
Tworzenie tf.SparseTensor
Konstruuj rzadkie tensory, określając bezpośrednio ich values , indices i dense_shape .
import tensorflow as tf
st1 = tf.SparseTensor(indices=[[0, 3], [2, 4]],
values=[10, 20],
dense_shape=[3, 10])

Kiedy używasz funkcji print() do drukowania rzadkiego tensora, pokazuje zawartość trzech tensorów składowych:
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))
Łatwiej jest zrozumieć zawartość rzadkiego tensora, jeśli values niezerowe są wyrównane z odpowiadającymi im indices . Zdefiniuj funkcję pomocniczą do ładnego drukowania rzadkich tensorów, tak aby każda wartość niezerowa była pokazywana w osobnym wierszu.
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}>
Można również skonstruować sparse tensory z gęstych tensorów za pomocą tf.sparse.from_dense i przekonwertować je z powrotem na gęste tensory za pomocą 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)
Manipulowanie rzadkimi tensorami
Użyj narzędzi w pakiecie tf.sparse , aby manipulować rzadkimi tensorami. Operacje takie jak tf.math.add , których można użyć do arytmetycznej manipulacji gęstymi tensorami, nie działają z rzadkimi tensorami.
Dodaj rzadkie tensory o tym samym kształcie, używając 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}>
Użyj tf.sparse.sparse_dense_matmul , aby pomnożyć rzadkie tensory z gęstymi macierzami.
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)
Połącz rzadkie tensory za pomocą tf.sparse.concat i rozłóż je za pomocą 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)
Jeśli używasz TensorFlow 2.4 lub nowszego, użyj tf.sparse.map_values dla operacji elementwise na wartościach niezerowych w rozrzedzonych tensorach.
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)
Zauważ, że tylko wartości niezerowe zostały zmodyfikowane – wartości zerowe pozostają zerowe.
Równoważnie możesz postępować zgodnie z poniższym wzorcem projektowym dla wcześniejszych wersji 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)
Korzystanie z tf.SparseTensor z innymi interfejsami API TensorFlow
Rzadkie tensory działają w przejrzysty sposób z następującymi interfejsami 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
Poniżej przedstawiono przykłady kilku z powyższych interfejsów API.
tf.keras
Podzbiór tf.keras API obsługuje rzadkie tensory bez kosztownych operacji rzutowania lub konwersji. Keras API umożliwia przekazywanie rzadkich tensorów jako danych wejściowych do modelu Keras. Ustaw sparse=True podczas wywoływania tf.keras.Input lub tf.keras.layers.InputLayer . Możesz przekazywać rzadkie tensory między warstwami Keras, a modele Keras zwracają je jako dane wyjściowe. Jeśli użyjesz sparse tensorów w warstwach tf.keras.layers.Dense w swoim modelu, wygenerują one gęste tensory.
Poniższy przykład pokazuje, jak przekazać rozrzedzony tensor jako dane wejściowe do modelu Keras, jeśli używasz tylko warstw obsługujących rzadkie dane wejściowe.
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
Interfejs API tf.data umożliwia tworzenie złożonych potoków wejściowych z prostych elementów wielokrotnego użytku. Jego podstawową strukturą danych jest tf.data.Dataset , która reprezentuje sekwencję elementów, w której każdy element składa się z jednego lub więcej składników.
Budowanie zbiorów danych z rzadkimi tensorami
Twórz zestawy danych z rzadkich tensorów przy użyciu tych samych metod, które są używane do tworzenia ich z tf.Tensor s lub NumPy, takich jak tf.data.Dataset.from_tensor_slices . Ta operacja zachowuje rzadkość (lub rzadką naturę) danych.
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}>
Grupowanie i rozdzielanie zbiorów danych z rzadkimi tensorami
Możesz wsadowo (łączyć kolejne elementy w jeden element) i rozdzielić zestawy danych z rzadkimi tensorami, używając odpowiednio metod Dataset.batch i 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}>
Możesz również użyć tf.data.experimental.dense_to_sparse_batch do partii elementów zestawu danych o różnych kształtach w rzadkich tensorach.
Przekształcanie zbiorów danych za pomocą rzadkich tensorów
Przekształć i utwórz rzadkie tensory w zestawach danych za pomocą 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.pociąg.Przykład
tf.train.Example to standardowe kodowanie protobuf dla danych TensorFlow. Używając sparse tensorów z tf.train.Example , możesz:
Wczytaj dane o zmiennej długości do
tf.SparseTensorprzy użyciutf.io.VarLenFeature. Powinieneś jednak rozważyć użycietf.io.RaggedFeature.Wczytaj dowolne rzadkie dane do
tf.SparseTensorza pomocątf.io.SparseFeature, który używa trzech oddzielnych kluczy funkcji do przechowywaniaindices,valuesidense_shape.
tf.function
Dekorator tf.function wstępnie oblicza wykresy TensorFlow dla funkcji Pythona, co może znacznie poprawić wydajność Twojego kodu TensorFlow. Rzadkie tensory działają przejrzyście zarówno z tf.function , jak i konkretnymi .
@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)
Rozróżnianie braków danych od wartości zerowych
Większość operacji na tf.SparseTensor s traktuje brakujące wartości i jawne wartości zerowe identycznie. Jest to zgodne z projektem — tf.SparseTensor ma działać jak gęsty tensor.
Istnieje jednak kilka przypadków, w których przydatne może być odróżnienie wartości zerowych od braków danych. W szczególności pozwala to na jeden sposób kodowania brakujących/nieznanych danych w danych treningowych. Rozważmy na przykład przypadek użycia, w którym masz tensor wyników (który może mieć dowolną wartość zmiennoprzecinkową od -Inf do +Inf) z pewnymi brakującymi wynikami. Możesz zakodować ten tensor za pomocą rzadkiego tensora, w którym jawne zera są znanymi punktami zerowymi, ale niejawne wartości zerowe w rzeczywistości reprezentują brakujące dane, a nie zero.
Zauważ, że niektóre operacje, takie jak tf.sparse.reduce_max , nie traktują brakujących wartości tak, jakby były zerowe. Na przykład po uruchomieniu poniższego bloku kodu oczekiwane dane wyjściowe to 0 . Jednak z powodu tego wyjątku wynik to -3 .
print(tf.sparse.reduce_max(tf.sparse.from_dense([-5, 0, -3])))
tf.Tensor(-3, shape=(), dtype=int32)
W przeciwieństwie do tego, gdy zastosujesz tf.math.reduce_max do gęstego tensora, wynik będzie wynosił 0, zgodnie z oczekiwaniami.
print(tf.math.reduce_max([-5, 0, -3]))
tf.Tensor(0, shape=(), dtype=int32)
Dalsze czytanie i zasoby
- Zapoznaj się z przewodnikiem dotyczącym tensorów, aby dowiedzieć się więcej o tensorach.
- Przeczytaj przewodnik po nierównych tensorach , aby dowiedzieć się, jak pracować z nierównymi tensorami — rodzajem tensora, który umożliwia pracę z niejednorodnymi danymi.
- Sprawdź ten model wykrywania obiektów w TensorFlow Model Garden , który używa rzadkich tensorów w dekoderze danych
tf.Example.
Zobacz na TensorFlow.org
Uruchom w Google Colab
Zobacz na GitHub
Pobierz notatnik