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
là 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
, indices
và dense_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:
-
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
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.batch
và Dataset.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ể:
Đọc dữ liệu có độ dài thay đổi vào
tf.SparseTensor
bằng cách sử dụngtf.io.VarLenFeature
. Tuy nhiên, bạn nên cân nhắc sử dụngtf.io.RaggedFeature
để thay thế.Đọc dữ liệu thưa thớt tùy ý vào
tf.SparseTensor
bằng cách sử dụngtf.io.SparseFeature
, sử dụng ba khóa tính năng riêng biệt để lưu trữ cácindices
,values
vàdense_shape
.
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.function
và chứ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
- Tham khảo hướng dẫn về tensor để tìm hiểu về tensor.
- Đọc hướng dẫn về tensor rách để tìm hiểu cách làm việc với tensor rách, một loại tensor cho phép bạn làm việc với dữ liệu không đồng nhất.
- Kiểm tra mô hình phát hiện đối tượng này trong Vườn mô hình TensorFlow sử dụng các tensor thưa thớt trong bộ giải mã dữ liệu
tf.Example
.