#はじめに
Kerasのコードを読むと様々な行列演算に遭遇する。これらの演算の中身を知らないと読み進めることが非常に難しい。今回、私が読んだコードを中心に、Kerasによく出てくる行列演算を実例を元に確認したため共有する。
#環境
Kerasといっても、今回確認した環境は、tensorflowの一部であった時代の古いバージョンである。ただ、行列演算は今とそれほど変わらないと思う。
- tensoflow 1.14.0
###サンプルを動作させる場合の注意点
本記事のソースコードは以下のインポートを前提としています。動作させる場合はコピペしておいてください。
import numpy as np
import warnings
import os
warnings.simplefilter('ignore')
import tensorflow as tf
from tensorflow.keras.models import Sequential, Model
from tensorflow.keras.layers import Dense, Input, Dropout, BatchNormalization
#確認方法
確認方法は、 Kerasでの処理をイメージできることを想定し、以下の通りとした。
-
演算を行うKerasのモデルを作成
-
モデルのpredictメソッドに入力を与え、その結果を出力させることで、計算の内容を確認
#やってみよう
##concat
指定した軸に沿ってテンソルのリストを連結する。
ソース
input1 = Input(shape=(2,))
input2 = Input(shape=(2,))
output = tf.concat(axis=1, values=[input1, input2])
model = Model(inputs=[input1, input2], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[10, 20], [20, 30]]), np.array([[20, 5], [30, 2]])]))
結果
[[10. 20. 20. 5.]
[20. 30. 30. 2.]]
##stack
ランクRのテンソルのリストをランクR+1のテンソルに積み上げる。
concatとの大きな違いは、そのまま連結するのではなく、軸を1つ追加した上で連結する点であり、連結後も連結前の情報を取り出すことができるという点だろうか。
###ソース
# stack
input1 = Input(shape=(2,))
input2 = Input(shape=(2,))
output = tf.stack(axis=1, values=[input1, input2])
model = Model(inputs=[input1, input2], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[10, 20], [20, 30]]), np.array([[20, 5], [30, 2]])]))
###結果
[[[10. 20.]
[20. 5.]]
[[20. 30.]
[30. 2.]]]
##expand_dims
添字"axis"でのサイズ1の次元を加える。
###ソース
input1 = Input(shape=(1,))
output = tf.expand_dims(input1, axis=1)
model = Model(inputs=[input1], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[10], [20], [30]])]))
結果
[[[10.]]
[[20.]]
[[30.]]]
squeeze
expand_dimsの逆で、テンソルから添字"axis"での1次元を除く。
ソース
input1 = Input(shape=(1,1,))
output = tf.squeeze(input1, axis=1)
model = Model(inputs=[input1], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[[10]], [[20]], [[30]]])]))
###結果
[[10.]
[20.]
[30.]]
reduce_max
テンソルの次元全体で要素の最大値を計算する。
###ソース
以下は 3x2 の行列に対し、0次元、1次元、2次元で演算してみた。0次元だと次元はそのままで結果がブロードキャストされ、1次元だと結果が全く同じであり、2次元で次数が減るというところが興味深い。
input1 = Input(shape=(1,2))
output = tf.reduce_max(input1, axis=0)
model = Model(inputs=[input1], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[[10, 20]], [[20, 5]], [[30, 4]]])]))
input1 = Input(shape=(1,2))
output = tf.reduce_max(input1, axis=1)
model = Model(inputs=[input1], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[[10, 20]], [[20, 5]], [[30, 4]]])]))
input1 = Input(shape=(1,2))
output = tf.reduce_max(input1, axis=2)
model = Model(inputs=[input1], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[[10, 20]], [[20, 5]], [[30, 4]]])]))
結果
[[30. 20.]
[30. 20.]
[30. 20.]]
[[10. 20.]
[20. 5.]
[30. 4.]]
[[20.]
[20.]
[30.]]
reduce_sum
テンソルの次元全体の要素の合計を計算する。ソースも同様に3x2 の行列に対し、0次元、1次元、2次元で演算してみた。reduce_maxと考え方は同じである。
ソース
input1 = Input(shape=(1,2))
output = tf.reduce_sum(input1, axis=0)
model = Model(inputs=[input1], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[[10, 20]], [[20, 5]], [[30, 4]]])]))
input1 = Input(shape=(1,2))
output = tf.reduce_sum(input1, axis=1)
model = Model(inputs=[input1], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[[10, 20]], [[20, 5]], [[30, 4]]])]))
input1 = Input(shape=(1,2))
output = tf.reduce_sum(input1, axis=2)
model = Model(inputs=[input1], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[[10, 20]], [[20, 5]], [[30, 4]]])]))
結果
[[60. 29.]
[60. 29.]
[60. 29.]]
[[10. 20.]
[20. 5.]
[30. 4.]]
[[30.]
[25.]
[34.]]
matmul
"Multiplies matrix"の略で、行列aに行列bを乗算して、a * bを生成する。全結合層等によく使われる。
ソース
以下は1x2と2x1の行列を演算した結果、1x1の行列が生成される例である。
input1 = Input(shape=(2,))
input2 = Input(shape=(2,1))
output = tf.matmul(input1, input2)
model = Model(inputs=[input1, input2], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[10, 20]]), np.array([[[1], [2]]])]))
###結果
[[[50.]]]
slice
テンソルに対し、開始位置および抽出サイズを指定し、テンソルの一部を抽出する。
###ソース
input1 = Input(shape=(4,))
input1_reshape = tf.reshape(input1, [4])
input2 = Input(shape=(1), dtype=tf.int32)
input2_reshape = tf.reshape(input2, [1])
input3 = Input(shape=(1), dtype=tf.int32)
input3_reshape = tf.reshape(input3, [1])
output = tf.slice(input1_reshape, input2_reshape, input3_reshape)
model = Model(inputs=[input1, input2, input3], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[1, 8, 3, 4]]), np.array([[1]]), np.array([[1]])]))
###結果
[8.]
gather
テンソルに対し、インデックスを指定し、インデックスの要素を取得する。
###ソース
[1,8,3,4]というリストに対し、0番目、3番目のものを取り出したリストと、1番目、2番目のものを取り出したリストを要素とした行列を返している。入力のshapeが確定していないとエラーとなるため、無理やりreshapeしている。
input1 = Input(shape=(4,))
input1_reshape = tf.reshape(input1, [4])
input2 = Input(shape=(2,2), dtype=tf.int32)
output = tf.gather(input1_reshape, input2)
model = Model(inputs=[input1, input2], outputs=[output])
print(model.summary())
print(model.predict(x=[np.array([[1, 8, 3, 4]]), np.array([[[0, 3],[1, 2]]])]))
###結果
[[[1. 4.]
[8. 3.]]]
#おわりに
- 今回は一部の演算のみの掲載ではあるが、多少、読解力があがったのではないかと期待する。
- Kerasのモデルの入力に与えて計算させるだけでも、行列の次元数が合わない等のエラーにより、中々すんなりとはいかずかなり苦労したが、行列の理解にはおおいに役立った。
- 特にInputのshape引数の値と、predcitに与える行列の形状を合わせることが重要となる
- 今回のサンプルは、Kerasにおいて複数のInputをとるModelの作成、予測する場合のミニマムコードともなるので、今後複雑なモデルを作成する場合にも役立つと考える。
#参考