[TensorFlow] APIドキュメントを眺める -Math編-

2015.12.23

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

前回のTensor編では Tensor クラスを中心としたテンソルの生成と、各種プロパティ、及びその形状を示す TensorShape についての解説を行いました。

本記事ではAPIドキュメントの Math セクションを中心にまとめてみます。

以下断りのない限り tensorflow ライブラリを tf で表し、x, y, a, b は同じ関数で用いられている時には同じ数値の型をもつ Tensor とします。

四則演算

関数 役割 備考
tf.add(x, y, name=None) 要素ごとの和
tf.sub(x, y, name=None) 要素ごとの差
tf.mul(x, y, name=None) 要素ごとの積
tf.div(x, y, name=None) 要素ごとの商 テンソルの数値型がint等の浮動小数でない型である場合、小数点以下切り捨て
tf.truediv(x, y, name=None) 要素ごとの商 テンソルの数値型がint等の浮動小数でない型である場合、先に浮動小数点型に変換する
tf.floordiv(x, y, name=None) 要素ごとの商 テンソルの数値型が浮動小数点型の場合、結果の小数点以下切り捨て
tf.mod(x, y, name=None) 要素ごとの剰余

各関数の最後の引数には name を指定して operation の名前を指定できます。

なお、サイズの異なる Tensor 同士を足し合わせると次のような挙動を示します

In [1]: import tensorflow as tf
In [2]: a = tf.constant([[2, 1], [1, 2]])
In [3]: b = tf.constant([[2, 9]])
In [4]: c = tf.add(a, b)
In [5]: sess = tf.Session()
In [6]: result = sess.run(c)
In [7]: result
Out[7]:
array([[ 4, 10],
       [ 3, 11]], dtype=int32)

小さい方のベクトルは大きい方のサイズに合わせた行列に変形され、各行が同じ行ベクトルをもつような行列にされた上で行列同士が足し合わされているものと見られます。

基礎的な数学関数

関数 役割 備考
tf.add_n(inputs, name=None) 要素ごとの和 inputsはテンソルのリスト、全てが同じサイズをもつ必要あり
tf.abs(x, name=None) 要素ごとの絶対値
tf.neg(x, name=None) 要素ごとにマイナスをかける
tf.sign(x, name=None) 要素ごとに正なら1、0なら0、負なら-1となる変換をかける
tf.inv(x, name=None) 要素ごとの逆数
tf.square(x, name=None) 要素ごとに二乗をとる
tf.round(x, name=None) 要素ごとに四捨五入
tf.sqrt(x, name=None) 要素ごとにルートをとる
tf.rsqrt(x, name=None) 要素ごとにルートの逆数を取る
tf.pow(x, y, name=None) 要素ごとに累乗(xの要素^yの要素)
tf.exp(x, name=None) 要素ごとに自然数を底とする指数関数をとる
tf.log(x, name=None) 要素ごとに自然対数をとる
tf.ceil(x, name=None) 要素ごとに小数点以下繰り上げ
tf.floor(x, name=None) 要素ごとに小数点以下切り捨て
tf.maximum(x, y, name=None) 要素ごとに最大値をとる
tf.minimum(x, y, name=None) 要素ごとに最小値をとる
tf.cos(x, name=None) 要素ごとにcosをとる
tf.sin(x, name=None) 要素ごとにsinをとる

先ほどと同様に各関数の最後の引数には name を指定できます。

行列に関わる関数

関数 役割 備考
tf.diag(diagonal, name=None) 対角テンソルを生成 この表の下に詳細を記載
tf.transpose(a, perm=None, name='transpose') 転置テンソルを生成 この表の下に詳細を記載
tf.matmul(a, b, transpose_a=False, transpose_b=False, a_is_sparse=False, b_is_sparse=False, name=None) 行列の積 a, b は行列、transpose_a等は転置フラグ、a_is_sparse等は疎行列フラグ。疎行列フラグがある場合は効率的なアルゴリズムが適用される可能性あり
tf.batch_matmul(x, y, adj_x=None, adj_y=None, name=None) テンソルの行列スライスの積を計算 この表の下に詳細を記載
tf.matrix_determinant(input, name=None) 行列式を求める inputは行と列の次数が同じ行列
tf.batch_matrix_determinant(input, name=None) テンソルの行列スライスの行列式を計算 tf.batch_matmulと同様のフローで各スライスの行列式を計算
tf.matrix_inverse(input, name=None) 逆行列を求める inputは行と列の次数が同じ行列。逆行列を持たない場合、この操作は動作を保証されない
tf.batch_matrix_inverse(input, name=None) テンソルの行列スライスの逆行列を計算 tf.batch_matmulと同様のフローで各スライスの逆行列を計算
tf.cholesky(input, name=None) コレスキー分解を実施し分解した結果の下三角行列を求める inputは正定値行列
tf.batch_cholesky(input, name=None) テンソルの行列スライスのコレスキー分解の結果を計算 tf.batch_matmulと同様のフローで各スライスのコレスキー分解を計算
tf.self_adjoint_eig(input, name=None) エルミート行列による固有値分解を行う inputは行と列の次数が同じ下三角行列。結果はM+1xMで得られ、はじめの列が固有値、以降の列が固有ベクトル
tf.batch_self_adjoint_eig(input, name=None) テンソルの行列スライスのエルミート行列による固有値分解を計算 tf.batch_matmulと同様のフローで各スライスの固有値分解を計算

対角テンソルを生成 (tf.diag)

対角テンソルの生成は入力 diagonal の階数に応じては複雑な場合があります。まず1階のテンソルであるベクトル

[latex] \boldsymbol{d} = \left( \begin{array}{c} d_1 \\ d_2 \\ \vdots \\ d_n \end{array} \right) [/latex]

の入力に対してはつぎのような行列(=2階のテンソル)を生成します。

[latex] D = \begin{eqnarray} \begin{pmatrix} d_{11} & & & 0 \\ & d_{22} & & \\ & & \ddots & \\ 0 & & & d_{ nn } \end{pmatrix} \end{eqnarray} [/latex]

2階のテンソルである行列

[latex] D = \begin{eqnarray} \begin{pmatrix} d_{11} & d_{12} & \ldots & d_{1n} \\ d_{21} & d_{22} & \ldots & d_{2n} \\ \vdots & \vdots & \ddots & \vdots \\ d_{n1} & d_{n2} & \ldots & d_{ nn } \end{pmatrix} \end{eqnarray} = (d_{ij}) [/latex]

の入力に対しては次のように計算される成分を有するテンソルを生成します。

[latex] \begin{eqnarray} D^{'} = (d_{ijkl}) \ \ \mathrm{where} \ \ d_{ijkl} = \begin{cases} d_{mn} & ( i = k = m, \ j = l = n ) \\ 0 & ( otherwise ) \end{cases} \end{eqnarray} [/latex]

iPythonで確認すると以下の通りです。

In [1]: import tensorflow as tf
In [2]: x = tf.constant([[11, 12], [21, 22]])
In [3]: sess = tf.Session()
In [4]: sess.run(x)
Out[4]:
array([[11, 12],
       [21, 22]], dtype=int32)
In [5]: sess.run(tf.diag(x))
Out[5]:
array([[[[11,  0],
         [ 0,  0]],

        [[ 0, 12],
         [ 0,  0]]],


       [[[ 0,  0],
         [21,  0]],

        [[ 0,  0],
         [ 0, 22]]]], dtype=int32)

ちょうどテンソルの各対角成分(1111, 1212, 2121, 2222)にそれぞれ行列の各成分(11, 12, 21, 22)が対応していることが分かります。

3次以上も同様にしてk階テンソル

[latex] D = (d_{i_{1}i_{2}\ldots i_{k}}) [/latex]

の入力に対しては次のように計算される成分を有するテンソルを生成します。

[latex] \begin{eqnarray} D^{'} = (d_{j_{1}j_{2}\ldots j_{2k}}) \ \ \mathrm{where} \ \ d_{j_{1}j_{2}\ldots j_{2k}} = \begin{cases} d_{m_{1}m_{2}\ldots m_{k}} & ( \ j_{1} = j_{k+1} = m_{1}, \ j{2} = j_{k+2} = m_{2}, \ldots , j_{k} = j_{2k} = m_{k} ) \\ 0 & ( \mathrm{otherwise} ) \end{cases} \end{eqnarray} [/latex]

転置テンソルを生成 (tf.transpose)

対角テンソルの生成と同様、転置テンソルもオプション引数の指定方法によっては生成規則が複雑な場合があります。

まず行列

[latex] D = \begin{eqnarray} \begin{pmatrix} d_{11} & d_{12} & \ldots & d_{1m} \\ d_{21} & d_{22} & \ldots & d_{2m} \\ \vdots & \vdots & \ddots & \vdots \\ d_{n1} & d_{n2} & \ldots & d_{ nm } \end{pmatrix} \end{eqnarray} = (d_{ij}) [/latex]

については転置テンソルは次のような行と列を入れ替えたものになります。

[latex] D^{\mathrm{T}} = \begin{eqnarray} \begin{pmatrix} d_{11} & d_{21} & \ldots & d_{n1} \\ d_{12} & d_{22} & \ldots & d_{n2} \\ \vdots & \vdots & \ddots & \vdots \\ d_{1m} & d_{n2} & \ldots & d_{ mn } \end{pmatrix} \end{eqnarray} = (d_{ji}) [/latex]

この式に対応する動作を iPython で確認すると次の通りです

In [1]: import tensorflow as tf
In [2]: x = tf.constant([[1, 2, 3], [4, 5, 6]])
In [3]: sess = tf.Session()
In [4]: sess.run(x)
Out[4]:
array([[1, 2, 3],
       [4, 5, 6]], dtype=int32)
In [5]: sess.run(tf.transpose(x))
Out[5]:
array([[1, 4],
       [2, 5],
       [3, 6]], dtype=int32)

3階以上のテンソルについては引数 perm が大きな役割を果たします。何も指定しない場合はデフォルトで perm ( permutation = 置換 )はテンソル

[latex] D = (d_{i_{1}i_{2}\ldots i_{n}}) [/latex]

に対してテンソルの各成分の順序を逆にした

[latex] D^{'} = (d_{i_{n}i_{n-1}\ldots i_{1}}) [/latex]

を生成しますが、perm を指定すると指定されたパラメータにしたがって添字を入れ替えてくれます。

3階のテンソル

[latex] D = (d_{ijk}) \ \ \mathrm{where} \ \ d_{ijk} = 100i + 10j + k [/latex]

について iPython で操作した例を見てみましょう

In [1]: import tensorflow as tf
In [2]: x = tf.constant([[[111, 112], [121, 122]], [[211, 212], [221, 222]]])
In [3]: sess = tf.Session()
In [4]: sess.run(x)
Out[4]:
array([[[111, 112],
        [121, 122]],

       [[211, 212],
        [221, 222]]], dtype=int32)
In [5]: sess.run(tf.transpose(x))
Out[5]:
array([[[111, 211],
        [121, 221]],

       [[112, 212],
        [122, 222]]], dtype=int32)
In [6]: sess.run(tf.transpose(x, perm=[0, 2, 1]))
Out[6]:
array([[[111, 121],
        [112, 122]],

       [[211, 221],
        [212, 222]]], dtype=int32)

3階テンソルの各成分はパラメータを指定しない転置によって各成分が逆さまになり、一行記法による置換 [0, 2, 1](0階成分を入れ替えず、1, 2階成分を入れ替える)を付与することで指定された各成分が逆さまになっていることが分かります。

テンソルの行列スライスの積を計算 (tf.batch_matmul)

行列スライス

3階以上のテンソル

[latex] D = (d_{k_{1}k_{2}\ldots k_{n}}) \ \ \ ( n \geq 3 ) [/latex]

に対して次のように末尾2階以外に対して固定された成分 \(k_{1}k_{2}\ldots k_{n-2} \) をもった行列

[latex] M_{k_{1}k_{2}\ldots k_{n-2}} = (m_{ij}) \ \ \mathrm{where} \ \ m_{ij} = d_{k_{1}k_{2}\ldots k_{n-2}ij} [/latex]

を行列スライスと呼びます

batch_matmul

batch_matmul は入力の2つのテンソルの行列スライス同士に対して積を実施していきます。例えば入力としてさきほどの3階のテンソルを考えてみます。

[latex] D = (d_{ijk}) \ \ \mathrm{where} \ \ d_{ijk} = 100i + 10j + k [/latex]

これに対して行列スライスが単位行列であるような3階のテンソル \(E\)

[latex] \begin{eqnarray} E = (e_{ijk}) \ \ \mathrm{where} \ \ e_{ijk} = \begin{cases} 1 & ( j = k ) \\ 0 & ( \mathrm{otherwise} ) \end{cases} \end{eqnarray} [/latex]

を用意して、2つのテンソルに対する batch_matmul を実行します。\(E\) の行列スライスが単位行列の為、結果についてはもとの \(D\) と変わらないです。

In [1]: import tensorflow as tf
In [2]: d = tf.constant([[[111, 112], [121, 122]], [[211, 212], [221, 222]]])
In [3]: e = tf.constant([[[1, 0], [0, 1]], [[1, 0], [0, 1]]])
In [4]: sess = tf.Session()
In [5]: sess.run(d)
Out[5]:
array([[[111, 112],
        [121, 122]],

       [[211, 212],
        [221, 222]]], dtype=int32)
In [6]: sess.run(tf.batch_matmul(d, e))
Out[6]:
array([[[111, 112],
        [121, 122]],

       [[211, 212],
        [221, 222]]], dtype=int32)

batch_matmul には adjx フラグ等が用意されていますが、これは行列スライスの転置をとるかどうかのオプション引数です。

この batch_matmul 関数と同様の手続きで他の batch_xxx 系関数も理解できます。その全てがテンソルの行列スライスに対して何らかの処理を施し、計算結果をテンソルとして得るような関数です。

複素数に関わる関数

関数 役割 備考
tf.complex(real, imag, name=None) 複素テンソルを生成 realが実部テンソルimagが虚部テンソル
tf.complex_abs(x, name=None) 要素ごとに複素数としての絶対値をとる
tf.conj(in_, name=None) 要素ごとに複素共役なテンソルを生成
tf.imag(in_, name=None) 要素ごとに虚部をとったテンソルを生成
tf.real(in_, name=None) 要素ごとに実部をとったテンソルを生成

縮約操作系関数

関数 役割 備考
tf.reduce_sum(input_tensor, reduction_indices=None, keep_dims=False, name=None) 和の操作で縮約 この表の下に詳細を記載
tf.reduce_prod(input_tensor, reduction_indices=None, keep_dims=False, name=None) 積の操作で縮約
tf.reduce_min(input_tensor, reduction_indices=None, keep_dims=False, name=None) 最小をとって縮約
tf.reduce_max(input_tensor, reduction_indices=None, keep_dims=False, name=None) 最大をとって縮約
tf.reduce_mean(input_tensor, reduction_indices=None, keep_dims=False, name=None) 平均値をとって縮約
tf.reduce_all(input_tensor, reduction_indices=None, keep_dims=False, name=None) and操作で縮約 input_tensorはBool値を要素とするテンソル
tf.reduce_any(input_tensor, reduction_indices=None, keep_dims=False, name=None) or操作で縮約 input_tensorはBool値を要素とするテンソル
tf.accumulate_n(inputs, shape=None, tensor_dtype=None, name=None) テンソルの要素ごとに総和をとる tf.add_n とは異なり、入力テンソル要素の数値型に一致が必要

縮約操作系関数について

このセクションにある関数の多くは reduce_xxx のような名前を持ち、引数として一つのテンソルをとり、オプション引数には reduction_indices, keep_dims をとります。

reduce_sum 関数を例に取ってその挙動を確認してみます。

まず、reduce_sum 関数はテンソルのみを引数に渡すと、全ての要素で和をとり0階のテンソルとしてスカラー値を返却します。

In [1]: import tensorflow as tf
In [2]: x = tf.constant([[1, 1, 1], [1, 1, 1]])
In [3]: sess = tf.Session()
In [4]: sess.run(tf.reduce_sum(x))
Out[4]: 6

reduction_indices 引数に一つの値を渡すとその引数の階数についての添字を足し合わせた結果を返します

In [5]: sess.run(tf.reduce_sum(x, 0))
Out[5]: array([2, 2, 2], dtype=int32)
In [6]: sess.run(tf.reduce_sum(x, 1))
Out[6]: array([3, 3], dtype=int32)

keep_dims 引数が True の時、結果として得られるテンソルの階数について入力テンソルの階数を保持します。いま行った操作に対して keep_dims 引数を True にしてみます。

In [7]: sess.run(tf.reduce_sum(x, 0, keep_dims=True))
Out[7]: array([[2, 2, 2]], dtype=int32)
In [8]: sess.run(tf.reduce_sum(x, 1, keep_dims=True))
Out[8]:
array([[3],
       [3]], dtype=int32)

括弧の深さ(=テンソルの階数)は入力と出力で変化していないことが分かります。

reduction_indices 引数には複数の値を渡すこともできます。その場合は与えた値の階数についての添字を足し合わせた結果を返します。

In [9]: sess.run(tf.reduce_sum(x, [0, 1]))
Out[9]: 6

今回は reduce_sum 関数を例に取りましたが、他の縮約操作系関数についても同様のことが言えます。

セグメント系関数

関数 役割 備考
tf.segment_sum(data, segment_ids, name=None) 各セグメント内での和 セグメントについてはこの表の下に詳細を記載
tf.segment_prod(data, segment_ids, name=None) 各セグメント内での積 同上
tf.segment_min(data, segment_ids, name=None) 各セグメント内での最小値 同上
tf.segment_max(data, segment_ids, name=None) 各セグメント内での最大値 同上
tf.segment_mean(data, segment_ids, name=None) 各セグメント内での平均値 同上
tf.unsorted_segment_sum(data, segment_ids, num_segments, name=None) 各セグメント内での和 この表の下に詳細を記載
tf.sparse_segment_sum(data, indices, segment_ids, name=None) 選択したセグメント内での和 この表の下に詳細を記載
tf.sparse_segment_mean(data, indices, segment_ids, name=None) 選択したセグメント内での平均値 同上

セグメントについて

segment_xxx のような形の関数で用いられる概念として セグメント があります。これは0階成分(ベクトルなら要素、行列なら行)でテンソルを分割したものになります。 セグメントへの分割は0階成分の各添字がどのセグメントに配置されるかを示す segment_ids 引数を通じて行われ、segment_ids 引数の要素数は0階成分の次数と一致します。

segment_sum 関数を例にとってセグメントの概念を具体的に説明してみます。

今、次のような行列があったとします。

[latex] A = \begin{eqnarray} \begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ -4 & -5 & -6 \\ \end{pmatrix} \end{eqnarray} [/latex]

segment_idsをtf.constant([0, 1, 1])でとったとすると対応するセグメントを表す行列 \( A_0, A_1 \) は以下のようになります。

[latex] A_0 = \begin{eqnarray} \begin{pmatrix} 1 & 2 & 3 \end{pmatrix} \end{eqnarray} [/latex]

[latex] A_1 = \begin{eqnarray} \begin{pmatrix} 4 & 5 & 6 \\ -4 & -5 & -6 \end{pmatrix} \end{eqnarray} [/latex]

segment_sum 関数の場合はセグメントごとに和をとるため \( A_1 \) は行成分で足しあわせて相殺された0のみを成分にもつ1x3行列になります。関数の各セグメントでの計算結果は最終的に結合され、以下の様な実行結果が得られることになります。

In [1]: import tensorflow as tf
In [2]: x = tf.constant([[1, 2, 3], [4, 5, 6], [-4, -5, -6]])
In [3]: sess = tf.Session()
In [4]: sess.run(tf.segment_sum(x, tf.constant([0, 1, 1])))
Out[4]:
array([[1, 2, 3],
       [0, 0, 0]], dtype=int32)

3階以上のテンソルについても同様に0階の成分でセグメントわけし、その成分に関しての和を各セグメントでとります。そして各セグメントが結合された結果が得られます。

segment_sum, segment_prod, segment_min, segment_max, segment_mean 関数については同様の考え方で各セグメントに対する計算が実行されます。

これらの関数で用いられる segment_ids は ソート済みでかつ値が増えるときは1づつ増えていくようなベクトルである必要があります。

unsortd_segment_sum

segment_sum 関数に渡す segment_ids について条件をゆるめ、ソート済みでないID列も受け付けるようにした関数です。num_segments 引数にはセグメントの総数を渡します。

In [5]: sess.run(tf.unsorted_segment_sum(x, tf.constant([1, 0, 0]), 2))
Out[5]:
array([[0, 0, 0],
       [1, 2, 3]], dtype=int32)

sparse_segment_sum

segment_sum 関数に計算を行う0階成分を選択する機能をもたせた関数です。indices引数にはテンソル0階成分のどの部分を計算の対象とするか渡します。

In [6]: sess.run(tf.sparse_segment_sum(x, tf.constant([1, 2]), tf.constant([0, 0])))
Out[6]: array([[0, 0, 0]], dtype=int32)

sparse_segment_mean関数も同様の機能を持ちます。

比較と添字に関する関数

関数 役割 備考
tf.argmin(input, dimension, name=None) 指定した階数についての最小値の添字 この表の下に詳細を記載
tf.argmax(input, dimension, name=None) 指定した階数についての最小値の添字 同上
tf.listdiff(x, y, name=None) ベクトルの差分 この表の下に詳細を記載
tf.where(input, name=None) Trueを持つ値の添字のリスト inputはBool値を要素とするテンソル
tf.unique(x, name=None) ベクトル内の重複のない要素 この表の下に詳細を記載
tf.edit_distance(hypothesis, truth, normalize=True, name='edit_distance') 要素ごとのレーベンシュタイン距離 この表の下に詳細を記載
tf.invert_permutation(x, name=None) 逆置換 引数 x は置換を一行記法で表したベクトル

argmin

引数が1階のテンソル(= ベクトル)だった場合、dimensionに0を指定してこの関数は最小値をもつ要素の添字を返します。

引数が2階以上のテンソルだった場合について考えてみましょう。

行列についてはdimensionに0を指定すると、行成分についての最小値をもつ要素の添字を返却します。

In [1]: import tensorflow as tf
In [2]: sess = tf.Session()
In [3]: x = tf.constant([[1, 6, 2], [5, 3, 4]])
In [4]: sess.run(tf.argmin(x, 0))
Out[4]: array([0, 1, 0])

一方dimensionに1を指定すると、列成分についての最小値を持つ要素の添字を返却します。

In [5]: sess.run(tf.argmin(x, 1))
Out[5]: array([0, 1])

\( n \ (\geq 3) \) 階テンソル

[latex] T = (t_{k_{1}k_{2}\ldots k_{n}}) \ \ \ ( n \geq 3 ) [/latex]

に対して、この関数はつぎのような成分をもつ \(n-1\) 階テンソルを返却します。( \( d = \mathrm{dimension} \) )

[latex] \mathrm{argmin}_{d} T = (m_{l_1 \ldots l_{n-1}}) \ \ \mathrm{where} \ \ m_{l_1 \ldots l_{n-1}} = \mathrm{argmin}_d t_{l_{1}l_{2} \ldots d \ldots l_{n-1}} [/latex]

argmaxについても同様です。

listdiff

listdiff 関数はベクトル x, y を引数にとり、x に含まれる要素のうち、y に含まれる要素を削除する操作を行います。

返り値として以下のようなタプルが返却されます。

(out, idx)

ここで out は x から y に含まれる要素を削除したもの、idxout にある各要素の x 上での添字を表すベクトルです。

In [1]: import tensorflow as tf
In [2]: sess = tf.Session()
In [3]: x = tf.constant([1, 2, 3, 4, 5, 6])
In [4]: y = tf.constant([1, 3, 5])
In [5]: sess.run(tf.listdiff(x, y))
Out[5]: [array([2, 4, 6], dtype=int32), array([1, 3, 5], dtype=int32)]

unique

unique 関数はベクトル x の要素を重複なく取得する操作を行います。

返り値としては listdiff と同じく以下の様なタプルが返却されます。

(out, idx)

ここで out は x に含まれる重複のない要素全体、idxout にある各要素の x 上での添字を表すベクトルです。

In [1]: import tensorflow as tf
In [2]: sess = tf.Session()
In [3]: x = tf.constant([1, 1, 2, 4, 4, 4, 7, 8, 8])
In [4]: sess.run(tf.unique(x))
Out[4]:
[array([1, 2, 4, 7, 8], dtype=int32),
 array([0, 0, 1, 2, 2, 2, 3, 4, 4], dtype=int32)]

edit_distance

edit_distance 関数は SparseTensor クラスのインスタンスである2つの引数 hypothesis と truth をとります。

hypothesis に含まれる各文字列が truth に含まれる各文字列になるまでに必要な手順の最小回数(レーベンシュタイン距離)をテンソルの各要素に対して計算します。

SparseTensor については本記事では解説を省き、別の記事にまわします。

hypothesis, truth ともに最後の階が文字列を表す可変長なフィールドとして使われます。例えばコードで説明すると次のように3階テンソルに対して文字列を代入することで、あたかも行列の各要素に文字列が入っている変数として扱われます。

In [1]: import tensorflow as tf
In [2]: sess = tf.Session()
In [3]: truth = tf.SparseTensor(indices = tf.constant([[0, 1, 0], [1, 0, 0], [1, 0, 1], [1, 1, 0]], tf.int64), values = ["a", "b", "c", "a"], shape = tf.constant([2, 2, 2], tf.int64))
In [4]: sess.run(truth)
Out[4]:
SparseTensorValue(indices=array([[0, 1, 0],
       [1, 0, 0],
       [1, 0, 1],
       [1, 1, 0]]), values=array(['a', 'b', 'c', 'a'], dtype=object), shape=array([2, 2, 2]))

SparseTensorの生成の際、int64 の型を明示的にしたのはのちの edit_distance 関数の使用の際に型のエラーが出るためです。

この truth は TensorFlow 上では Sparse Tensor として扱われますが、解釈上は行列に文字列が入っているものと考えた方が理解しやすいです。

[latex] \mathrm{truth} = \begin{eqnarray} \begin{pmatrix} "" & "a" \\ "bc" & "a" \\ \end{pmatrix} \end{eqnarray} [/latex]

hypothesis を次のように宣言すると。

In [5]: hypothesis = tf.SparseTensor(indices = tf.constant([[0, 0, 0], [1, 0, 0]], tf.int64), values = ["a", "b"], shape = tf.constant([2, 1, 1], tf.int64))

これは次のようなベクトルに文字列が入っているものと解釈できます。

[latex] \mathrm{hypothesis} = \begin{eqnarray} \begin{pmatrix} "a" \\ "b" \\ \end{pmatrix} \end{eqnarray} [/latex]

truth, hypothesis を edit_distance 関数に代入してレーベンシュタイン距離を計算すると以下のような値が返却されます。

In [6]: sess.run(tf.edit_distance(hypothesis, truth))
Out[6]:
array([[ inf,  1. ],
       [ 0.5,  1. ]], dtype=float32)

truth に対応する hypothesis の要素がある (00, 10) 成分にはそれぞれ正規化されたレーベンシュタイン距離(truthの長さで元の結果を割った)が付与されます。

00 成分は元のレーベンシュタイン距離としては "a" の削除操作のみの1になりますが、truth の 00 成分の文字列長が0のため、正規化されたものは無限大になっています。

10 成分は元のレーベンシュタイン距離としては、"c" の追加操作のみの1になりますが、truth の 10 成分の文字列長が2のため、正規化されたものは0.5になっています。

hypothesis のベクトルの方がサイズが小さく、truth に対応する hypothesis の要素が (01, 11) の成分にはないため、結果値としては1が返却されます。

デフォルトでは normarize 引数は True になっていますが、False にすると次のように正規化されないレーベンシュタイン距離が得られます。

In [7]: sess.run(tf.edit_distance(hypothesis, truth, normalize = False))
Out[7]:
array([[ 1.,  1.],
       [ 1.,  1.]], dtype=float32)

おわりに

比較と添字に関する関数の edit_distance の箇所で出てきたSparse Tensor については後日改めて Sparse Tensor 編としてまとめられればと思います。お楽しみに!

参考