kerasで頭に描いたネットワーク構造を実現するためのTips ~ Lambda 編 ~

# 追記2019/10/01
消費税が10％に上がりました。

ではなく、Tensorflow2.0が正式リリースされました。少し前からKerasはTensorflowにインクルードされていますが、そのKerasにおいて

```
Raw TensorFlow functions can now be used in conjunction with the Keras Functional API during model creation. This obviates the need for users to create Lambda layers in most cases when using the Functional API. Like Lambda layers, TensorFlow functions that result in Variable creation or assign ops are not supported.
```

このように記載があります。
要するに、Lambdaレイヤー使って書いてたけど、生のTensorflowの関数をKerasのFunctionalAPIで繋げるよって事だと思います。
どこまで完全な対応かは追々実験する必要がありますが、この記事でドヤ顔して書いてる内容は時代の流れによって不要と化した可能性があります。

(※あれ、でもそうなるとcoremltools等のコンバータの実装がより一層追い付かなくなるじゃん… https://qiita.com/Mco7777/items/c5df09e8b68988cdf916 )

以下の記事はご認識の上御覧ください。

# 本編
functionalAPIについて書いた[前回](https://qiita.com/Mco7777/items/1339d01bc6ef028e7b44)の続きです。前回で説明したfunctionalAPIの範囲だけでもこんな落書きみたいなネットワークも割りとすんなり書けたりします。まぁこんなネットワークを作ったところで何の役に立つのかは知りませんが。

![IMG_4713.JPG](https://qiita-image-store.s3.amazonaws.com/0/219344/07d9e36a-89e7-bbff-1e81-02d66974ba8a.jpeg)

これが

```network.py
from keras.models import Model
from keras.layers import Input, Dense, Activation, Multiply

my_dense = Dense(5)

model_input = Input(shape=(5,))
mid1 = my_dense(model_input)
mid2 = Dense(5)(mid1)
mid3 = Multiply()([mid1, mid2])
loop = my_dense(mid3)
output1 = Activation('relu')(loop)
output2 = Activation('relu')(mid2)
model = Model(inputs=model_input, outputs=[output1, output2])
```

![スクリーンショット 2017-12-02 16.25.23.png](https://qiita-image-store.s3.amazonaws.com/0/219344/8ae58910-a76a-794d-8312-94331c1484bb.png)

こんな感じで。

今回はさらにもう少しレイヤーの中身に迫って弄りたい時にどうしたら良いかを書いていきたいと思います。掲題の通りLambdaと呼ばれるクラスを使うのですが、このLambda、汎用性が凄いんです。

# Lambdaの前に

例えばサイズ5のレイヤー2つがあってそれを合体させて10のレイヤーにしたい場合、どうしたらよいでしょうか。numpyでイメージするならこんな感じ。

```concatenate.py
x1 = np.random.rand(3, 5)
x2 = np.random.rand(3, 5)
x = np.concatenate((x1, x2), axis=1)
x.shape
# (3, 10)
```

ここで1次元目の3はバッチサイズです。サイズ5のデータが3つあるのが x1,x2 でそれを合体させたのが x ですね。前回の記事でMultiplyのように、合算して1つのTensorにする方法は見ましたが、今回はそうではなく横につなげる感じです。

ではkerasのネットワークではどのように実現するかというと、同じような感じで可能です。

```concatenate2.py
from keras.models import Model
from keras.layers import Concatenate, Input

x1 = Input(shape=(5,))
x2 = Input(shape=(5,))
x = Concatenate()([x1, x2])
model = Model(inputs=[x1,x2], outputs=x)
```

![スクリーンショット 2017-12-02 17.14.04.png](https://qiita-image-store.s3.amazonaws.com/0/219344/8972400a-be8b-2e6f-0bde-f0014e5a771b.png)

numpyと大した違いはなく結構直感的にできました。 `Concatenate` はバッチ次元はNoneだと考えれば基本的にMultiplyなどとは違いサイズが異なっても繋がります。それでは逆にサイズ10のTensorをサイズ5のTensor2つに分離したいときはどうしたらよいでしょうか？

# Lambda
##Lambdaの使い方その1

普段numpyを使っている方は配列を分離するなんて何でもない事だと思います。例示するまでも無いと思いますが

```split.py
x = np.random.rand(3, 10)
x1 = x[:, :5]
x2 = x[:, 5:]
```

こんな感じですね。ですが、kerasのTensorに対して `model_input[:, :5]` こんなことができるのかと言うと流石にそんな便利機能はありません。

しかし `Lambda` という魔法の箱（と表現したくもなります）を使うとこのように書けてしまうのです。

```split2.py
from keras.layers.core import Lambda

model_input = Input(shape=(10,))
x1 = Lambda(lambda x: x[:, :5], output_shape=(5,))(model_input)
x2 = Lambda(lambda x: x[:, 5:], output_shape=(5,))(model_input)
model = Model(inputs=model_input, outputs=[x1,x2])
```

![スクリーンショット 2017-12-02 17.26.33.png](https://qiita-image-store.s3.amazonaws.com/0/219344/4117b226-7102-e6bf-3d70-5c84206374cd.png)

`Lambda` の中は少しややこしいかもしれませんが、xという引数にデータが渡されてきて、そのデータはnumpyのような配列操作ができてしまうのです。
注意点としてはnumpyからの流れで書いてるので不自然に見えないかもしれませんが、
 
- lambdaの中ではバッチ次元（`x[:, :5]`の最初の`:,`です）を含めないといけない点
- output_shapeを指定しないといけない点（実は入力と同じサイズなら不要らしいですが…）

の2点です。この操作ができてしまえば後は半分だろうが1つずつだろうが好きなように分割できますね。

## Lambdaの使い方その2

Lambdaを使うことによってレイヤー内部の形を自由自在に扱えるようになりました。
ではLambdaの果たす役割はそれだけなのでしょうか。実は（私的に）もう1つ大きな役割があります。それはbackendの関数を利用する時です。

実はkerasではbackendという、バックエンドのライブラリ（ここではTensorflow）が持つレイヤー操作を利用できるAPIがあります。
例えばあるレイヤーのすべてのノードに定数を足して平方根を取りたい、けどネットワークの途中でそれをやりたい、なんて事はよくあります。そう思ってググって辿り着くのはだいたい[ここ](https://keras.io/ja/backend/)でしょう。

しかしこれをみてサクッとモデルを作るとこうしたくなります。

```backend.py
from keras import backend as K

model_input = Input(shape=(10,))
calculated = K.sqrt(model_input + 1.0)
model = Model(inputs=model_input, outputs=calculated)
```

しかしこれではエラーになります。 `K.sqrt` のようなバックエンドが返す値はkerasのレイヤーではないんです。<font color="DarkGray">なんかドキュメントではこの辺明確に説明されてないような気がするんだよなぁ…</font>

ではどうするかというと、割りと知ってしまえば単純です。

```backend2.py
from keras import backend as K

model_input = Input(shape=(10,))
calculated = Lambda(lambda x: K.sqrt(x + 1.0), output_shape=(10,))(model_input)
model = Model(inputs=model_input, outputs=calculated)
```

`Lambda` でラッピングしてあげれば何でもできるような気がしてきます。

# Lambdaの便利さに取り憑かれたが故の悲劇
今回はLambdaの便利さについて書きましたが、実はある特定の目的でモデルを設計していると（現時点では）悲しみを背負うことになります。そうです、私は背負いました。

そう、coreMLでの利用です。

次回はcoreMLに関する辛みを書き連ねたいと思います。それを見て誰かのお役に立てればこれ幸いです。

先に1つだけ書くと、Lambdaを使ったkerasのモデルはcoreMLのモデルに変換できません。では次回。