このページは Cloud Translation API によって翻訳されました。
Switch to English

グラフと関数の紹介

TensorFlow.orgで見る Google Colabで実行 GitHubでソースを表示する ノートブックをダウンロード

グラフとtf.function

このガイドはTensorFlowとKerasの表面の下にあり、TensorFlowがどのように機能するかを確認します。 Kerasをすぐに使い始めたい場合は、Kerasガイドのコレクションをご覧ください。

このガイドでは、TensorFlowを使用してコードに簡単な変更を加えてグラフを取得する方法、それらを保存および表現する方法、およびそれらを使用してモデルを高速化およびエクスポートする方法の中核について説明します。

これは短い形式の紹介です。これらの概念の完全な紹介については、 tf.functionガイドを参照してください

グラフとは?

前の3つのガイドでは、TensorFlowが熱心に実行さていることを確認しました。つまり、TensorFlowオペレーションはPythonによって実行され、オペレーションごとに実行され、結果がPythonに返されます。 Eager TensorFlowはGPUを利用して、変数、テンソル、さらには演算をGPUとTPUに配置できるようにします。デバッグも簡単です。

一部のユーザーにとっては、Pythonを使用する必要がない、または使用したくない場合があります。

ただし、PythonでTensorFlowをop-by-opで実行すると、他の方法では利用できない多くの高速化が妨げられます。 Pythonからテンソル計算を抽出できる場合は、それらをグラフにすることができます

グラフは、計算の単位を表すtf.Operationオブジェクトのセットを含むデータ構造です。オペレーション間を流れるデータの単位を表すtf.Tensorオブジェクト。それらはtf.Graphコンテキストで定義されます。これらのグラフはデータ構造であるため、元のPythonコードなしで保存、実行、復元できます。

これは、TensorBoardで視覚化したときに単純な2層グラフがどのように見えるかです。

2層のテンソルフローグラフ

グラフの利点

グラフを使用すると、柔軟性が大幅に向上します。モバイルアプリケーション、組み込みデバイス、バックエンドサーバーなど、Pythonインタープリターがない環境でTensorFlowグラフを使用できます。 TensorFlowは、Pythonからモデルをエクスポートするときに、保存されたモデルの形式としてグラフを使用します。

グラフも簡単に最適化されるため、コンパイラーは次のような変換を行うことができます。

  • 計算で定数ノードをフォールディング(「定数フォールディング」)して 、テンソルの値を静的に推測します。
  • 独立した計算のサブパートを分離し、スレッドまたはデバイス間で分割します。
  • 一般的な部分式を削除することにより、算術演算を簡素化します。

これと他の高速化を実行するための最適化システムGrapplerがあります。

つまり、グラフは非常に有用であり、TensorFlowを高速で実行し、並行して実行し、複数のデバイスで効率的実行できます

ただし、便宜上、Pythonで機械学習モデル(またはその他の計算)を定義し、必要に応じて自動的にグラフを作成する必要があります。

グラフのトレース

TensorFlowでグラフを作成する方法は、直接呼び出しまたはデコレータとしてtf.functionを使用することtf.function

 import tensorflow as tf
import timeit
from datetime import datetime
 
 # Define a Python function
def function_to_get_faster(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Create a `Function` object that contains a graph
a_function_that_uses_a_graph = tf.function(function_to_get_faster)

# Make some tensors
x1 = tf.constant([[1.0, 2.0]])
y1 = tf.constant([[2.0], [3.0]])
b1 = tf.constant(4.0)

# It just works!
a_function_that_uses_a_graph(x1, y1, b1).numpy()
 
array([[12.]], dtype=float32)

tf.function -ized関数は、同等のPythonと同じように機能するPython呼び出し可能オブジェクトです。それらは特定のクラス( python.eager.def_function.Function )を持っていますが、あなたにとってそれらは非追跡バージョンとして機能します。

tf.functionは、それが呼び出すPython関数を再帰的にトレースします。

 def inner_function(x, y, b):
  x = tf.matmul(x, y)
  x = x + b
  return x

# Use the decorator
@tf.function
def outer_function(x):
  y = tf.constant([[2.0], [3.0]])
  b = tf.constant(4.0)

  return inner_function(x, y, b)

# Note that the callable will create a graph that
# includes inner_function() as well as outer_function()
outer_function(tf.constant([[1.0, 2.0]])).numpy()
 
array([[12.]], dtype=float32)

TensorFlow 1.xを使用したことがある場合は、 Placeholderまたはtf.Sesssionを定義する必要がなかったことがtf.Sesssionます。

フロー制御と副作用

フロー制御とループは、デフォルトでtf.autographを介してtf.autograph変換されます。 Autographは、ループ構造の標準化、展開、 AST操作などの方法を組み合わせて使用​​します。

 def my_function(x):
  if tf.reduce_sum(x) <= 1:
    return x * x
  else:
    return x-1

a_function = tf.function(my_function)

print("First branch, with graph:", a_function(tf.constant(1.0)).numpy())
print("Second branch, with graph:", a_function(tf.constant([5.0, 5.0])).numpy())
 
First branch, with graph: 1.0
Second branch, with graph: [4. 4.]

Autograph変換を直接呼び出して、PythonがTensorFlow演算にどのように変換されるかを確認できます。これは、ほとんどの場合、判読できませんが、変換を確認できます。

 # Don't read the output too carefully.
print(tf.autograph.to_code(my_function))
 
def tf__my_function(x):
    with ag__.FunctionScope('my_function', 'fscope', ag__.ConversionOptions(recursive=True, user_requested=True, optional_features=(), internal_convert_user_code=True)) as fscope:
        do_return = False
        retval_ = ag__.UndefinedReturnValue()

        def get_state():
            return (retval_, do_return)

        def set_state(vars_):
            nonlocal retval_, do_return
            (retval_, do_return) = vars_

        def if_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = (ag__.ld(x) * ag__.ld(x))
            except:
                do_return = False
                raise

        def else_body():
            nonlocal retval_, do_return
            try:
                do_return = True
                retval_ = (ag__.ld(x) - 1)
            except:
                do_return = False
                raise
        ag__.if_stmt((ag__.converted_call(ag__.ld(tf).reduce_sum, (ag__.ld(x),), None, fscope) <= 1), if_body, else_body, get_state, set_state, ('retval_', 'do_return'), 2)
        return fscope.ret(retval_, do_return)


Autographは、 if-then句、ループ、 breakreturncontinueなどを自動的に変換します。

ほとんどの場合、Autographは特別な配慮なしに機能します。ただし、いくつかの注意点があり、 tf.functionガイド完全なサインのリファレンスだけでなく、ここでも役立ちます。

スピードアップを見て

tf.functionテンソルを使用する関数をラップするだけでは、コードの速度は自動的にはtf.functionしません。 1台のマシンで数回呼び出される小さな関数の場合、グラフまたはグラフフラグメントを呼び出すオーバーヘッドがランタイムを支配する可能性があります。また、GPUの多いたたみ込みのスタックなど、ほとんどの計算がすでにアクセラレータで行われている場合、グラフのスピードアップは大きくありません。

複雑な計算の場合、グラフは大幅なスピードアップを提供します。これは、グラフがPythonからデバイスへの通信を減らし、いくつかの高速化を実行するためです。

このコードは、いくつかの小さな密集したレイヤーで数回実行されます。

 # Create an oveerride model to classify pictures
class SequentialModel(tf.keras.Model):
  def __init__(self, **kwargs):
    super(SequentialModel, self).__init__(**kwargs)
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return x

input_data = tf.random.uniform([60, 28, 28])

eager_model = SequentialModel()
graph_model = tf.function(eager_model)

print("Eager time:", timeit.timeit(lambda: eager_model(input_data), number=10000))
print("Graph time:", timeit.timeit(lambda: graph_model(input_data), number=10000))

 
Eager time: 4.9249971129993355
Graph time: 2.026840765000088

ポリモーフィック関数

関数をトレースすると、 Functionオブジェクトがpolymorphicになります。ポリモーフィック関数は、1つのAPIの背後にあるいくつかの具体的な関数グラフをカプセル化するPython呼び出し可能関数です。

このFunction 、あらゆる種類のdtypesと形状で使用できます。新しい引数シグネチャで呼び出すたびに、元の関数は新しい引数で再トレースされます。 Functionその後、格納tf.Graphにそのトレースに対応するconcrete_function 。関数がその種の引数ですでにトレースされている場合は、事前トレースされたグラフを取得するだけです。

概念的には、次のようになります。

  • tf.Graphは、計算を記述する生のポータブルデータ構造です。
  • Functionは、ConcreteFunctionsのキャッシング、トレース、ディスパッチャーです。
  • ConcreteFunctionは、Pythonからグラフを実行できるグラフの熱心な互換ラッパーです。

多態性関数の検査

あなたは検査できa_functionの呼び出しの結果である、 tf.function Pythonの関数にmy_function 。この例では、3種類の引数を指定してa_functionを呼び出すと、3つの異なる具象関数が生成されます。

 print(a_function)

print("Calling a `Function`:")
print("Int:", a_function(tf.constant(2)))
print("Float:", a_function(tf.constant(2.0)))
print("Rank-1 tensor of floats", a_function(tf.constant([2.0, 2.0, 2.0])))
 
<tensorflow.python.eager.def_function.Function object at 0x7f466417bf60>
Calling a `Function`:
Int: tf.Tensor(1, shape=(), dtype=int32)
Float: tf.Tensor(1.0, shape=(), dtype=float32)
Rank-1 tensor of floats tf.Tensor([1. 1. 1.], shape=(3,), dtype=float32)

 # Get the concrete function that works on floats
print("Inspecting concrete functions")
print("Concrete function for float:")
print(a_function.get_concrete_function(tf.TensorSpec(shape=[], dtype=tf.float32)))
print("Concrete function for tensor of floats:")
print(a_function.get_concrete_function(tf.constant([2.0, 2.0, 2.0])))

 
Inspecting concrete functions
Concrete function for float:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=()
  Returns:
    float32 Tensor, shape=()
Concrete function for tensor of floats:
ConcreteFunction my_function(x)
  Args:
    x: float32 Tensor, shape=(3,)
  Returns:
    float32 Tensor, shape=(3,)

 # Concrete functions are callable
# Note: You won't normally do this, but instead just call the containing `Function`
cf = a_function.get_concrete_function(tf.constant(2))
print("Directly calling a concrete function:", cf(tf.constant(2)))
 
Directly calling a concrete function: tf.Tensor(1, shape=(), dtype=int32)

この例では、スタックがかなり遠くまで見えています。特にトレースを管理しているのでない限り、通常、ここに示すように具象関数を直接呼び出す必要はありません。

熱心な実行に戻す

長いスタックトレース、特にtf.Graphを参照するトレース、またはwith tf.Graph().as_default()を使用しているトレースを確認する場合があります。これは、グラフコンテキストで実行している可能性が高いことを意味します。 TensorFlowのコア関数は、Kerasのmodel.fit()などのグラフコンテキストを使用します。

多くの場合、熱心な実行をデバッグする方がはるかに簡単です。スタックトレースは比較的短く、簡単に理解できる必要があります。

グラフがデバッグを困難にする状況では、デバッグに熱心な実行を使用するように戻すことができます。

熱心に実行していることを確認する方法は次のとおりです。

  • 呼び出し可能オブジェクトとしてモデルとレイヤーを直接呼び出す

  • model.compile(run_eagerly=True) compile / fitを使用するときは、コンパイル時にmodel.compile(run_eagerly=True)使用します

  • tf.config.run_functions_eagerly(True)介してグローバル実行モードを設定します

run_eagerly=True使用

 # Define an identity layer with an eager side effect
class EagerLayer(tf.keras.layers.Layer):
  def __init__(self, **kwargs):
    super(EagerLayer, self).__init__(**kwargs)
    # Do some kind of initialization here

  def call(self, inputs):
    print("\nCurrently running eagerly", str(datetime.now()))
    return inputs
 
 # Create an override model to classify pictures, adding the custom layer
class SequentialModel(tf.keras.Model):
  def __init__(self):
    super(SequentialModel, self).__init__()
    self.flatten = tf.keras.layers.Flatten(input_shape=(28, 28))
    self.dense_1 = tf.keras.layers.Dense(128, activation="relu")
    self.dropout = tf.keras.layers.Dropout(0.2)
    self.dense_2 = tf.keras.layers.Dense(10)
    self.eager = EagerLayer()

  def call(self, x):
    x = self.flatten(x)
    x = self.dense_1(x)
    x = self.dropout(x)
    x = self.dense_2(x)
    return self.eager(x)

# Create an instance of this model
model = SequentialModel()

# Generate some nonsense pictures and labels
input_data = tf.random.uniform([60, 28, 28])
labels = tf.random.uniform([60])

loss_fn = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
 

まず、熱心にモデルをコンパイルします。モデルはトレースされないことに注意してください。その名前にもかかわらず、 compileは、損失関数、最適化、およびその他のトレーニングパラメーターのみをセットアップします。

 model.compile(run_eagerly=False, loss=loss_fn)
 

次に、 fitを呼び出して、関数がトレースされ(2回)、その後、熱心な効果が再び実行されることを確認します。

 model.fit(input_data, labels, epochs=3)
 
Epoch 1/3

Currently running eagerly 2020-08-04 01:22:21.848492

Currently running eagerly 2020-08-04 01:22:21.955102
2/2 [==============================] - 0s 1ms/step - loss: 1.4056
Epoch 2/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0037
Epoch 3/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0019

<tensorflow.python.keras.callbacks.History at 0x7f45f03c5630>

ただし、eagerで単一のエポックを実行する場合は、eagerの副作用を2回見ることができます。

 print("Running eagerly")
# When compiling the model, set it to run eagerly
model.compile(run_eagerly=True, loss=loss_fn)

model.fit(input_data, labels, epochs=1)

 
Running eagerly

Currently running eagerly 2020-08-04 01:22:22.152979
1/2 [==============>...............] - ETA: 0s - loss: 8.7806e-04
Currently running eagerly 2020-08-04 01:22:22.173295
2/2 [==============================] - 0s 5ms/step - loss: 4.6877e-04

<tensorflow.python.keras.callbacks.History at 0x7f45f0312940>

run_functions_eagerly使用

すべてを積極的に実行するようにグローバルに設定することもできます。これは、再トレースした場合にのみ機能することに注意してください。トレースされた関数はトレースされたままで、グラフとして実行されます。

 # Now, globally set everything to run eagerly
tf.config.run_functions_eagerly(True)
print("Run all functions eagerly.")

# First, trace the model, triggering the side effect
polymorphic_function = tf.function(model)

# It was traced...
print(polymorphic_function.get_concrete_function(input_data))

# But when you run the function again, the side effect happens (both times).
result = polymorphic_function(input_data)
result = polymorphic_function(input_data)
 
Run all functions eagerly.

Currently running eagerly 2020-08-04 01:22:22.202726
ConcreteFunction function(self)
  Args:
    self: float32 Tensor, shape=(60, 28, 28)
  Returns:
    float32 Tensor, shape=(60, 10)

Currently running eagerly 2020-08-04 01:22:22.206521

Currently running eagerly 2020-08-04 01:22:22.207818

 # Don't forget to set it back when you are done
tf.config.experimental_run_functions_eagerly(False)

 
WARNING:tensorflow:From <ipython-input-17-782fe9ce7b18>:2: experimental_run_functions_eagerly (from tensorflow.python.eager.def_function) is deprecated and will be removed in a future version.
Instructions for updating:
Use `tf.config.run_functions_eagerly` instead of the experimental version.

トレースとパフォーマンス

トレースにはある程度のオーバーヘッドがかかります。小さな関数のトレースは高速ですが、大きなモデルのトレースにはかなりの時間がかかる場合があります。この投資は通常、パフォーマンスの向上により迅速に回収されますが、大規模なモデルトレーニングの最初の数エポックは、トレースのために遅くなる可能性があることに注意することが重要です。

モデルがどれほど大きくても、頻繁にトレースすることは避けたいです。 tf.functionガイドのこのセクションでは、入力指定を設定し、テンソル引数を使用して再トレースを回避する方法について説明します。パフォーマンスが異常に低下している場合は、誤って再トレースしていないかどうかを確認することをお勧めします。

熱心なだけの副作用(Python引数の出力など)を追加して、関数がいつトレースされているかを確認できます。ここでは、新しいPython引数が常に再トレースをトリガーするため、追加の再トレースが表示されます。

 # Use @tf.function decorator
@tf.function
def a_function_with_python_side_effect(x):
  print("Tracing!")  # This eager
  return x * x + tf.constant(2)

# This is traced the first time
print(a_function_with_python_side_effect(tf.constant(2)))
# The second time through, you won't see the side effect
print(a_function_with_python_side_effect(tf.constant(3)))

# This retraces each time the Python argument chances
# as a Python argument could be an epoch count or other
# hyperparameter
print(a_function_with_python_side_effect(2))
print(a_function_with_python_side_effect(3))

 
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
tf.Tensor(11, shape=(), dtype=int32)
Tracing!
tf.Tensor(6, shape=(), dtype=int32)
Tracing!
tf.Tensor(11, shape=(), dtype=int32)

次のステップ

tf.function APIリファレンスページとガイドの両方で、より詳細な説明を読むことができます。