Python
ML
機械学習
TensorFlow

Tensorflow 1.5のEager Executionを触ってみた

はじめに

2018年1月26日にTensorflow 1.5が公開されました。いや〜ほんとに早いですね。Tensorflowはディープラーニング関連のフレームワークで一番開発が早いですね。やはりGoogle先生の力か。。。もはやデファクト・スタンダードと言ってもいいのではないでしょうか?

ただ初心者の方にとっては少々敷居が高いようです (私自身Tensorflowを素で扱うのは苦手です。)。これはグラフの考え方もあるのかな。。。個人的に、このバージョンからEager executionが実装されたので、その敷居が下がったのではないかと感じています。つまり、(Eager Executionを使う限り) 明示的にグラフと流す値を区別する必要がなくなりました。普段いつもPythonで書いているようにTensorflowも書けばディープラーニングの計算を行うことができます。この記事では、その御利益をわかってもらうためにいくつかのサンプルコードを考えてみることにします。

注意
この記事の内容はTensorflowにおける各種演算まとめで追記したものとほとんど同じですが、新しい記事としてまとめることにしました。

Eager executionについて

Tensorflowによらず、ディープラーニング全体の根底にある考え方として、ネットワーク構造と与える値は別々に考えるというものがあります。つまり、最終的にどういう結果が得られるかというのは、ネットワーク構造どういう値(の集合)を考えるかに依存します。(もちろん最適化法とかいろいろ自由度もありますが、ここではそういう細かいことは省きます。このあたりはポテチ製造から学ぶTensorflowの考え方をご参考ください)

これまでは「値をグラフに流す」ときにrun.Session()を用いて書く必要がありましたが、Eager Executionを用いる限り、その必要がありません。Eager Execution: An imperative, define-by-run interface to TensorFlowではEager Executionのメリットして以下を挙げています。

  • Pythonのツールを用いたデバッグが容易になる。(あとで紹介するようにNumpy型の値を取り出せます。)
  • Pythonの構文を用いた処理が可能
  • 自分で実装したGradient等が利用可能
  • ほとんどすべてのTensorflowの操作が利用可能

ここではわかり易い例として、加算と値の代入のコードを通して触れてみたいと思います。詳しくは記事下の参考文献よりどうぞ。

例1: 加算

これまでTensorflowで定数同士を加算するには

tf_add_non_egex.py
import tensorflow as tf

const1 = tf.constant(2)
const2 = tf.constant(3)
add_op = tf.add(const1, const2) # 2 + 3
with tf.Session() as sess:
    result = sess.run(add_op)
    print(result)
# 5

と書く必要がありました。計算させたい変数に対してsess.runを実行する必要がありました。このようにすることで、変数add_opに値2, 3を流して計算させていました。一方、Eager Executionを使うようにコードを書き換えてみると、

tf_add_with_egex.py
import tensorflow as tf
import tensorflow.contrib.eager as tfe

tfe.enable_eager_execution()

const1 = tf.constant(2)
const2 = tf.constant(3)
add_op = tf.add(const1, const2) # 2 + 3
print(add_op)
# tf.Tensor(5, shape=(), dtype=int32)

となります。with文がなくなった代わりに、add_opprint文で表示させればよくなりました。ここでの注意点としてはまず、tfe.enable_eager_execution()は必ずプログラムの最初に書くようにします。 (Jupyter上で実行する際は、カーネルにメモリーが残っていると

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-2-a35e1dbe978d> in <module>()
      2 import tensorflow.contrib.eager as tfe
      3 
----> 4 tfe.enable_eager_execution()
      5 
      6 const1 = tf.constant(2)

/usr/local/lib/python3.6/site-packages/tensorflow/python/framework/ops.py in enable_eager_execution(config, device_policy)
   4892   else:
   4893     raise ValueError(
-> 4894         "tfe.enable_eager_execution has to be called at program startup.")
   4895 
   4896 

ValueError: tfe.enable_eager_execution has to be called at program startup.

というエラーが出るので(例)、カーネル再起動は必須です。

ちなみに出力結果の型を見てみると、

type(add_op)
# EagerTensor

となっています。そこでためしにこの値に3をたしてみると、

add_op + 3
# <tf.Tensor: id=5, shape=(), dtype=int32, numpy=8>

となり、numpyの値としては正しく計算されています。この値を取り出したいときは、numpy()メソッドを使います。つまり

add_op2 = add_op + 3
print(add_op2.numpy())
# 8

とすることで、値を取り出せます。ここで型は

type(add_op2)
# numpy.int32

となっているので、通常の四則演算が可能になっています。

例2: 値の代入

同様にして、値の代入のコード

tf_substitute_non_egex.py
import tensorflow as tf

# see https://www.tensorflow.org/api_guides/python/state_ops
print("\n変数\n=====================")

w = tf.Variable(tf.zeros([3, 2])) #変数(行列)wの宣言. 初期値はゼロ行列

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())

    print("w = %s" % sess.run(w))
    sess.run(tf.assign(w, tf.ones([3, 2])))

    wn = sess.run(w)
    print("w = %s" % wn)

を書き換えてみると

tf_substitute_with_egex.py
import tensorflow as tf
import tensorflow.contrib.eager as tfe

tfe.enable_eager_execution()

# see https://www.tensorflow.org/api_guides/python/state_ops
print("\n変数\n=====================")

w = tfe.Variable(tf.zeros([3, 2])) #変数(行列)wの宣言. 初期値はゼロ行列

tf.global_variables_initializer()

print("w = %s" % w)
tf.assign(w, tf.ones([3, 2]))

wn = w
print("w = %s" % wn)

# 変数
# =====================
# w = <tf.Variable 'Variable:0' shape=(3, 2) dtype=float32, numpy=
# array([[0., 0.],
#        [0., 0.],
#        [0., 0.]], dtype=float32)>
# w = <tf.Variable 'Variable:0' shape=(3, 2) dtype=float32, numpy=
# array([[1., 1.],
#        [1., 1.],
#        [1., 1.]], dtype=float32)>

となります。ここで変数を宣言するときにtfe.Variableを使うことに注意します。(余談ですが、Eager Executionを使うときはtf.Placeholderは使えません。incompatibleエラーがでます。) それ以外はwith文を全部取り除くことで、先ほどと同様に書き換えることができます。値をNumpy型で取り出したいときは

print(w.numpy()

# array([[1., 1.],
#       [1., 1.],
#       [1., 1.]], dtype=float32)

(wnも同様)とすればでき、numpy()メソッドを使えばNumpy arrayとして取り出し、Numpyの四則演算を行うことができます。

参考文献