In [ ]:
#@title Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

Eager Execution

Note: これらのドキュメントは私たちTensorFlowコミュニティが翻訳したものです。コミュニティによる 翻訳はベストエフォートであるため、この翻訳が正確であることや英語の公式ドキュメントの 最新の状態を反映したものであることを保証することはできません。 この翻訳の品質を向上させるためのご意見をお持ちの方は、GitHubリポジトリtensorflow/docsにプルリクエストをお送りください。 コミュニティによる翻訳やレビューに参加していただける方は、 docs-ja@tensorflow.org メーリングリストにご連絡ください。

TensorflowのEager Executionは、計算グラフの作成と評価を同時におこなう命令的なプログラミングを行うための環境です: オペレーションはあとで実行するための計算グラフでなく、具体的な計算結果の値を返します。 この方法を用いることにより、初心者にとってTensorFlowを始めやすくなり、またモデルのデバッグも行いやすくなります。 さらにコードの記述量も削減されます。 このガイドの内容を実行するためには、対話的インタープリタpythonを起動し、以下のコードサンプルを実行してください。

Eager Executionは研究や実験のための柔軟な機械学習環境として、以下を提供します。

  • 直感的なインタフェース—Pythonのデータ構造を使用して、コードをナチュラルに記述することができます。スモールなモデルとデータに対してすばやく実験を繰り返すことができます。
  • より簡単なデバッグ—opsを直接呼び出すことで、実行中のモデルを調査したり、変更をテストすることができます。Python標準のデバッグツールを用いて即座にエラーのレポーティングができます。
  • 自然な制御フロー—TensorFlowのグラフ制御フローの代わりにPythonの制御フローを利用するため、動的なモデルのパラメータ変更をシンプルに行うことができます。

Eager ExecutionはTensorflowのほとんどのオペレーションとGPUアクセラレーションをサポートします。 Eager Executionの実行例については、以下を参照してください。 tensorflow/contrib/eager/python/examples.

Note: いくつかのモデルはEager Executionを有効化することでオーバヘッドが増える可能性があります。 パフォーマンス改善を行っていますが、もしも問題を発見したら、バグ報告してベンチマークを共有してください。

セットアップと基本的な使い方

Eager Executionをはじめるためには、プログラムやコンソールセッションの最初に、tf.enable_eager_execution()を追加してください。 プログラムが呼び出すほかのモジュールにこのオペレーションを追加しないでください。


In [ ]:
import tensorflow.compat.v1 as tf

これでTensorFlowのオペレーションを実行してみましょう。結果はすぐに返されます。


In [ ]:
tf.executing_eagerly()

In [ ]:
x = [[2.]]
m = tf.matmul(x, x)
print("hello, {}".format(m))

Eager Executionを有効化することで、TensorFlowの挙動は変わります—TensorFlowは即座に式を評価して結果をPythonに返すようになります。 tf.Tensor オブジェクトは計算グラフのノードへのシンボリックハンドルの代わりに具体的な値を参照します。 セッションの中で構築して実行する計算グラフが存在しないため、print()やデバッガを使って容易に結果を調べることができます。 勾配計算を終了することなくテンソル値を評価、出力、およびチェックすることができます。

Eager Executionは、NumPyと一緒に使うことができます。 NumPyのオペレーションは、tf.Tensorを引数として受け取ることができます。 TensorFlow math operations はPythonオブジェクトとNumpy arrayをtf.Tensorにコンバートします。 tf.Tensor.numpyメソッドはオブジェクトの値をNumPyのndarray形式で返します。


In [ ]:
a = tf.constant([[1, 2],
                 [3, 4]])
print(a)

In [ ]:
# ブロードキャストのサポート
b = tf.add(a, 1)
print(b)

In [ ]:
# オペレータのオーバーロードがサポートされている
print(a * b)

In [ ]:
# NumPy valueの使用
import numpy as np

c = np.multiply(a, b)
print(c)

In [ ]:
# Tensorからnumpyの値を得る
print(a.numpy())
# => [[1 2]
#     [3 4]]

tf.contrib.eager モジュールは、Eager ExecutionとGraph Executionの両方の環境で利用可能なシンボルが含まれており、Graph Execution方式での記述に便利です:


In [ ]:
tfe = tf.contrib.eager

動的な制御フロー

Eager Executionの主要なメリットは、モデルを実行する際にホスト言語のすべての機能性が利用できることです。 たとえば、fizzbuzzが簡単に書けます:


In [ ]:
def fizzbuzz(max_num):
  counter = tf.constant(0)
  max_num = tf.convert_to_tensor(max_num)
  for num in range(1, max_num.numpy()+1):
    num = tf.constant(num)
    if int(num % 3) == 0 and int(num % 5) == 0:
      print('FizzBuzz')
    elif int(num % 3) == 0:
      print('Fizz')
    elif int(num % 5) == 0:
      print('Buzz')
    else:
      print(num.numpy())
    counter += 1

In [ ]:
fizzbuzz(15)

この関数はテンソル値に依存する条件式を持ち、実行時にこれらの値を表示します。

モデルの構築

多くの機械学習モデルはレイヤーを積み重ねによって成り立っています。Eager ExecutionでTensorFlowを使うときは、自分でレイヤーの内容を記述してもいいし、もしくは tf.keras.layersパッケージで提供されるレイヤーを使うこともできます。

レイヤーを表現するためには任意のPythonオブジェクトを使用できますが、 TensorFlowには便利な基本クラスとして tf.keras.layers.Layerがあります。 このクラスを継承した独自のレイヤーを実装してみます:


In [ ]:
class MySimpleLayer(tf.keras.layers.Layer):
  def __init__(self, output_units):
    super(MySimpleLayer, self).__init__()
    self.output_units = output_units

  def build(self, input_shape):
    # buildメソッドは、レイヤーが初めて使われたときに呼ばれます
    # build()で変数を作成すると、それらのshapeを入力のshapeに依存させることができ、
    # ユーザがshapeを完全に指定する必要はありません。
    # 既に完全なshapeが決まっている場合は、__init__()の中で変数を作成することもできます。
    self.kernel = self.add_variable(
      "kernel", [input_shape[-1], self.output_units])

  def call(self, input):
    # __call__の代わりにcall()を上書きします。
    return tf.matmul(input, self.kernel)

MySimpleLayerの代わりに、その機能のスーパーセットを持っているtf.keras.layers.Denseレイヤーを使用してください (このレイヤーはバイアスを加えることもできるもできます)。

レイヤーをモデルに組み立てるとき、レイヤーの線形スタックである モデルを表すために tf.keras.Sequentialを使うことができます。この書き方は基本的なモデルを扱いやすいです。


In [ ]:
model = tf.keras.Sequential([
  tf.keras.layers.Dense(10, input_shape=(784,)),  # 入力のshapeを指定する必要がある
  tf.keras.layers.Dense(10)
])

もしくは、 tf.keras.Modelを継承してモデルをクラスにまとめます。 これはレイヤー自身であるレイヤーのコンテナで、 tf.keras.Modelオブジェクトが他のtf.keras.Modelオブジェクトを含むことを可能にします。

Alternatively, organize models in classes by inheriting from tf.keras.Model. This is a container for layers that is a layer itself, allowing tf.keras.Model objects to contain other tf.keras.Model objects.


In [ ]:
class MNISTModel(tf.keras.Model):
  def __init__(self):
    super(MNISTModel, self).__init__()
    self.dense1 = tf.keras.layers.Dense(units=10)
    self.dense2 = tf.keras.layers.Dense(units=10)

  def call(self, input):
    """Run the model."""
    result = self.dense1(input)
    result = self.dense2(result)
    result = self.dense2(result)  # dense2レイヤーを再利用します reuse variables from dense2 layer
    return result

model = MNISTModel()

入力のshapeは最初のレイヤーに初めて入力データを渡すときにセットされるため、 モデル構築時にtf.keras.Modelクラスに設定する必要はありません。

tf.keras.layersクラスは独自のモデル変数を作成し、包含します。このモデル変数は、それを含むレイヤーオブジェクトのライフタイムにひもづきます。レイヤー変数を共有するには、それらのオブジェクトを共有します。

Eager Executionにおける学習

勾配の計算

自動微分はニューラルネットワークの学習で利用されるバックプロパゲーションなどの機械学習アルゴリズムの実装を行う上で便利です。 Eager Executionでは、勾配計算をあとで行うためのオペレーションをトレースするためにtf.GradientTape を利用します。

tf.GradientTape はトレースしない場合に最大のパフォーマンスを提供するオプトイン機能です。各呼び出し中に異なるオペレーションが発生する可能性があるため、すべてのforward-passオペレーションは一つの「テープ」に記録されます。勾配を計算するには、テープを逆方向に再生してから破棄します。特定の tf.GradientTapeは一つのグラデーションしか計算できません。後続の呼び出しは実行時エラーをスローします。


In [ ]:
w = tf.Variable([[1.0]])
with tf.GradientTape() as tape:
  loss = w * w

grad = tape.gradient(loss, w)
print(grad)  # => tf.Tensor([[ 2.]], shape=(1, 1), dtype=float32)

モデル学習

以下のexampleはMNISTという手書き数字分類を行うマルチレイヤーモデルを作成します。 Eager Execution環境における学習可能なグラフを構築するためのオプティマイザーとレイヤーAPIを提示します。


In [ ]:
# mnistデータのを取得し、フォーマットする
(mnist_images, mnist_labels), _ = tf.keras.datasets.mnist.load_data()

dataset = tf.data.Dataset.from_tensor_slices(
  (tf.cast(mnist_images[...,tf.newaxis]/255, tf.float32),
   tf.cast(mnist_labels,tf.int64)))
dataset = dataset.shuffle(1000).batch(32)

In [ ]:
# モデルを構築する
mnist_model = tf.keras.Sequential([
  tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
  tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
  tf.keras.layers.GlobalAveragePooling2D(),
  tf.keras.layers.Dense(10)
])

学習を行わずとも、モデルを呼び出して、Eager Executionにより、出力を検査することができます:


In [ ]:
for images,labels in dataset.take(1):
  print("Logits: ", mnist_model(images[0:1]).numpy())

kerasモデルは組み込みで学習のループを回すメソッドfitがありますが、よりカスタマイズが必要な場合もあるでしょう。 Eager Executionを用いて実装された学習ループのサンプルを以下に示します:


In [ ]:
optimizer = tf.train.AdamOptimizer()

loss_history = []

In [ ]:
for (batch, (images, labels)) in enumerate(dataset.take(400)):
  if batch % 10 == 0:
    print('.', end='')
  with tf.GradientTape() as tape:
    logits = mnist_model(images, training=True)
    loss_value = tf.losses.sparse_softmax_cross_entropy(labels, logits)

  loss_history.append(loss_value.numpy())
  grads = tape.gradient(loss_value, mnist_model.trainable_variables)
  optimizer.apply_gradients(zip(grads, mnist_model.trainable_variables),
                            global_step=tf.train.get_or_create_global_step())

In [ ]:
import matplotlib.pyplot as plt

plt.plot(loss_history)
plt.xlabel('Batch #')
plt.ylabel('Loss [entropy]')

値とオプティマイザ

tf.Variable オブジェクトは、学習中にアクセスされるミュータブルなtf.Tensor値を格納し、自動微分を容易にします。 モデルのパラメータは、変数としてクラスにカプセル化できます。

tf.GradientTapeと共にtf.Variableを使うことでモデルパラメータはよりカプセル化されます。たとえば、上の の自動微分の例は以下のように書き換えることができます:


In [ ]:
class Model(tf.keras.Model):
  def __init__(self):
    super(Model, self).__init__()
    self.W = tf.Variable(5., name='weight')
    self.B = tf.Variable(10., name='bias')
  def call(self, inputs):
    return inputs * self.W + self.B

# 3 * 2 + 2を近似するトイデータセット
NUM_EXAMPLES = 2000
training_inputs = tf.random_normal([NUM_EXAMPLES])
noise = tf.random_normal([NUM_EXAMPLES])
training_outputs = training_inputs * 3 + 2 + noise

# オプティマイズ対象のloss関数
def loss(model, inputs, targets):
  error = model(inputs) - targets
  return tf.reduce_mean(tf.square(error))

def grad(model, inputs, targets):
  with tf.GradientTape() as tape:
    loss_value = loss(model, inputs, targets)
  return tape.gradient(loss_value, [model.W, model.B])

# 定義:
# 1. モデル
# 2. モデルパラメータに関する損失関数の導関数
# 3. 導関数に基づいて変数を更新するストラテジ。
model = Model()
optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.01)

print("Initial loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))

# 学習ループ
for i in range(300):
  grads = grad(model, training_inputs, training_outputs)
  optimizer.apply_gradients(zip(grads, [model.W, model.B]),
                            global_step=tf.train.get_or_create_global_step())
  if i % 20 == 0:
    print("Loss at step {:03d}: {:.3f}".format(i, loss(model, training_inputs, training_outputs)))

print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))

Eager Executionの途中でオブジェクトのステータスを使用する

Graph Executionでは、プログラムの状態(変数など)はglobal collectionに格納され、それらの存続期間は tf.Sessionオブジェクトによって管理されます。 対照的に、Eager Executionの間、状態オブジェクトの存続期間は、対応するPythonオブジェクトの存続期間によって決定されます。

変数とオブジェクト

Eager Executionの間、変数はオブジェクトへの最後の参照が削除され、その後削除されるまで存続します。


In [ ]:
if tf.test.is_gpu_available():
  with tf.device("gpu:0"):
    v = tf.Variable(tf.random_normal([1000, 1000]))
    v = None  # vは既にGPUメモリ上を使用しないようにする

オブジェクトベースの保存

tf.train.Checkpointはチェックポイントを用いてtf.Variableを保存および復元することができます:


In [ ]:
x = tf.Variable(10.)
checkpoint = tf.train.Checkpoint(x=x)

In [ ]:
x.assign(2.)   # 変数に新しい値を割り当てて保存する
checkpoint_path = './ckpt/'
checkpoint.save('./ckpt/')

In [ ]:
x.assign(11.)  # 保存後に変数の値を変更する

# チェックポイントから値を復元する
checkpoint.restore(tf.train.latest_checkpoint(checkpoint_path))

print(x)  # => 2.0

モデルを保存して読み込むために、 tf.train.Checkpointは隠れ変数なしにオブジェクトの内部状態を保存します。 モデルオプティマイザ、そしてグローバルステップの状態を記録するには、それらを tf.train.Checkpointに渡します。


In [ ]:
import os
import tempfile

model = tf.keras.Sequential([
  tf.keras.layers.Conv2D(16,[3,3], activation='relu'),
  tf.keras.layers.GlobalAveragePooling2D(),
  tf.keras.layers.Dense(10)
])
optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
checkpoint_dir = tempfile.mkdtemp()
checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt")
root = tf.train.Checkpoint(optimizer=optimizer,
                           model=model,
                           optimizer_step=tf.train.get_or_create_global_step())

root.save(checkpoint_prefix)
root.restore(tf.train.latest_checkpoint(checkpoint_dir))

オブジェクト指向メトリクス

tfe.metricsはオブジェクトとして保存されます。新しいデータを呼び出し可能オブジェクトに渡してメトリクスを更新し、 tfe.metrics.resultメソッドを使って結果を取得します。次に例を示します:


In [ ]:
m = tfe.metrics.Mean("loss")
m(0)
m(5)
m.result()  # => 2.5
m([8, 9])
m.result()  # => 5.5

サマリとTensorBoard

TensorBoard はモデルの学習プロセスを理解、デバッグ、最適化するための可視化ツールです。プログラムの実行中に書き込まれるサマリイベントを使用します。

tf.contrib.summaryはEager ExecutionとGraph Executionの両方の環境と互換性があります。 tf.contrib.summary.scalarのようなサマリオペレーションはモデル構築の間に挿入されます。 たとえば、100のグローバルステップごとにサマリを記録するには、次のようにします。


In [ ]:
global_step = tf.train.get_or_create_global_step()

logdir = "./tb/"
writer = tf.contrib.summary.create_file_writer(logdir)
writer.set_as_default()

for _ in range(10):
  global_step.assign_add(1)
  # record_summariesメソッドをincludeする必要がある
  with tf.contrib.summary.record_summaries_every_n_global_steps(100):
    # ここにモデルのコードを記述する
    tf.contrib.summary.scalar('global_step', global_step)

In [ ]:
!ls tb/

高度な自動分類トピック

動的なモデル

tf.GradientTapeは動的モデルでも使うことができます。 以下のバックトラックライン検索 アルゴリズムの例は、複雑な制御フローにも関わらず 勾配があり、微分可能であることを除いて、通常のNumPyコードのように見えます:


In [ ]:
def line_search_step(fn, init_x, rate=1.0):
  with tf.GradientTape() as tape:
    # 変数は自動的に記録されるが、手動でTensorを監視する
    tape.watch(init_x)
    value = fn(init_x)
  grad = tape.gradient(value, init_x)
  grad_norm = tf.reduce_sum(grad * grad)
  init_value = value
  while value > init_value - rate * grad_norm:
    x = init_x - rate * grad
    value = fn(x)
    rate /= 2.0
  return x, value

勾配計算のための追加機能

tf.GradientTapeは強力な勾配計算インタフェースですが、 自動微分に利用できる別のAutogradスタイルのAPIもあります。 これらの関数はテンソルと勾配関数のみを使って、tf.variablesを使わずに数式コードを書く場合に便利です:

  • tfe.gradients_function—引数をとり、入力関数パラメータの導関数を計算する関数を返します。 入力パラメータはスカラ値を返さなければなりません。返された関数が されると、 tf.Tensorオブジェクトのリストを返します:入力関数のそれぞれの 引数に対して一つの要素。重要なものすべてを関数パラメータとして渡さなければならないので、 多くのtrainableパラメータに依存している場合、これは扱いにくくなります。
  • tfe.value_and_gradients_functiontfe.gradients_functionに似ていますが、 返された関数が呼び出されると、その引数に関する入力関数の導関数のリストに加えて、入力関数からの値を返します。

次の例では、 tfe.gradients_functionは引数としてsquare 関数を取り、その入力に関して squareの偏微分 導関数を計算する関数を返します。 3におけるsquareの微分を計算するために、 grad(3.0)6を返します。


In [ ]:
def square(x):
  return tf.multiply(x, x)

grad = tfe.gradients_function(square)

In [ ]:
square(3.).numpy()

In [ ]:
grad(3.)[0].numpy()

In [ ]:
# 平方の二次導関数:
gradgrad = tfe.gradients_function(lambda x: grad(x)[0])
gradgrad(3.)[0].numpy()

In [ ]:
# 3次導関数はNoneになる:
gradgradgrad = tfe.gradients_function(lambda x: gradgrad(x)[0])
gradgradgrad(3.)

In [ ]:
# フロー制御:
def abs(x):
  return x if x > 0. else -x

grad = tfe.gradients_function(abs)

In [ ]:
grad(3.)[0].numpy()

In [ ]:
grad(-3.)[0].numpy()

カスタム勾配

カスタム勾配は、Eager ExecutionとGraph Executionの両方の環境で、勾配を上書きする簡単な方法です。 フォワード関数では、 入力、出力、または中間結果に関する勾配を定義します。たとえば、逆方向パスにおいて勾配のノルムを切り取る簡単な方法は次のとおりです:


In [ ]:
@tf.custom_gradient
def clip_gradient_by_norm(x, norm):
  y = tf.identity(x)
  def grad_fn(dresult):
    return [tf.clip_by_norm(dresult, norm), None]
  return y, grad_fn

カスタム勾配は、一連の演算に対して数値的に安定した勾配を提供するために共通的に使用されます。


In [ ]:
def log1pexp(x):
  return tf.log(1 + tf.exp(x))
grad_log1pexp = tfe.gradients_function(log1pexp)

In [ ]:
# 勾配計算はx = 0のときにはうまくいきます。
grad_log1pexp(0.)[0].numpy()

In [ ]:
# しかし、x = 100のときは数値的不安定により失敗します。
grad_log1pexp(100.)[0].numpy()

ここで、 log1pexp関数はカスタム勾配を用いて解析的に単純化することができます。 以下の実装は、フォワードパスの間に計算された tf.exp(x)の値を 再利用します—冗長な計算を排除することでより効率的になります:


In [ ]:
@tf.custom_gradient
def log1pexp(x):
  e = tf.exp(x)
  def grad(dy):
    return dy * (1 - 1 / (1 + e))
  return tf.log(1 + e), grad

grad_log1pexp = tfe.gradients_function(log1pexp)

In [ ]:
# 上と同様に、勾配計算はx = 0のときにはうまくいきます。
grad_log1pexp(0.)[0].numpy()

In [ ]:
# また、勾配計算はx = 100でも機能します。
grad_log1pexp(100.)[0].numpy()

パフォーマンス

Eager Executionの間、計算は自動的にGPUにオフロードされます。計算を実行するデバイスを指定したい場合は、 tf.device( '/ gpu:0')ブロック(もしくはCPUを指定するブロック)で囲むことで指定できます:


In [ ]:
import time

def measure(x, steps):
    
  # TensorFlowはGPUを初めて使用するときに初期化するため、時間計測対象からは除外する。
  tf.matmul(x, x)
  start = time.time()
  for i in range(steps):
    x = tf.matmul(x, x)
    
  # tf.matmulは、行列乗算が完了する前に戻ることができます。
  # (たとえば、CUDAストリームにオペレーションをエンキューした後に戻すことができます)。
  # 以下のx.numpy()呼び出しは、すべてのキューに入れられたオペレーションが完了したことを確認します。
  # (そして結果をホストメモリにコピーするため、計算時間は単純なmatmulオペレーションよりも多くのことを含む時間になります。)
  _ = x.numpy()
  end = time.time()
  return end - start

shape = (1000, 1000)
steps = 200
print("Time to multiply a {} matrix by itself {} times:".format(shape, steps))

# CPU上で実行するとき:
with tf.device("/cpu:0"):
  print("CPU: {} secs".format(measure(tf.random_normal(shape), steps)))

# GPU上で実行するとき(GPUが利用できれば):
if tfe.num_gpus() > 0:
  with tf.device("/gpu:0"):
    print("GPU: {} secs".format(measure(tf.random_normal(shape), steps)))
else:
  print("GPU: not found")

tf.Tensorオブジェクトはそのオブジェクトに対するオペレーションを実行するために別のデバイスにコピーすることができます:


In [ ]:
if tf.test.is_gpu_available():
  x = tf.random_normal([10, 10])

  x_gpu0 = x.gpu()
  x_cpu = x.cpu()

  _ = tf.matmul(x_cpu, x_cpu)    # CPU上で実行するとき
  _ = tf.matmul(x_gpu0, x_gpu0)  # GPU:0上で実行するとき

  if tfe.num_gpus() > 1:
    x_gpu1 = x.gpu(1)
    _ = tf.matmul(x_gpu1, x_gpu1)  # GPU:1で実行するとき

ベンチマーク

GPUでの ResNet50 の学習のような、計算量の多いモデルの場合は、Eager ExecutionのパフォーマンスはGraph Executionのパフォーマンスに匹敵します。 しかし、この2つの環境下のパフォーマンスの違いは計算量の少ないモデルではより大きくなり、小さなたくさんのオペレーションからなるモデルでホットコードパスを最適化するためにやるべきことがあります。

Graph Executionの実行

Eager Executionは開発とデバッグをより対話的にしますが、 TensorFlowのGraph Executionは分散学習、パフォーマンスの最適化、そしてプロダクション環境へのデプロイの観点で利点があります。 しかし、Graph Executionのコードの記述方法、標準的なのPythonコードの書き方と異なり、デバッグがより難しく感じるかもしれません。

Graph Execution形式のモデルの構築と学習のために、Pythonプログラムは最初に計算グラフを構築し、 それからC++ベースのランタイムで実行するためにSession.runを呼び出し、グラフを渡します。この機能の特徴は以下のとおりです:

  • 静的なautodiffによる自動微分
  • プラットフォームに依存しないサーバーへの簡単なデプロイ
  • グラフベースの最適化(共通的な部分式の削除、定数の畳み込みなど)
  • コンパイルとカーネルフュージョン
  • 自動分散とレプリケーション(分散システムへのノード配置)

Eager Executionのコードは、Graph Executionのコードよりもデプロイが難しいです:モデルから 計算グラフを生成するか、またはサーバ上で直接Pythonランタイムからコードを実行する必要があります。

互換性のあるコードの記述

Eager Execution環境で記述されたコードは、Eager Executionが有効になっていない新しいPythonセッションでおなじコードを実行するだけで おなじコードのままGraph Executionで実行することができます。

ほとんどのTensorFlowオペレーションはEager Executionで動作しますが、注意すべき点がいくつかあります:

  • 入力処理にはキューの代わりに tf.dataを使います。この方法はより高速で簡単です。
  • tf.keras.layerstf.keras.Modelのような、オブジェクト指向のレイヤーAPIを使用します—これらのAPIは変数のための明示的なストレージを持っているためです。
  • ほとんどのモデルコードは、Eager ExecutionとGraph Executionにおなじように機能しますが、例外があります。 (たとえば、Pythonによる制御フローで入力に基づいて演算を変更する動的モデルなど)
  • 一度tf.enable_eager_executionによってEager Executionが有効化されると、それを無効化することはできません。 Graph Executionに戻すには、新しいPythonセッションを開始する必要があります。

以上が、Eager Execution Graph Executionの両方のためのコードを書くためのベストプラクティスです。これによって、 Eager Executionによる対話的な実験とデバッガビリティを享受することができ、かつGraph Executionによる分散パフォーマンスの恩恵を受けることができます。

Eager Executionを用いてコードを記述、デバッグ、実験を繰り返したのちにプロダクションへのデプロイのためにモデルパスをimportします。 モデル変数を保存および復元するには tf.train.Checkpointを使います。これはEager ExecutionとGraph Executionの両環境の互換性を担保します。 以下にEager Executionのサンプル集があります:
tensorflow/contrib/eager/python/examples

Graph Execution環境でEager Executionを使う

tfe.py_funcを使ってTensorFlowGraph Execution環境でEager Executionを選択的に可能にすることができます。 この機能は、 tf.enable_eager_execution()が呼ばれていないときに使うことができます。


In [ ]:
def my_py_func(x):
  x = tf.matmul(x, x)  # tfオペレーションを使用することができる
  print(x)  # しかしEager Executionで実行される!
  return x

with tf.Session() as sess:
  x = tf.placeholder(dtype=tf.float32)
  # Graph Execution環境でEager Executionを呼び出す
  pf = tfe.py_func(my_py_func, [x], tf.float32)

  sess.run(pf, feed_dict={x: [[2.0]]})  # [[4.0]]