Tensorflow, Kerasの四則演算とか結合とか

#はじめに
機械学習で使われているKerasでは、全結合や畳み込みなどのLayerを重ねていってモデルを作りますが、途中でデータ同士を足したりすることがあります
ResNetやU-Netで何回も出てくる結合層です
お手本のソースコードからコピペしたものをオリジナルデータへ適合させるべくいじっていると、そんなデータを結合したり加算したりする場所でよくエラーを出してしまいます

```python
Incompatible shapes: [1,3,3,16] vs. [1,1,16]
```
シェープが違いますよ！って

チュートリアルをきちんと読んで理解するのが正道ですが、とりあえず”やってみる”系なので、簡単なコードを書きながら動作をまとめました

[TensorFlow Core v2.4.0 > Module: tf](https://www.tensorflow.org/api_docs/python/tf)

動作確認は tensorflow 2.4.1 にて行っています

##Tensorを作成

```python
t = tf.constant([1,2,3]) # shape=(3,) numpy=array([1, 2, 3]
t = tf.ones([7,7,3])     # shape=(7,7,3) すべてのデータが1で埋まったTensor
t = tf.zeros([7,7,3])    # shape=(7,7,3) すべてのデータが0で埋まったTensor
t = tf.fill([7,7,3], 5)  # shape=(7,7,3) すべてのデータが5で埋まったTensor
```

配列から作成したり、指定したシェープを1や0、もしくは指定した数で埋めて作成したりするメソッドです
モデルの定義内で使われることはないでしょうが、モデルでの処理は、入力されたデータがTensorに変換されるところから始まっています

```python
def NewModel():
  inputs = tf.keras.layers.Input(shape=[7,7,3]) # shape=(None,7,7,3)
```
一度に何個のデータが渡されるかわからないので、第一次元はNone（可変長のデータ）になっています

##サンプルモデル
```python
def NewModel():
  inputs = tf.keras.layers.Input(shape=[7,7,3]) # shape=(None,7,7,3)

  x = inputs

  # ここに結合層を追加していきます

  outputs = x

  return tf.keras.Model(inputs=inputs, outputs=outputs)
```

7x7のサイズでRGBのチャンネルを持つ画像に対して何かをするというモデル構築を想定します

以下の方法でモデルの動作確認

```python
model = NewModel()
model.summary()

model.predict(tf.ones([1,7,7,3])
```

##足したり引いたり

```python
# 加算は以下のいずれかの方法で可能です
  x = x + 1        #tf.__operators__.add (TFOpLambda)
  x = tf.add(x, 1) #tf.math.add (TFOpLambda)
  x = tf.keras.layers.Lambda(lambda v: v+1, name='lambda_add')(x) #lambda_add (Lambda)        
  x = tf.keras.layers.Add(name='layer_add')([x, tf.constant([1.])]) #layer_add (Add)    
```

レイヤーの出力と数値を直接計算はできないので、Lambda層でラップする必要があります
最初の行のように数式を直に書くと、自動でラップされるようです
最後のlayers.Addで加算する場合はテンソル形式で渡す必要があります

式を直接書く方が短くて楽ですが、レイヤーの名前が自動になってしまいます
tf.keras.layersを使うことでレイヤー名を明示的につけることができます

```python
# 減算
  x = tf.keras.layers.Subtract()([x, tf.constant([1.])]) # x = x - 1

# 乗算
  x = tf.keras.layers.Multiply()([x, tf.constant([2.])]) # x = x * 2

# 除算
  x = tf.keras.layers.Multiply()([x, tf.constant([0.5])]) # x = x / 2
  # LayerにDivideがない！！

```

上記では全てに同じ数値を足したり引いたりしていますが、画像データなどでチャンネルごとに加減したい場合は以下のようにチャンネルの次元の要素数に合わせることで可能です

```python
  x = tf.keras.layers.Add()([x, tf.constant([0.1,0.5,1.])]) #Rに0.1、Gに0.5、Bに1が、それぞれ加算される   
```

また、以下のようにすれば、列ごとや行ごとに異なる数値を当てることができます

```python
  y = tf.reshape(tf.constant([0.1,0.1,0.1,0.5,1,1,1]), [-1,7,1,1])]) # shape=(None,7,1,1)
  x = tf.keras.layers.Add()([x, y]) # shape=(None,7,7,3)
```

##くっつけたり削ったり

```python
def NewModel():
  inputs = tf.keras.layers.Input(shape=[7,7,3]) # shape=(None,7,7,3)

  x = tf.keras.layers.Dense(1, kernel_initializer=tf.keras.initializers.ones, use_bias=False)(inputs)

  y = tf.keras.layers.Dense(2, kernel_initializer=tf.keras.initializers.zeros, use_bias=False)(inputs)

  # ここに結合層を追加していきます

  outputs = x

  return tf.keras.Model(inputs=inputs, outputs=outputs)
```

演算結果が分かりやすいようにバイアスを使用せず、重みの初期値を1に固定したxと0に固定したyを使用します


```python
# 結合
  x = tf.keras.layers.Concatenate()([x, y]) # shape=(None,7,7,3) 
```

結合する次元以外は要素数が揃っていないとエラーになります
結合次元の初期値が-1(最後の次元)になっているので、画像データの場合はチャンネルでの結合になります

結合方向を指定することで、画像データの縦方向や横方向への結合も可能です

```python
# 結合
  x = tf.keras.layers.Concatenate(axis=1)([x, x]) # shape=(None,14,7,1) 
```

次元を削減する方法も様々

```python
# 平坦化と全結合層
  x = tf.keras.layers.Flatten()(x) # shape=(None,49)
  x = tf.keras.layers.Dense(1)(x) # shape=(None,1) 
  x = tf.keras.layers.Add()([x, y])
```

```python
# プーリング層でがっつり
  x = tf.keras.layers.GlobalAveragePooling2D()(x) # shape=(None,1) 
  x = tf.keras.layers.Add()([x, y])
```

#さいごに - 結合したいだけのモデル

```python
def NewModel():
  inputs_x = tf.keras.layers.Input(shape=[7,7,3]) # shape=(None,7,7,3)
  inputs_y = tf.keras.layers.Input(shape=[7,7,3]) # shape=(None,7,7,3)

  x_y = tf.keras.layers.Concatenate()([inputs_x, inputs_y]) # shape=(None,7,7,6)

  x = tf.keras.layers.GlobalAveragePooling2D()(x_y) # shape=(None,6)
  x = tf.keras.layers.Dense(1)(x)                   # shape=(None,1)

  y = tf.keras.layers.Conv2D(128, 3, padding='same')(x_y) # shape=(None,7,7,128)
  y = tf.keras.layers.Conv2D(32, 1, padding='same')(y)    # shape=(None,7,7,32)

  y = tf.keras.layers.Add()([x, y])                   # shape=(None,7,7,32)
  y = tf.keras.layers.Conv2D(3, 1, padding='same')(y) # shape=(None,7,7,3)

  outputs = tf.keras.layers.Activation('sigmoid')(y)  # shape=(None,7,7,3)

  return tf.keras.Model(inputs=[inputs_x, inputs_y], outputs=outputs)
```

二つの画像を入力値に、畳み込んだり全結合したりして何らかの画像を出力するモデル
特に意味はない、です

#参考
[TensorFlowのTensorオブジェクトに慣れたい](https://qiita.com/everylittle/items/d90ac57cce4aabb26f16)
[Tensorflowにおける各種演算まとめ](https://qiita.com/hiroyuki827/items/72152cdbcb073f0d4bb7)
[TensorFlow メソッド、shape をいじる系メモ](https://qiita.com/cfiken/items/04925d4da39e1a24114e)