Keras(Tensorflow)で用いられる様々な行列演算のイメージを実例で理解する

#はじめに
Kerasのコードを読むと様々な行列演算に遭遇する。これらの演算の中身を知らないと読み進めることが非常に難しい。今回、私が読んだコードを中心に、Kerasによく出てくる行列演算を実例を元に確認したため共有する。

#環境
Kerasといっても、今回確認した環境は、tensorflowの一部であった時代の古いバージョンである。ただ、行列演算は今とそれほど変わらないと思う。

- tensoflow 1.14.0

###サンプルを動作させる場合の注意点
本記事のソースコードは以下のインポートを前提としています。動作させる場合はコピペしておいてください。

```python
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での処理をイメージできることを想定し、以下の通りとした。

1. 演算を行うKerasのモデルを作成

2. モデルのpredictメソッドに入力を与え、その結果を出力させることで、計算の内容を確認


#やってみよう


##concat 
指定した軸に沿ってテンソルのリストを連結する。

### ソース
```python
    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つ追加した上で連結する点であり、連結後も連結前の情報を取り出すことができるという点だろうか。

###ソース
```python
    # 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の次元を加える。

###ソース
```python
    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次元を除く。

### ソース
```python
    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次元で次数が減るというところが興味深い。

```python
    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と考え方は同じである。

### ソース　
```python
    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の行列が生成される例である。

```python
    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
テンソルに対し、開始位置および抽出サイズを指定し、テンソルの一部を抽出する。

###ソース
```python
    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している。

```python
    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の作成、予測する場合のミニマムコードともなるので、今後複雑なモデルを作成する場合にも役立つと考える。

#参考
- https://keras.io/ja/backend/
- https://www.tensorflow.org/api_docs/python/tf/math/reduce_max