Kerasで分岐を書いてみる　その3　Case文を書いてみる

# Kerasで分岐を書いてみる　その3　Case文を書いてみる

Kerasでニューラルネットワークに分岐を組み込むことができます。
たとえば前レイヤーの出力が0か1の場合、0の場合はAレイヤー、1の場合はBレイヤーというようにルールベースでレイヤーを選択させることができます。
Kerasで用意されているKeras.backend.switch()を使うのですが、このswitch()でできる分岐は2分岐です。0か1か、以上か以下か、＝か≠かという条件のみが可能です。

![2018-03-11_0.png](https://qiita-image-store.s3.amazonaws.com/0/55384/9da0f2d4-2d8e-30c3-1114-7397975a787c.png)


※　Keras.backend.switchの使い方は以下で投稿しています。
[Kerasで分岐を書いてみる](https://qiita.com/cvusk/items/6955ae3bc802c28c3b66)
[Kerasで分岐を書いてみる　その2](https://qiita.com/cvusk/items/c07f74d2aaa24f37025a)

しかし2分岐だけでは足りないケースもあると思うので、switch()を使って複数の分岐を書いてみました。

## やりたいこと
- Kerasで任意のレイヤーの出力を条件に、次のレイヤーをルールベースで分岐する。
- 分岐先の選択肢は2個以上ある。
- 簡単に書く。
- 以下のような分岐をできるようにする。


![2018-03-11_1.png](https://qiita-image-store.s3.amazonaws.com/0/55384/61182655-16e1-4082-d72a-10ffc268dcd2.png)


## 方針
そもそもKeras.backend.switch()で2分岐できるので、これを組み合わせれば複数の分岐にできます。
2値分類を組み合わせて多値分類を実装するようなものです。


ここでは例として論理回路をニューラルネットワークで書いてみます。
まずはAND回路とOR回路です。
入力に0 or 1の2値と、3値目にANDフラグ、ORフラグをとります。
入力の3値目は0ならAND回路、1ならOR回路です。
例：[0,1,0]の場合、最初2値の0,1は真理値、3値目は0なのでAND回路
　　[1,0,1]の場合、最初2値の1,0は真理値、3値目は1なのでOR回路

```python

import numpy as np

X = np.array([[0,0,0],[0,1,0],[1,0,0],[1,1,0],  #and
              [0,0,1],[0,1,1],[1,0,1],[1,1,1]]) #or
Y = np.array([0,0,0,1,  #and
              0,1,1,1]) #or
Y = keras.utils.to_categorical(Y, 2)

```

これをKeras.backend.switch()を使って分岐させると以下のようになります。

```python

import keras
from keras.models import Model
from keras.layers import Dense, Input, Lambda
from keras.optimizers import Adam
import keras.backend as K
import numpy as np

X = np.array([[0,0,0],[0,1,0],[1,0,0],[1,1,0],  #and
              [0,0,1],[0,1,1],[1,0,1],[1,1,1]]) #or
Y = np.array([0,0,0,1,  #and
              0,1,1,1]) #or
Y = keras.utils.to_categorical(Y, 2)

inputs = Input(shape=X.shape[1:],name="input")

# and layers and or layers
def andGate(inputs):
    andDense = Dense(16, activation="relu", name="and1")(inputs)
    andDense = Dense(32, activation="relu", name="and2")(andDense) 
    return andDense
def orGate(inputs):
    orDense = Dense(32, activation="sigmoid", name="or1")(inputs)     
    return orDense

# switch cases for "and" gate and "or" gate
x_switch = Lambda(lambda x: K.switch(x[:,2]<1,
                                     andGate(x[:,:2]),
                                     orGate(x[:,:2])),
                  output_shape=(32,))(inputs)

# output layer
outputs = Dense(2, activation="softmax", name="softmax")(x_switch)

```

LambdaレイヤーでK.switch()をとります。
K.switch()の入力は(条件、条件がTrueのときの処理、条件がFalseのときの処理)になっています。

```python
#        条件　　　Trueのときの処理　  Falseのときの処理
K.switch(x[:,2]<1, andGate(x[:,:2]), orGate(x[:,:2])),

```

このK.switch()は標準で2分岐しかできませんが、2分岐を重ねていけば2以上の分岐も可能になります。
ここでは「条件がFalseのときの処理」に、さらにK.switch()を加えていくことで、複数分岐を書いていきます。

## より多くの分岐を書く
次はAND回路、OR回路に加えて、XOR回路、NOR回路、NAND回路を入出力に追加してみます。
入力の3値目が条件で、以下のような対応になっています。
0: AND
1: OR
2: XOR
3: NOR
4: NAND

```python

# and, or, xor, nor, nand
X = np.array([[0,0,0],[0,1,0],[1,0,0],[1,1,0], #and
              [0,0,1],[0,1,1],[1,0,1],[1,1,1], #or
              [0,0,2],[0,1,2],[1,0,2],[1,1,2], #xor
              [0,0,3],[0,1,3],[1,0,3],[1,1,3], #nor
              [0,0,4],[0,1,4],[1,0,4],[1,1,4]]) #nand
Y = np.array([0,0,0,1, #and
              0,1,1,1, #or
              0,1,1,0, #xor
              1,0,0,0, #nor
              1,1,1,0]) #nand

# and layers and or layers
def andGate(inputs=inputs):
    andDense = Dense(32, activation="relu", name="and1")(inputs) 
    return andDense
def orGate(inputs=inputs):
    orDense = Dense(32, activation="sigmoid", name="or1")(inputs)     
    return orDense
def xorGate(inputs=inputs):
    xorDense = Dense(32, activation="tanh", name="xor1")(inputs)
    return xorDense
def norGate(inputs=inputs):
    norDense = Dense(32, activation="sigmoid", name="nor1")(inputs)
    norDense = Dense(32, activation="relu", name="nor2")(norDense)
    return norDense
def nandGate(inputs=inputs):
    nandDense = Dense(16, activation="relu", name="nand1")(inputs)
    nandDense = Dense(32, activation="relu", name="nand2")(nandDense)
    return nandDense

# stack of K.switches
x_case = Lambda(lambda x: K.switch(K.equal(x[:,2],0),
                                   andGate(x[:,:2]),
                                   K.switch(K.equal(x[:,2],1),
                                            orGate(x[:,:2]),
                                            K.switch(K.equal(x[:,2],2),
                                                     xorGate(x[:,:2]),
                                                     K.switch(K.equal(x[:,2],3),
                                                              norGate(x[:,:2]),
                                                              nandGate(x[:,:2]))))),
                  output_shape=(32,))(inputs)

```

## もうちょいスマートに
しかしこれだと読みにくいし書きにくいので、もっとスマートに書けるようにcase()という関数にしてみました。

```python

def case(case_and_proc, default):
    """
    case_and_proc: pairs of case and process in list type or dict type
    default: default process. i.e. executed whenever the condition doesn't match any case
    """
    print(case_and_proc)
    if type(case_and_proc) == list:
        for cnum in range(len(case_and_proc)):
            if cnum == 0:
                print(case_and_proc[cnum][1])
                print(default)
                x = K.switch(case_and_proc[cnum][0], case_and_proc[cnum][1], default)
            else:
                print(case_and_proc[cnum][1])
                x = K.switch(case_and_proc[cnum][0], case_and_proc[cnum][1], x)
    elif type(case_and_proc) == dict:
        for i, (cond, expression) in enumerate(case_and_proc.items()):
            if i == 0:
                print(case_and_proc[cond])
                print(default)
                x = K.switch(cond, expression, default)
            else:
                print(case_and_proc[cond])
                x = K.switch(cond, expression, x)
    return x

# input layer
inputs = Input(shape=X.shape[1:],name="input")

x_case = Lambda(lambda x: case({K.equal(x[:,2],0): andGate(x[:,:2]),
                                K.equal(x[:,2],1): orGate(x[:,:2]),
                                K.equal(x[:,2],2): xorGate(x[:,:2]),
                                K.equal(x[:,2],3): norGate(x[:,:2])},
                               default=nandGate(x[:,:2])),
               output_shape=(32,))(inputs)

# output layer
outputs = Dense(2, activation="softmax", name="softmax")(x_case)
# model
model = Model(inputs, outputs)
model.compile(loss='categorical_crossentropy',
              optimizer=Adam(),
              metrics=['accuracy'])

```

case()関数はcase_and_procとdefaultを引数にとります。
case_and_proc: 条件と処理のペアで、ListまたはDict形式でとります。
default: 条件に当てはまらない場合のデフォルト処理です。
上記ではcase_and_procにDict形式で{条件:処理}にAND回路、OR回路、XOR回路、NOR回路を羅列し、最後にデフォルト処理としてNAND回路を書いています。

List形式で条件と処理を書くことも可能です。

```python

x_case = Lambda(lambda x: case([(K.equal(x[:,2],0), andGate(x[:,:2])),
                                (K.equal(x[:,2],1), orGate(x[:,:2])),
                                (K.equal(x[:,2],2), xorGate(x[:,:2])),
                                (K.equal(x[:,2],3), norGate(x[:,:2]))],
                               default=nandGate(x[:,:2])),
               output_shape=(32,))(inputs)

```

List内はSetで(条件と処理)のペアを書きます。


プログラム全文は以下です。
https://github.com/shibuiwilliam/kerasswitch/blob/master/kerascase.ipynb

## 参考
なお、Tensorflowにはすでにtf.case()が用意されていて、複数分岐を書くことができます。
[tf.case](https://www.tensorflow.org/api_docs/python/tf/case)

KerasとTensorflowの2分岐は以下です。
[Keras.backend.switch()](https://keras.io/backend/)
[tf.cond](https://www.tensorflow.org/versions/r1.2/api_docs/python/tf/cond)