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.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
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.SparseTensor
przy użyciutf.io.VarLenFeature
. Powinieneś jednak rozważyć użycietf.io.RaggedFeature
.Wczytaj dowolne rzadkie dane do
tf.SparseTensor
za pomocątf.io.SparseFeature
, który używa trzech oddzielnych kluczy funkcji do przechowywaniaindices
,values
idense_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
.