はじめに
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に合わせていくという流れになるのか、別物として開発するのか、それとも別のライブラリが台頭するのでしょうか。