見出し画像

モバイルアプリエンジニアのためのTensorFlow 2.x 入門 (4) - さまざまなモデルの書き方

モバイルアプリエンジニアの方がTensorFlowに入門するための連載記事です。

今回はさまざまなモデルの書き方を説明します。

TensorFlowは歴史的な経緯から、さまざまなモデルの書き方ができます。これも混乱ポイントなので1つ1つ紹介したいと思います。

書き方は主に次のような分類があります。

・Sequential API
・Functional API
・Subclassing API

それぞれの書き方と特徴を説明したいと思います。

この入門は、Google Colaboratoryを使います。使い方は第1回にありますので、そちらをご覧下さい。

過去の記事はこちらからご覧下さい。

Sequential APIでの書き方

Sequential APIは、レイヤーをaddしながら一直線上にモデルを構築することができます。前回の記事がSequential APIを使った書き方です。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential()
model.add(layers.Dense(512,input_shape=(1,),activation='relu'))
model.add(layers.Dense(256,activation='relu'))
model.add(layers.Dense(2,activation='softmax'))

また、上のモデルは次のように書くこともできます。
keras.Sequentialのコンストラクタにまとめてレイヤーの情報を渡しています。これもSequential APIを利用した書き方の一種です。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
   layers.Dense(512,input_shape=(1,),activation='relu'),
   layers.Dense(256,activation='relu'),
   layers.Dense(2,activation='softmax')
])

これまでActivationは引数で指定していましたが、個別に追加していくこともできます。上のモデルと意味は同じです。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential()
model.add(layers.Dense(512,input_shape=(1,)))
model.add(layers.Activation('relu'))
model.add(layers.Dense(256))
model.add(layers.Activation('relu'))
model.add(layers.Dense(2))
model.add(layers.Activation('softmax'))

Functional APIでの書き方

Functional APIはレイヤーを関数呼び出しのようにつなげながらモデルを構築します。レイヤーが分岐したり統合したりと複雑なモデルを作ることができます。

これまで作ってきたモデルは次のように書けます。前のレイヤーの戻り値を関数呼び出しのようにしてつないでいきます。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers

input = tf.keras.Input(shape=(1,))
x = layers.Dense(512,activation='relu')(input)
x = layers.Dense(256,activation='relu')(x)
output = layers.Dense(2,activation='softmax')(x)

model = tf.keras.Model(inputs=input, outputs=output)

Functional APIは分岐したり統合したりできるのが特徴です。
途中まで同じレイヤーを共有し、最後に異なる2つの推論値を出すモデルを作ってみましょう。

教師データには、これまでと同じ奇数[1,0]、偶数[0,1]のものと、値を反転させた奇数[0,1]、偶数[1,0]のものを2つ与えます。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np

x_train = np.random.randint(0, 5, (20, 1))
# これは今までと同じ教師データ
y1_train = np.where( x_train%2 == 0, [0,1], [1,0])
# y1_trainとは全く逆の値である教師データ
y2_train = np.where( x_train%2 == 0, [1,0], [0,1])

input = tf.keras.Input(shape=(1,))
x = layers.Dense(512,activation='relu')(input)
x = layers.Dense(256,activation='relu')(x)
# y1_trainに対応した推論値の算出レイヤー
output1 = layers.Dense(2,activation='softmax')(x)
# y2_trainに対応した推論値の算出レイヤー
output2 = layers.Dense(2,activation='softmax')(x)

# 出力レイヤーは2つなので配列で渡す
model = tf.keras.Model(inputs=input, outputs=[output1, output2])

# 損失関数は別々に指定しないと同じ損失値で学習してしまう
model.compile('adam', loss=['categorical_crossentropy','categorical_crossentropy'], metrics=['accuracy'])

# 教師データも配列で渡す
model.fit(x_train, [y1_train, y2_train], epochs=400, batch_size=32, validation_split=0.2)

Dense(256)までは同じで、出力層がoutput1とoutput2で分かれているのが分かると思います。

モデルを図にしたものを出力してみましょう。

keras.utils.plot_model(model,  show_shapes=True)

(出力結果)

画像1

出力層のところで分岐していますね。

推論させてみます。

np.round(model.predict([1])) # np.roundにより推論値を四捨五入する
# 出力結果
# array([[[1., 0.]],
#       [[0., 1.]]], dtype=float32)

2つの値が真逆になっていることが分かると思います。

Subclassing API での書き方

Keras Subclassing APIはtf.keras.Modelのサブクラスを定義することでモデルを定義する方法で、一番柔軟にモデルを構築することができます。

これまで作ってきたモデルは次のように書けます。keras.Modelを継承したクラスを作成し、コンストラクタにレイヤーの定義を書き、callメソッドにレイヤーのつなぎ方を書きます。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np 

# keras.Modelを継承する
class MyModel(keras.Model):
  def __init__(self):
      super(MyModel, self).__init__()
      # レイヤーを定義する
      self.dense1 = layers.Dense(512,input_shape=(1,),activation='relu')
      self.dense2 = layers.Dense(256, activation='relu')
      self.dense3 = layers.Dense(2, activation='softmax')

  def call(self, x, training=False):
      # レイヤーのつなぎ方を定義する
      x = self.dense1(x)
      x = self.dense2(x)
      x = self.dense3(x)
      return x

model = MyModel()

Subclassing APIは一番柔軟にモデルを構築することができます。例えば、TensorFlowの低水準APIを使用して独自のレイヤーを定義することが出来ます。

カスタムでrelu関数を定義してみます。keras.layers.Layerを継承したクラスを作り、callメソッドで自作のreluを呼び出します。

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np

# 自作のreluを定義する
def my_relu(x):
   return tf.math.maximum(0.0, x)

# 自作の活性化関数。keras.layers.Layerを継承する
class MyActivation(layers.Layer):
 def __init__(self, **kwargs):
   super(MyActivation, self).__init__(**kwargs)

 def call(self, inputs):
   # 自作のreluを呼び出す
   return my_relu(inputs)

# 先程のMyModelとほぼ同じだが、自作の活性化関数を利用するように修正を入れている
class MyModel(keras.Model):
  def __init__(self):
      super(MyModel, self).__init__()
      # 自作の活性化関数を登録しておく
      self.my_activation1 = MyActivation()
      self.my_activation2 = MyActivation()
      # ここで、Denseのactivationに'relu'を指定しない(後で自作のreluを使うため)
      self.dense1 = layers.Dense(512,input_shape=(1,))
      self.dense2 = layers.Dense(256)
      self.dense3 = layers.Dense(2, activation='softmax')

  def call(self, x, training=False):
      # 自作の活性化関数にDenseの出力を与える
      x = self.my_activation1(self.dense1(x))
      x = self.my_activation2(self.dense2(x))
      x = self.dense3(x)
      return x

model = MyModel()

# トレーニングや推論はこれまでと全く同じ
x_train = np.random.randint(0, 5, (20, 1))
y_train = np.where( x_train%2 == 0, [0,1], [1,0])
model.compile('adam', 'categorical_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, epochs=400, batch_size=8, validation_split=0.2)

model.predict([1])


TensorFlow 2系の3種類のモデルの書き方を紹介しました。

ブログ記事などを読むときはどのパターンで書かれているかを意識すると混乱することが少ないと思います。

なお、以前の記事で紹介したように、TensorFlowは1系の書き方と2系の書き方で異なっています。古い記事を読む時は1系の書き方であることを意識する必要がありますが、その説明はこちらの記事をご覧下さい。


最後に、若干宣伝ぽくて恐縮ですが、私はフリーランスエンジニアをしております。このような機械学習をiPhoneデバイス上で動作させるといったお仕事もできますので、お気軽にご相談下さい。

連絡先名:TokyoYoshida
連絡先: yoshidaforpublic@gmail.com


この記事が気に入ったらサポートをしてみませんか?