Bekerja dengan tensor yang jarang

Lihat di TensorFlow.org Jalankan di Google Colab Lihat di GitHub Unduh buku catatan

Saat bekerja dengan tensor yang berisi banyak nilai nol, penting untuk menyimpannya dengan cara yang efisien ruang dan waktu. Tensor yang jarang memungkinkan penyimpanan dan pemrosesan tensor yang efisien yang berisi banyak nilai nol. Tensor jarang digunakan secara luas dalam skema pengkodean seperti TF-IDF sebagai bagian dari pra-pemrosesan data dalam aplikasi NLP dan untuk pra-pemrosesan gambar dengan banyak piksel gelap dalam aplikasi visi komputer.

Tensor yang jarang di TensorFlow

TensorFlow mewakili tensor sparse melalui objek tf.SparseTensor . Saat ini, tensor sparse di TensorFlow dikodekan menggunakan format daftar koordinat (COO). Format pengkodean ini dioptimalkan untuk matriks hiper-jarang seperti penyematan.

Pengkodean COO untuk tensor sparse terdiri dari:

  • values : Tensor 1D dengan bentuk [N] yang berisi semua nilai bukan nol.
  • indices : Tensor 2D dengan bentuk [N, rank] , berisi indeks dari nilai bukan nol.
  • dense_shape : Tensor 1D dengan bentuk [rank] , yang menentukan bentuk tensor.

Nilai bukan nol dalam konteks tf.SparseTensor adalah nilai yang tidak dikodekan secara eksplisit. Dimungkinkan untuk secara eksplisit memasukkan nilai nol dalam values matriks sparse COO, tetapi "nol eksplisit" ini umumnya tidak disertakan saat merujuk ke nilai bukan nol dalam tensor sparse.

Membuat tf.SparseTensor

Bangun tensor sparse dengan secara langsung menentukan values , indices , dan dense_shape .

import tensorflow as tf
st1 = tf.SparseTensor(indices=[[0, 3], [2, 4]],
                      values=[10, 20],
                      dense_shape=[3, 10])

Saat Anda menggunakan fungsi print() untuk mencetak tensor sparse, ini menunjukkan konten dari tiga tensor komponen:

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))

Lebih mudah untuk memahami isi tensor sparse jika values bukan nol disejajarkan dengan indices sesuai . Tetapkan fungsi pembantu untuk mencetak cukup tensor sparse sehingga setiap nilai bukan nol ditampilkan pada barisnya sendiri.

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}>

Anda juga dapat membuat tensor sparse dari tensor padat dengan menggunakan tf.sparse.from_dense , dan mengonversinya kembali menjadi tensor padat dengan menggunakan 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)

Memanipulasi tensor yang jarang

Gunakan utilitas dalam paket tf.sparse untuk memanipulasi tensor sparse. Operasi seperti tf.math.add yang dapat Anda gunakan untuk manipulasi aritmatika tensor padat tidak bekerja dengan tensor jarang.

Tambahkan tensor sparse dengan bentuk yang sama dengan menggunakan 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}>

Gunakan tf.sparse.sparse_dense_matmul untuk mengalikan tensor sparse dengan matriks padat.

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)

Satukan tensor sparse dengan menggunakan tf.sparse.concat dan pisahkan dengan menggunakan 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)

Jika Anda menggunakan TensorFlow 2.4 atau yang lebih baru, gunakan tf.sparse.map_values untuk operasi elemen pada nilai bukan nol dalam tensor sparse.

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)

Perhatikan bahwa hanya nilai bukan nol yang dimodifikasi – nilai nol tetap nol.

Demikian pula, Anda dapat mengikuti pola desain di bawah ini untuk versi TensorFlow sebelumnya:

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)

Menggunakan tf.SparseTensor dengan API TensorFlow lainnya

Tensor jarang bekerja secara transparan dengan API TensorFlow berikut:

Contoh ditunjukkan di bawah untuk beberapa API di atas.

tf.keras

Subset dari tf.keras API mendukung tensor sparse tanpa casting mahal atau operasi konversi. Keras API memungkinkan Anda meneruskan sparse tensor sebagai input ke model Keras. Setel sparse=True saat memanggil tf.keras.Input atau tf.keras.layers.InputLayer . Anda dapat melewatkan tensor sparse di antara lapisan Keras, dan juga meminta model Keras mengembalikannya sebagai output. Jika Anda menggunakan sparse tensor di lapisan tf.keras.layers.Dense di model Anda, mereka akan menampilkan tensor padat.

Contoh di bawah ini menunjukkan cara melewatkan tensor sparse sebagai input ke model Keras jika Anda hanya menggunakan lapisan yang mendukung input sparse.

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

API tf.data memungkinkan Anda membangun saluran input yang kompleks dari bagian sederhana yang dapat digunakan kembali. Struktur data intinya adalah tf.data.Dataset , yang mewakili urutan elemen di mana setiap elemen terdiri dari satu atau lebih komponen.

Membangun kumpulan data dengan tensor yang jarang

Bangun kumpulan data dari tensor sparse menggunakan metode yang sama yang digunakan untuk membangunnya dari tf.Tensor s atau NumPy, seperti tf.data.Dataset.from_tensor_slices . Operasi ini menjaga sparsity (atau sifat sparse) data.

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 dan unbatching dataset dengan tensor yang jarang

Anda dapat mengelompokkan (menggabungkan elemen berurutan menjadi satu elemen) dan menghapus kumpulan data dengan tensor sparse menggunakan metode Dataset.batch dan Dataset.unbatch masing-masing.

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}>

Anda juga dapat menggunakan tf.data.experimental.dense_to_sparse_batch untuk mengelompokkan elemen kumpulan data dari berbagai bentuk ke dalam tensor sparse.

Mengubah Kumpulan Data dengan tensor yang jarang

Transformasikan dan buat tensor sparse di Dataset menggunakan 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.Contoh

tf.train.Example adalah enkode protobuf standar untuk data TensorFlow. Saat menggunakan sparse tensor dengan tf.train.Example , Anda dapat:

tf.function

Dekorator tf.function menghitung grafik TensorFlow untuk fungsi Python, yang secara substansial dapat meningkatkan kinerja kode TensorFlow Anda. Tensor jarang bekerja secara transparan dengan fungsi tf.function dan beton .

@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)

Membedakan nilai yang hilang dari nilai nol

Sebagian besar operasi di tf.SparseTensor memperlakukan nilai yang hilang dan nilai nol eksplisit secara identik. Ini dirancang — tf.SparseTensor seharusnya bertindak seperti tensor padat.

Namun, ada beberapa kasus yang berguna untuk membedakan nilai nol dari nilai yang hilang. Secara khusus, ini memungkinkan satu cara untuk mengkodekan data yang hilang/tidak diketahui dalam data pelatihan Anda. Misalnya, pertimbangkan kasus penggunaan di mana Anda memiliki tensor skor (yang dapat memiliki nilai floating point dari -Inf hingga +Inf), dengan beberapa skor yang hilang. Anda dapat menyandikan tensor ini menggunakan tensor sparse di mana nol eksplisit diketahui skor nol tetapi nilai nol implisit sebenarnya mewakili data yang hilang dan bukan nol.

Perhatikan bahwa beberapa operasi seperti tf.sparse.reduce_max tidak memperlakukan nilai yang hilang seolah-olah mereka nol. Misalnya, ketika Anda menjalankan blok kode di bawah ini, output yang diharapkan adalah 0 . Namun, karena pengecualian ini, outputnya adalah -3 .

print(tf.sparse.reduce_max(tf.sparse.from_dense([-5, 0, -3])))
tf.Tensor(-3, shape=(), dtype=int32)

Sebaliknya, saat Anda menerapkan tf.math.reduce_max ke tensor padat, outputnya adalah 0 seperti yang diharapkan.

print(tf.math.reduce_max([-5, 0, -3]))
tf.Tensor(0, shape=(), dtype=int32)

Bacaan lebih lanjut dan sumber daya