ドット×Deep Learningで流れを意識したモデリング

Reactive ExtensionやRubyとか大好きです。`.`繋ぎしたいです。
Deep Learningのモデルって、結局フィルタだから1つの流れなのでは？
そんな思いから本記事のコードが試験的に生まれました。

## `.` × Something
RxSwiftなら、

```swift
somethingId
    .asObservable()
    .filter{ $0 != 0 }
    .subscribe(onNext: { id in
        // do something
    })
    .disposed(by: disposeBag)
```

Rubyなら、

```ruby
'some thing'.chars.select{ |c| c =~ /\w/ }.join
```

直前の処理の結果に対して、次にどんな処理がなされるか、視覚的にわかりやすく、流れも意識しやすい書き方です。
どちらかというと、Rubyの方が流れを意識する気がしなくもない。

## 普通にDeep Learningのモデリング
Kerasのsequensial APIとfunctional APIを使った例を踏まえて書きたいと思います。
まず、Deep Learningの主要フレームワークを利用したモデリングには次の特徴があります。

- レイヤごとに変数名が必要
- レイヤを定義するとき、参照する、直前のレイヤの名前が必要

さて、KerasはTensorFlowのラッパーで、簡潔にモデリングすることが出来ますが、functional APIで起こりうる問題にはどんなものがあるでしょうか。
今回は出力が分岐するモデルを扱いませんが、実際には1つのシーケンスで成り立つモデルではなく、より複雑なモデルを扱う場面が多くあります。
そういったときには、

- シーケンス内のあるレイヤに対して、一々命名するのはとても面倒
- 参照するレイヤの名前を間違える

など、プログラミングでいうと、かなり初歩的なミスをしがちになります(自分はこれで何時間か無駄にします)。
基本的に全部流れなんだったら、ほとんどの途中のレイヤは、保持している変数がモデルのスクリプト内に露出される必要はないと思われます。

つまり、

```python
input = Input(shape=(784,))

lay1 = Dense(64, activation='relu')(input)
lay2 = Dense(64, activation='relu')(lay1)
lay3 = Dense(32, activation='relu')(lay2)
```
を

```python
model                             \
    .input(shape=(784,))          \
    .dense(64, activation='relu') \
    .dense(64, activation='relu') \
    .dense(64, activation='relu')
```

に置き換えれば、参照しない途中の変数は見えなくなり、重要な、モデルの構造にフォーカスできます。

## `.` × Deep Learning
それでは、`.`でレイヤを繋げるように、Kerasが提供しているAPIをラップしていきます。

```python
class KerasMonad:
    def __init__(self, base=None):
        self.stream = base
        self.root = base

    def build(self):
        return Model(inputs=self.root, outputs=self.stream)

    def input(self,
              shape=(32, 32, 3)):

        self.stream = Input(shape=shape)
        self.root = self.root or self.stream
        return self

    def conv2d(self,
               filters=32,
               kernel_size=(32, 32),
               strides=(1, 1),
               padding='same',
               activation='relu',
               use_bias=True,
               kernel_initializer='random_normal',
               bias_initializer='zeros'):

        self.stream = Conv2D(
            filters=filters,
            kernel_size=kernel_size,
            strides=strides,
            padding=padding,
            activation=activation,
            use_bias=use_bias,
            kernel_initializer=kernel_initializer,
            bias_initializer=bias_initializer
          ) (self.stream)
        return self

    def max_pooling2d(self,
                pool_size=(2, 2),
                strides=(2, 2),
                padding='same'):

        self.stream = MaxPool2D(
            pool_size=pool_size,
            strides=strides,
            padding=padding
          ) (self.stream)
        return self

    def global_avg_pooling2d(self,
                   data_format=None):

        self.stream = GlobalAveragePooling2D(
            data_format=data_format
        ) (self.stream)
        return self

    def flatten(self):
        self.stream = Flatten() (self.stream)
        return self

    def normalize(self,
                  axis=-1,
                  momentum=0.99,
                  epsilon=0.001,
                  center=True,
                  scale=True,
                  beta_initializer='zeros',
                  gamma_initializer='ones',
                  moving_mean_initializer='zeros',
                  moving_variance_initializer='ones',
                  beta_regularizer=None,
                  gamma_regularizer=None,
                  beta_constraint=None,
                  gamma_constraint=None):

        self.stream = BatchNormalization(
            axis=-axis,
            momentum=momentum,
            epsilon=epsilon,
            center=center,
            scale=scale,
            beta_initializer=beta_initializer,
            gamma_initializer=gamma_initializer,
            moving_mean_initializer=moving_mean_initializer,
            moving_variance_initializer=moving_variance_initializer,
            beta_regularizer=beta_regularizer,
            gamma_regularizer=gamma_regularizer,
            beta_constraint=beta_constraint,
            gamma_constraint=gamma_constraint
        ) (self.stream)
        return self

    def dense(self,
              units,
              activation='relu',
              use_bias=True,
              kernel_initializer='glorot_uniform',
              bias_initializer='zeros',
              kernel_regularizer=None,
              bias_regularizer=None,
              activity_regularizer=None,
              kernel_constraint=None,
              bias_constraint=None):

        self.stream = Dense(
            units=units,
            activation=activation,
            use_bias=use_bias,
            kernel_initializer=kernel_initializer,
            bias_initializer=bias_initializer,
            kernel_regularizer=kernel_regularizer,
            bias_regularizer=bias_regularizer,
            activity_regularizer=activity_regularizer,
            kernel_constraint=kernel_constraint,
            bias_constraint=bias_constraint
        ) (self.stream)
        return self

    def dropout(self,
                rate=0.5,
                noise_shape=None,
                seed=None):

        self.stream = Dropout(
            rate=rate,
            noise_shape=noise_shape,
            seed=seed
        ) (self.stream)
        return self
```
これで、`KerasMonad`をimportすれば、`.`でレイヤを繋いでいくことができます。
では、超有名なAlexNetをKerasMonadを用いてモデリングしてみます。

```python
class Model:
    def __init__(self):
        pass

    def build_alexnet(self):
        model = KerasMonad()
        alexnet = model                                 \
                  .input(conf.dims)                     \
                  .conv2d(48, 11,   strides=(2, 3))     \
                  .max_pooling2d(3, strides=(1, 2))     \
                  .normalize()                          \
                  .conv2d(128, 5,   strides=(2, 3))     \
                  .max_pooling2d(3, strides=(2, 1))     \
                  .normalize()                          \
                  .conv2d(192, 3,   strides=(1, 2))     \
                  .conv2d(192, 3,   strides=(1, 1))     \
                  .conv2d(128, 3,   strides=(1, 1))     \
                  .max_pooling2d(3, strides=(1, 2))     \
                  .normalize()                          \
                  .flatten()                            \
                  .dense(256)                           \
                  .dropout(0.5)                         \
                  .dense(256)                           \
                  .dropout(0.5)                         \
                  .dense(conf.num_classes, activation='softmax')

        return alexnet.build()
```

シンプルに、モデルの構造にのみ注目してモデリングができるようになりました(全然Pythonっぽくないけど)。

## モデルの構造をチェックしたいの？
モデルの構造をチェックしたいというよりは、変数を保持することで、各レイヤが参照する変数を間違えるなど、単純なミスを防ぐことを目的としています。
また、スクリプトを見ているとき、前後のレイヤで**本当に必要な情報のみが記述されている状況**を作ることで、視覚的なノイズを除去することも目的の1つです。

## モデルの構造チェックは？
個人的には、モデリングのフェーズとは分離して、学習前にTensorBoardとか、Graphvizとか、色々ある視覚化ツールを使うのが良いと思っています。
Kerasだったら`summary`関数使えばめっちゃ楽に可視化できますね(文字列ですが)。
あとはNeural Network ConsoleとかGUIツールでモデリングするとかでしょうか。