TensorFlow.org で表示 | Google Colab で実行 | GitHub でソースを表示 | ノートブックをダウンロード |
多くのゼロ値を含むテンソルを使用する場合、それらをスペース効率と時間効率の高い方法で格納することが重要です。スパーステンソルは、多くのゼロ値を含むテンソルの効率的な格納と処理を可能にします。スパーステンソルは、TF-IDF などのエンコードスキームで、NLP アプリケーションでのデータ前処理の一部として広く使用されています。また、コンピュータビジョンアプリケーションで多くの暗いピクセルを含む画像を前処理するために使用されています。
TensorFlow のスパーステンソル
TensorFlow は、tf.sparse.SparseTensor
オブジェクトを通じてスパーステンソルを表します。現在、TensorFlow のスパーステンソルは、COO 形式 (座標形式) を使用してエンコードされています。 このエンコード形式は、埋め込みなどの超疎行列用に最適化されています。
スパーステンソルの COO エンコーディングは、次で構成されます。
values
: すべての非ゼロ値を含む形状[N]
の 1 次元テンソル。indices
: 非ゼロ値のインデックスを含む、[N, rank]
の形状を持つ 2 次元テンソル。dense_shape
: テンソルの形状を指定する、形状[rank]
を持つ 1 次元テンソル。
tf.sparse.SparseTensor
のコンテキストにおける非ゼロ値は、明示的にエンコードされていない値です。COO 疎行列の values
にゼロ値を明示的に含めることは可能ですが、スパース テンソルで非ゼロ値を参照する場合、これらの「明示的なゼロ」は通常含まれません。
注意: tf.sparse.SparseTensor
では、インデックス/値が特定の順序である必要はありませんが、いくつかの演算は行優先の順序であると想定しています。tf.sparse.reorder
を使用して、標準の行優先順で並べ替えられたスパーステンソルのコピーを作成します。
tf.sparse.SparseTensor
の構築
values
、indices
、および dense_shape
を直接指定して、スパーステンソルを構築します。
import tensorflow as tf
2022-12-14 20:03:23.233432: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer.so.7'; dlerror: libnvinfer.so.7: cannot open shared object file: No such file or directory 2022-12-14 20:03:23.233525: W tensorflow/compiler/xla/stream_executor/platform/default/dso_loader.cc:64] Could not load dynamic library 'libnvinfer_plugin.so.7'; dlerror: libnvinfer_plugin.so.7: cannot open shared object file: No such file or directory 2022-12-14 20:03:23.233534: W tensorflow/compiler/tf2tensorrt/utils/py_utils.cc:38] TF-TRT Warning: Cannot dlopen some TensorRT libraries. If you would like to use Nvidia GPU with TensorRT, please make sure the missing libraries mentioned above are installed properly.
st1 = tf.sparse.SparseTensor(indices=[[0, 3], [2, 4]],
values=[10, 20],
dense_shape=[3, 10])
print()
関数を使用してスパーステンソルを出力すると、3 つの成分テンソルの内容が表示されます。
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))
非ゼロの values
が対応する indices
と整列している場合、スパーステンソルの内容が理解しやすくなります。非ゼロ値がそれぞれの行に表示されるようにスパーステンソルを出力するヘルパー関数を定義します。
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}>
また、tf.sparse.from_dense
を使用して密テンソルからスパーステンソルを構築し、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)
スパーステンソルの操作
tf.sparse
パッケージのユーティリティを使用して、スパーステンソルを操作します。密なテンソルに使用できる tf.math.add
のような算術演算は、スパーステンソルでは機能しません。
tf.sparse.add
を使用して、同じ形状のスパーステンソルを追加します。
st_a = tf.sparse.SparseTensor(indices=[[0, 2], [3, 4]],
values=[31, 2],
dense_shape=[4, 10])
st_b = tf.sparse.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}>
tf.sparse.sparse_dense_matmul
を使用して、スパーステンソルと密行列を乗算します。
st_c = tf.sparse.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)
tf.sparse.concat
を使用してスパーステンソルをまとめ、tf.sparse.slice
を使用してそれらをスライスします。
sparse_pattern_A = tf.sparse.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.sparse.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.sparse.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)
TensorFlow 2.4 以降を使用している場合は、スパーステンソルのゼロ以外の値に対する要素ごとの演算に tf.sparse.map_values
を使用します。
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)
ゼロ以外の値のみが変更されたことに注意してください。ゼロ値はゼロのままです。
同様に、TensorFlow の以前のバージョンでは、以下の設計パターンに従います。
st2_plus_5 = tf.sparse.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)
他の TensorFlow API で tf.sparse.SparseTensor
を使用する
スパーステンソルは、これらの TensorFlow API で透過的に動作します。
tf.keras
tf.data
tf.Train.Example
protobuftf.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
上記の API のいくつかの例を以下に示します。
tf.keras
tf.keras
API のサブセットは、コストが高いキャストや変換演算なしでスパーステンソルをサポートします。Keras API を使用すると、スパーステンソルを入力として Keras モデルに渡すことができます。tf.keras.Input
または tf.keras.layers.InputLayer
を呼び出すときには sparse=True
を設定します。 Keras レイヤー間でスパーステンソルを渡し、Keras モデルがそれらを出力として返すようにすることもできます。モデルの tf.keras.layers.Dense
レイヤーでスパーステンソルを使用すると、密テンソルが出力されます。
以下の例は、スパース入力をサポートするレイヤーのみを使用する場合に、スパーステンソルを入力として Keras モデルに渡す方法を示しています。
x = tf.keras.Input(shape=(4,), sparse=True)
y = tf.keras.layers.Dense(4)(x)
model = tf.keras.Model(x, y)
sparse_data = tf.sparse.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)
1/1 [==============================] - 0s 86ms/step array([[-0.32035512, -0.46087486, -0.7131735 , -1.9910709 ], [ 0. , 0. , 0. , 0. ], [ 0. , 0. , 0. , 0. ], [ 0. , 0. , 0. , 0. ], [ 0.677319 , -0.18420744, 0.3503905 , -0.58141994], [ 0.25797474, 0.36448914, -0.10873491, -1.3242698 ]], dtype=float32)
tf.data
tf.data は、単純で再利用可能な部分から複雑な入力パイプラインを構築できる API です。主なデータ構造は tf.data.Dataset
で、一連の要素を表し、その各要素には 1 つ以上の成分が含まれます。
スパーステンソルを使用したデータセットの構築
tf.data.Dataset.from_tensor_slices
など、tf.Tensor
または NumPy 配列からデータセットを構築するために使用されるメソッドを使用して、スパーステンソルからデータセットを構築します。この演算は、データのスパース性を保持します。
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}>
スパーステンソルを使用したデータセットのバッチ処理とバッチ処理解除
スパーステンソルを使用してデータセットをバッチ処理 (連続する要素を 1 つの要素に結合) およびバッチ処理解除できます。バッチ処理には Dataset.batch
メソッド、バッチ処理解除には 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}>
また、tf.data.experimental.dense_to_sparse_batch
を使用して、さまざまな形状のデータセット要素をスパーステンソルにバッチ処理することもできます。
スパーステンソルを使用したデータセットの変換
Dataset.map
を使用して、データセット内のスパーステンソルを変換および作成します。
transform_dataset = dataset.map(lambda x: x*2)
for i in transform_dataset:
print(pprint_sparse_tensor(i))
WARNING:tensorflow:From /tmpfs/src/tf_docs_env/lib/python3.9/site-packages/tensorflow/python/autograph/pyct/static_analysis/liveness.py:83: Analyzer.lamba_check (from tensorflow.python.autograph.pyct.static_analysis.liveness) is deprecated and will be removed after 2023-09-23. Instructions for updating: Lambda fuctions will be no more assumed to be used in the statement where they are used, or at least in the same block. https://github.com/tensorflow/tensorflow/issues/56089 <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
は、TensorFlow データの標準の protobuf エンコーディングです。tf.train.Example
でスパーステンソルを使用すると、次のことができます。
tf.io.VarLenFeature
を使用して、可変長データをtf.sparse.SparseTensor
に読み込みますが、代わりにtf.io.RaggedFeature
の使用を検討する必要があります。tf.io.SparseFeature
を使用して任意のスパースデータをtf.sparse.SparseTensor
に読み込みます。これは 3 つの個別の特徴キーを使用してindices
、values
、およびdense_shape
を格納します。
tf.function
tf.function
は、Python 関数向けに TensorFlow グラフを事前計算するデコレータで、TensorFlow コードのパフォーマンスを大幅に向上できます。スパーステンソルは、tf.function
と concrete functions の両方で透過的に動作します。
@tf.function
def f(x,y):
return tf.sparse.sparse_dense_matmul(x,y)
a = tf.sparse.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)
欠損値とゼロ値の区別
tf.sparse.SparseTensor
のほとんどの演算は、欠損値と明示的なゼロ値を同じように扱います。これは設計によるもので、tf.sparse.SparseTensor
は密なテンソルのように振る舞います。
ただし、ゼロ値と欠損値を区別することが役立つ場合がいくつかあります。特に、トレーニングデータ内の欠落/不明なデータをエンコードする場合に便利です。たとえば、スコアのテンソル (-Inf から +Inf までの任意の浮動小数点値を持つことができる) があり、スコアが欠落しているユースケースを考えてみます。このテンソルは、明示的なゼロが既知のゼロスコアであるスパーステンソルを使用してエンコードできますが、暗黙的なゼロ値は実際には欠損データを表し、ゼロではありません。
注意: これは一般的に、tf.sparse.SparseTensor
の意図した使用法ではないので、既知/未知の値の位置を識別する別のマスクテンソルを使用するなど、これをエンコードするための他の手法も検討してみてください。ただし、ほとんどのスパース演算は明示的ゼロ値と暗黙的ゼロ値を同じように扱うため、このアプローチを使用するときは注意してください。
tf.sparse.reduce_max
のような一部の演算は、欠損値をゼロであるかのように扱わないことに注意してください。たとえば、以下のコードブロックを実行すると、期待される出力は 0
になります。ただし、この例外のため、出力は -3
になります。
print(tf.sparse.reduce_max(tf.sparse.from_dense([-5, 0, -3])))
tf.Tensor(-3, shape=(), dtype=int32)
対照的に、密テンソルに tf.math.reduce_max
を適用すると、出力は予想どおり 0 になります。
print(tf.math.reduce_max([-5, 0, -3]))
tf.Tensor(0, shape=(), dtype=int32)
その他の資料とリソース
- テンソルについての詳細は、テンソルガイドを参照してください。
- 不均一なデータを扱うテンソルの一種である不規則テンソルの使用方法については、不規則テンソルガイドを参照してください。
tf.Example
データデコーダでスパーステンソルを使用する TensorFlow Model Garden のオブジェクト検出モデルをご確認ください。