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