Kerasで多数のモデルを繋げる

WARNING: これはNeural Networkの入門ではありません。深層学習をある程度知っているという前提です。And i'm sure my Japanese make less sense sometime, bear me
# 背景と目標
Deep Learningをある現実問題に適用しょうとして、データの複雑さ、関連性などでの原因ですごく複雑なNNネットを設計してしまったとき、Kerasにまだ慣れっていないひとはそれをKerasで実装するに難しいと思います。
よくある「作りたい」構造の一つとして、図１のようなNNを作ります。
<img src="https://qiita-image-store.s3.amazonaws.com/0/148692/9ba0e353-a239-d537-2d17-13313005ec7f.png" width="300px"/>
*図１：Mult-blocks Neural Network for multiple inputs and single ouput*
それはInception Layerではないかと思う人もおると思います、確かに図１のNN1、NN2などブロックが1層ぐらいだと同じです。
<img src="https://qiita-image-store.s3.amazonaws.com/0/148692/d46f57e6-c4a5-ab58-88ef-17295a86093f.png" width="400px"/>
*図2：Inception Layer*

Q1：**何でこんな構造作るんですか：**
A1：**現実では問題に対して集められたデータがすべて同じく対応するには行かない、データのドメインが違ったり、物理意味が違ったりするによって、すべてを同じ扱い（only one NN）は効率が悪い、だが各データに対してNNを独立するには行かない（システムとして各NNの誤差が別物になってしまう）、そいうときこそ、図１のよな構造が必要**


# NNブロックを作成

説明しやすくするために、cifar10を目標データとして作ります。

##　準備

```python
import tensorflow as tf
from keras.datasets import cifar10

from keras.models import Model, Sequential

from keras.layers import Input, Dense, Dropout, Activation, Flatten
from keras.layers import Conv2D, MaxPooling2D, BatchNormalization

from keras.utils import np_utils
from keras import backend as K

from keras.layers import add, concatenate

ksess = K.get_session()#Graphを読み出すために
```
以上でLibの参照は終わる


##　NNブロックを作る
目標は結果がいいモデルということではないので、適当なCNNモデルを作ります。
### Block #1
```python
with tf.name_scope("NN1_Block"):
    NN1= Sequential()
    NN1.add(Conv2D(32, (3, 3), padding='same', input_shape=(32,32,3)))
    NN1.add(Activation('relu'))
#     NN1.add(BatchNormalization())#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN1.add(Conv2D(filters=32, kernel_size=(3,3), padding='same'))
    NN1.add(Activation('relu'))
#     NN1.add(BatchNormalization())#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
#     NN1.add(Dropout(0.2))#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN1.add(Conv2D(filters=32, kernel_size=(3,3), padding='same'))
    NN1.add(Activation('relu'))
#     NN1.add(BatchNormalization())#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN1.add(Conv2D(filters=32, kernel_size=(3,3), padding='same'))

```

### Block #2
```python
with tf.name_scope("NN2_Block"):
    NN2= Sequential()
    NN2.add(Conv2D(32, (3, 3), padding='same', input_shape=(32,32,3)))
    NN2.add(Activation('relu'))
#     NN2.add(BatchNormalization())#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN2.add(Conv2D(filters=32, kernel_size=(3,3), padding='same'))
    NN2.add(Activation('relu'))
#     NN2.add(BatchNormalization())#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
#     NN2.add(Dropout(0.2))#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN2.add(Conv2D(filters=32, kernel_size=(3,3), padding='same'))
    NN2.add(Activation('relu'))
#     NN2.add(BatchNormalization())#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN2.add(Conv2D(filters=32, kernel_size=(3,3), padding='same'))

```

### Block #3
```python
with tf.name_scope("NN3_Block"):
    NN3= Sequential()
    NN3.add(Conv2D(32, (3, 3), padding='same', input_shape=(32,32,3)))
    NN3.add(Activation('relu'))
#     NN3.add(BatchNormalization())#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN3.add(Conv2D(filters=32, kernel_size=(3,3), padding='same'))
    NN3.add(Activation('relu'))
#     NN3.add(BatchNormalization())#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
#     NN3.add(Dropout(0.2))#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN3.add(Conv2D(filters=32, kernel_size=(3,3), padding='same'))
    NN3.add(Activation('relu'))
#     NN3.add(BatchNormalization())#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN3.add(Conv2D(filters=32, kernel_size=(3,3), padding='same'))
```
<img src="https://qiita-image-store.s3.amazonaws.com/0/148692/aba199ef-68fb-3a2b-d588-c7b7b2474c30.jpeg" width="100px"/>
*図3：NN1からNN3まではこんな形になります*


次はNN4を作り同時にNN1,NN2とNN3を繋げって行きます
### NN1とNN2の結果を足算をして、一つにします(addblock )

```python
added = add([NN1.output, NN2.output])
addblock = Model([NN1.input, NN2.input], added)
```
<img src="https://qiita-image-store.s3.amazonaws.com/0/148692/ec049e2f-2d32-125f-88b4-c843aaf6ff6f.jpeg" width="400px"/>
*図4：Model after two blocks added*
 
### addblockとNN3をつなげます(merged)

```python

merged = concatenate([addblock.output, NN3.output])
```

### mergedとNN4をつなげます
NN4もSequential APIで作ろうとしましたが、非常に難しくて、Functional APIを使いました、Sequential APIで繋がる方法が知っている人がいるなら教えてください。

```python
with tf.name_scope("NN4_Block"):
    NN4_conv = Conv2D(32, (3, 3), padding='same')(merged)
    NN4_conv = Activation('relu')(NN4_conv)
#     NN4_conv = BatchNormalization()(NN4_conv)#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN4_conv = Conv2D(32, (3, 3), padding='same')(NN4_conv)
    NN4_conv = Activation('relu')(NN4_conv)
#     NN4_conv = BatchNormalization()(NN4_conv)#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN4_conv = Conv2D(32, (3, 3), padding='same')(NN4_conv)
    NN4_conv = Activation('relu')(NN4_conv)
#     NN4_conv = BatchNormalization()(NN4_conv)#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN4_conv = Conv2D(32, (3, 3), padding='same')(NN4_conv)
    NN4_conv = Flatten()(NN4_conv)
    NN4_conv = Dense(256)(NN4_conv)
#     NN4_conv = BatchNormalization()(NN4_conv)#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN4_conv = Dense(128)(NN4_conv)
#     NN4_conv = BatchNormalization()(NN4_conv)#グラフの繋がりが分からなくなりますので、ここで取っていきます、実際では使います
    NN4_conv = Dense(32)(NN4_conv)
    NN4_conv = Dense(10, activation="softmax")(NN4_conv)
```

### 最終Model生成

```python
finalblock = Model([NN1.input, NN2.input, NN3.input], NN4_conv)
finalblock.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

```

### 最後のgraph結果
<img src="https://qiita-image-store.s3.amazonaws.com/0/148692/8c4db478-6838-41bc-7004-183ee2854cbf.jpeg" width="600px"/>
*図5：Final Model Graph*

## 学習
ここまでできて、実際に学習して見ないと、残念が残りそうです。早速学習してみましょう。

```python
(X_train, y_train), (X_test, y_test) = cifar10.load_data()

X_train = X_train.astype('float32')
X_test = X_test.astype('float32')
X_train /= 255
X_test /= 255

Y_train = np_utils.to_categorical(y_train, 10)
Y_test = np_utils.to_categorical(y_test, 10)

n_epochs = 20
batch_size = 64

H = finalblock.fit([X_train, X_train, X_train], Y_train, validation_data=([X_test, X_test, X_test], Y_test), 
              epochs=n_epochs, batch_size=batch_size, callbacks=None)

```

```bash
Train on 50000 samples, validate on 10000 samples
Epoch 1/20
50000/50000 [==============================] - 37s 743us/step - loss: 1.2756 - acc: 0.5486 - val_loss: 1.3998 - val_acc: 0.5553
.
.
.
Epoch 8/20
50000/50000 [==============================] - 32s 630us/step - loss: 0.3911 - acc: 0.8598 - val_loss: 0.9137 - val_acc: 0.7142
Epoch 9/20
50000/50000 [==============================] - 31s 625us/step - loss: 0.3219 - acc: 0.8838 - val_loss: 1.0909 - val_acc: 0.6962
Epoch 10/20
50000/50000 [==============================] - 31s 630us/step - loss: 0.2626 - acc: 0.9047 - val_loss: 1.0493 - val_acc: 0.7135
.
.
.
Epoch 20/20
50000/50000 [==============================] - 31s 628us/step - loss: 0.0976 - acc: 0.9653 - val_loss: 1.6173 - val_acc: 0.6955
```
やはりこのデータセットは簡単すぎて、すぐover-fittingになりますね。

##　まとめ
これは単純なKerasの使い方のメモみたいなものなんで、まとめがない。