1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PearAdvent Calendar 2018

Day 10

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

Last updated at Posted at 2018-12-09

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

. × Something

RxSwiftなら、

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

Rubyなら、

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

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

普通にDeep Learningのモデリング

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

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

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

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

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

つまり、

input = Input(shape=(784,))

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

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

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

. × Deep Learning

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

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を用いてモデリングしてみます。

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ツールでモデリングするとかでしょうか。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?