9
6

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.

TensorFlow2.0の傾向と対策 〜 keras三銃士

Last updated at Posted at 2019-06-09

はじめに

TF2.0のベータ版がついに公開されました(Announcing TensorFlow 2.0 Beta)。
すでにalpha版やそれ以前からTF2.0を試したりしている人もいると思います。ベータ版の公開を機に、同じコードで挙動が変わる例を紹介しようと思います。

問題のコード

問題のコードは次です。このコードは、TF1系のtf.keras、TF2.0のtf.keras、生kerasの3つで挙動が異なります

import numpy as np
import tensorflow as tf
from tensorflow import keras  # kerasと比較するときは単にimport keras


class M(keras.Model):
    def __init__(self):
        super().__init__()
        self.d = [keras.layers.Dense(1)]
        self.r = tf.random.uniform([])

    def call(self, x):
        return self.d[0](x + self.r)


def main():
    m = M()
    m.compile(keras.optimizers.SGD(), loss="mse")
    m.fit(np.zeros([5, 1]), np.ones([5, 1]))


if __name__ == '__main__':
    main()

全結合層が1つと乱数が1つあるモデルです。全結合層がリストに入っていますが、これは、本来は好きな個数を入れて使いたいのを説明のため1つにしています。

3つのkerasで次の違いが出ます。

変数の扱い 乱数の扱い
TF1系のkeras 訓練される 実行毎に異なる
TF2.0のkeras 訓練される 固定
生keras 訓練されない 実行毎に異なる

端的に何が起こっているかと言うと、

  • eagerモードのTF2.0では乱数を取得した時点で固定になる
  • 生kerasではリストなどで層を持つと訓練対象にならない

ことが原因です。

以下でその違いを詳しく説明します。

TF1系のkeras

乱数のシードを固定してsummaryやらpredictの値を見てみます。この場合は、あまり驚かない結果だと思います。

結果は、

  • paramsが2
  • 重みの値が訓練で変化
  • predictの結果が実行毎に異なる

という感じになります。

コード

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

print(tf.__version__)
tf.random.set_random_seed(0)


class M(keras.Model):
    def __init__(self):
        super().__init__()
        self.d = [keras.layers.Dense(1)]
        self.r = tf.random.uniform([])

    def call(self, x):
        return self.d[0](x + self.r)


def main():
    m = M()
    m.compile(keras.optimizers.SGD(), loss="mse")
    m.fit(np.zeros([5, 1]), np.ones([5, 1]))

    # trainableな変数について
    m.summary()
    s = keras.backend.get_session()
    print(s.run(m.variables))
    m.fit(np.zeros([5, 1]), np.ones([5, 1]))
    print(s.run(m.variables))

    # 乱数について
    print(m.predict(np.zeros([1, 1])))
    print(m.predict(np.zeros([1, 1])))


if __name__ == '__main__':
    main()

結果

1.13.1
<<TFのwarningは省略>>
5/5 [==============================] - 1s 193ms/sample - loss: 0.6114
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
dense (Dense)                multiple                  2
=================================================================
Total params: 2
Trainable params: 2
Non-trainable params: 0
_________________________________________________________________
[array([[0.6851259]], dtype=float32), array([0.01563858], dtype=float32)]
5/5 [==============================] - 0s 333us/sample - loss: 0.9524
[array([[0.68536645]], dtype=float32), array([0.03515691], dtype=float32)]
[[0.3513423]]
[[0.38965154]]

TF2.0のkeras

同じことをTF2.0でします。乱数の固定方法が少し変わったり、直接変数の値が見れる違いがありますが、やっていることは同じです。

結果は、

  • paramsが2
  • 重みの値が訓練で変化
  • predictの結果が実行毎に変化しない

という感じになります。実行毎に変化させるにはcallメソッド内で乱数を作る必要があります。

コード


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

print(tf.__version__)
tf.random.set_seed(0)


class M(keras.Model):
    def __init__(self):
        super().__init__()
        self.d = [keras.layers.Dense(1)]
        self.r = tf.random.uniform([])

    def call(self, x):
        return self.d[0](x + self.r)


def main():
    m = M()
    m.compile(keras.optimizers.SGD(), loss="mse")
    m.fit(np.zeros([5, 1]), np.ones([5, 1]))

    # trainableな変数について
    m.summary()
    print(m.variables)
    m.fit(np.zeros([5, 1]), np.ones([5, 1]))
    print(m.variables)

    # 乱数について
    print(m.predict(np.zeros([1, 1])))
    print(m.predict(np.zeros([1, 1])))


if __name__ == '__main__':
    main()

結果

2.0.0-beta0
<<TFのwarningは省略>>
5/5 [==============================] - 1s 137ms/sample - loss: 0.8910
Model: "m"
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
dense (Dense)                multiple                  2
=================================================================
Total params: 2
Trainable params: 2
Non-trainable params: 0
_________________________________________________________________
[<tf.Variable 'm/dense/kernel:0' shape=(1, 1) dtype=float32, numpy=array([[0.19747218]], dtype=float32)>, <tf.Variable 'm/dense/bias:0' shape=(1,) dtype=float32, numpy=array([0.01887905], dtype=float32)>]
Train on 5 samples
5/5 [==============================] - 0s 367us/sample - loss: 0.8528
[<tf.Variable 'm/dense/kernel:0' shape=(1, 1) dtype=float32, numpy=array([[0.20286475]], dtype=float32)>, <tf.Variable 'm/dense/bias:0' shape=(1,) dtype=float32, numpy=array([0.03734833], dtype=float32)>]
[[0.09657979]]
[[0.09657979]]

生keras

生kerasでも同じことをします。backendはTF1系なので、同じように使えるんだろうと思っていると痛い目を見ます。

結果は、

  • paramsが0
  • 重みの値が訓練で変化しない
  • predictの結果が実行毎に変化

という感じになります。雑な使い方をすると何も訓練されないという落とし穴があります。

コード

import numpy as np
import tensorflow as tf
import keras

print(tf.__version__)
print(keras.__version__)
tf.random.set_random_seed(0)


class M(keras.Model):
    def __init__(self):
        super().__init__()
        self.d = [keras.layers.Dense(1)]
        self.r = tf.random.uniform([])

    def call(self, x):
        return self.d[0](x + self.r)


def main():
    m = M()
    m.compile(keras.optimizers.SGD(), loss="mse")
    m.fit(np.zeros([5, 1]), np.ones([5, 1]))

    # trainableな変数について
    m.summary()
    s = keras.backend.get_session()
    print(s.run(m.d[0].weights))
    m.fit(np.zeros([5, 1]), np.ones([5, 1]))
    print(s.run(m.d[0].weights))

    # 乱数について
    print(m.predict(np.zeros([1, 1])))
    print(m.predict(np.zeros([1, 1])))


if __name__ == '__main__':
    main()

結果

Using TensorFlow backend.
1.13.1
2.2.4
<<TFのwarningは省略>>
5/5 [==============================] - 1s 152ms/step - loss: 0.4004
_________________________________________________________________
Layer (type)                 Output Shape              Param #
=================================================================
Total params: 0
Trainable params: 0
Non-trainable params: 0
_________________________________________________________________
[array([[1.1453341]], dtype=float32), array([0.], dtype=float32)]
Epoch 1/1
5/5 [==============================] - 0s 189us/step - loss: 0.9720
[array([[1.1453341]], dtype=float32), array([0.], dtype=float32)]
[[0.5283858]]
[[0.59240544]]

いかがでしたでしょうか

今回、どのkerasを使うかによって挙動が変わってくることが分かりました。そもそも層をリストで持つこととeagerの相性まで調べてないので、TF2.0ではこんなコードを書かないってなる可能性もあります。

とはいえ、仕様が少しずつ違うkerasの亜種が増えて心配です。生kerasがTF2.0のkerasに合わせていくという流れになるのか、別物として開発するのか、それとも別のライブラリが台頭するのでしょうか。

9
6
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
9
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?