Làm việc với bộ căng thưa

Xem trên TensorFlow.org Chạy trong Google Colab Xem trên GitHub Tải xuống sổ ghi chép

Khi làm việc với các tenxơ chứa nhiều giá trị 0, điều quan trọng là phải lưu trữ chúng theo cách hiệu quả về không gian và thời gian. Bộ căng thưa cho phép lưu trữ và xử lý hiệu quả các bộ căng chứa nhiều giá trị 0. Bộ căng thưa được sử dụng rộng rãi trong các lược đồ mã hóa như TF-IDF như một phần của quá trình xử lý trước dữ liệu trong các ứng dụng NLP và để xử lý trước hình ảnh có nhiều pixel tối trong các ứng dụng thị giác máy tính.

Tensor thưa thớt trong TensorFlow

TensorFlow đại diện cho các tensor thưa thớt thông qua đối tượng tf.SparseTensor . Hiện tại, các tenxơ thưa thớt trong TensorFlow được mã hóa bằng cách sử dụng định dạng danh sách tọa độ (COO). Định dạng mã hóa này được tối ưu hóa cho các ma trận siêu thưa thớt chẳng hạn như nhúng.

Mã hóa COO cho tensors thưa thớt bao gồm:

  • values : Một tensor 1D có hình dạng [N] chứa tất cả các giá trị khác không.
  • indices : Một tensor 2D có hình dạng [N, rank] , chứa các chỉ số của các giá trị khác không.
  • dense_shape : Một tensor 1D với shape [rank] , chỉ định hình dạng của tensor.

Giá trị khác không trong ngữ cảnh tf.SparseTensor một giá trị không được mã hóa rõ ràng. Có thể bao gồm các giá trị 0 một cách rõ ràng trong các values của ma trận thưa COO, nhưng các "số không rõ ràng" này thường không được đưa vào khi tham chiếu đến các giá trị khác không trong một tensor thưa thớt.

Tạo tf.SparseTensor

Xây dựng các tenxơ thưa thớt bằng cách chỉ định trực tiếp các values , indicesdense_shape .

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

Khi bạn sử dụng hàm print() để in một tensor thưa thớt, nó sẽ hiển thị nội dung của ba tensor thành phần:

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

Sẽ dễ dàng hiểu nội dung của một tensor thưa hơn nếu các values khác không được căn chỉnh với các indices tương ứng của chúng. Xác định một chức năng trợ giúp để in ra các dải phân cách thưa thớt sao cho mỗi giá trị khác không được hiển thị trên dòng riêng của nó.

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

Bạn cũng có thể xây dựng các tenxơ thưa thớt từ các tenxơ dày đặc bằng cách sử dụng tf.sparse.from_dense và chuyển đổi chúng trở lại thành tenxơ dày đặc bằng cách sử dụng 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)

Thao tác với tensors thưa thớt

Sử dụng các tiện ích trong gói tf.sparse để thao tác các tensor thưa thớt. Các lệnh như tf.math.add mà bạn có thể sử dụng để thao tác số học với các tenxơ dày đặc không hoạt động với các tenxơ thưa.

Thêm các tenxơ thưa thớt có cùng hình dạng bằng cách sử dụng 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}>

Sử dụng tf.sparse.sparse_dense_matmul để nhân các tenxơ thưa với ma trận dày đặc.

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)

Đặt các tensors thưa thớt lại với nhau bằng cách sử dụng tf.sparse.concat và tách chúng ra bằng cách sử dụng 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)

Nếu bạn đang sử dụng TensorFlow 2.4 trở lên, hãy sử dụng tf.sparse.map_values cho các phép toán theo từng phần tử trên các giá trị khác không trong các tensors thưa thớt.

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)

Lưu ý rằng chỉ các giá trị khác không mới được sửa đổi - các giá trị 0 vẫn là 0.

Tương tự, bạn có thể làm theo mẫu thiết kế bên dưới cho các phiên bản trước của 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)

Sử dụng tf.SparseTensor với các API TensorFlow khác

Các bộ căng thưa hoạt động minh bạch với các API TensorFlow này:

Ví dụ được hiển thị bên dưới cho một số API ở trên.

tf.keras

Một tập hợp con của API tf.keras hỗ trợ các tensors thưa thớt mà không cần quá trình truyền hoặc chuyển đổi tốn kém. API Keras cho phép bạn chuyển các tensors thưa thớt làm đầu vào cho mô hình Keras. Cài đặt sparse=True khi gọi tf.keras.Input hoặc tf.keras.layers.InputLayer . Bạn có thể truyền các tensors thưa thớt giữa các lớp Keras và cũng có thể yêu cầu các mô hình Keras trả lại chúng dưới dạng kết quả đầu ra. Nếu bạn sử dụng tensors thưa thớt trong tf.keras.layers.Dense các lớp trong mô hình của bạn, chúng sẽ tạo ra tensors dày đặc.

Ví dụ dưới đây cho bạn thấy cách chuyển một tensor thưa thớt làm đầu vào cho mô hình Keras nếu bạn chỉ sử dụng các lớp hỗ trợ đầu vào thưa thớt.

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 cho phép bạn xây dựng các đường ống đầu vào phức tạp từ các mảnh đơn giản, có thể tái sử dụng. Cấu trúc dữ liệu cốt lõi của nó là tf.data.Dataset , đại diện cho một chuỗi các phần tử, trong đó mỗi phần tử bao gồm một hoặc nhiều thành phần.

Xây dựng bộ dữ liệu với tensors thưa thớt

Xây dựng tập dữ liệu từ các tensor thưa thớt bằng cách sử dụng các phương pháp tương tự được sử dụng để xây dựng chúng từ các mảng tf.Tensor hoặc NumPy, chẳng hạn như tf.data.Dataset.from_tensor_slices . Op này bảo tồn tính chất thưa thớt (hoặc tính chất thưa thớt) của dữ liệu.

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

Ghép và hủy ghép các tập dữ liệu với bộ căng thưa

Bạn có thể kết hợp hàng loạt (kết hợp các phần tử liên tiếp thành một phần tử duy nhất) và bỏ nhóm các tập dữ liệu với các tensors thưa thớt bằng cách sử dụng các phương pháp Dataset.batchDataset.unbatch tương ứng.

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

Bạn cũng có thể sử dụng tf.data.experimental.dense_to_sparse_batch để xử lý hàng loạt các phần tử tập dữ liệu có hình dạng khác nhau thành các tensors thưa thớt.

Biến đổi tập dữ liệu với bộ căng thưa

Biến đổi và tạo các tensor thưa thớt trong Datasets bằng 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.Example

tf.train.Example là một mã hóa protobuf tiêu chuẩn cho dữ liệu TensorFlow. Khi sử dụng tensors thưa thớt với tf.train.Example . Ví dụ, bạn có thể:

tf.function

Trình trang trí tf.function . function tính toán trước các đồ thị TensorFlow cho các hàm Python, có thể cải thiện đáng kể hiệu suất của mã TensorFlow của bạn. Máy căng thưa hoạt động minh bạch với cả chức năng tf.functionchức năng cụ thể .

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

Phân biệt các giá trị còn thiếu với các giá trị 0

Hầu hết các hoạt động trên tf.SparseTensor xử lý các giá trị bị thiếu và các giá trị 0 rõ ràng giống hệt nhau. Đây là do thiết kế - tf.SparseTensor được cho là hoạt động giống như một tensor dày đặc.

Tuy nhiên, có một số trường hợp có thể hữu ích để phân biệt các giá trị 0 với các giá trị bị thiếu. Đặc biệt, điều này cho phép một cách để mã hóa dữ liệu bị thiếu / không xác định trong dữ liệu đào tạo của bạn. Ví dụ: hãy xem xét một trường hợp sử dụng trong đó bạn có hàng chục điểm số (có thể có bất kỳ giá trị dấu phẩy động nào từ -Inf đến + Inf), với một số điểm bị thiếu. Bạn có thể mã hóa tensor này bằng cách sử dụng tensor thưa thớt trong đó các số không rõ ràng được biết đến là điểm 0 nhưng các giá trị 0 ngầm thực sự đại diện cho dữ liệu bị thiếu chứ không phải số 0.

Lưu ý rằng một số hoạt động như tf.sparse.reduce_max không xử lý các giá trị bị thiếu như thể chúng bằng không. Ví dụ: khi bạn chạy khối mã bên dưới, đầu ra mong đợi là 0 . Tuy nhiên, vì ngoại lệ này, đầu ra là -3 .

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

Ngược lại, khi bạn áp dụng tf.math.reduce_max cho một tensor dày đặc, đầu ra là 0 như mong đợi.

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

Đọc thêm và tài nguyên