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

グラフと関数の紹介

TensorFlow.orgで表示 GoogleColabで実行 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層のテンソルフローグラフ

グラフの利点

グラフを使用すると、非常に柔軟性があります。 TensorFlowグラフは、モバイルアプリケーション、組み込みデバイス、バックエンドサーバーなど、Pythonインタープリターがない環境で使用できます。 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化された関数は、同等の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.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がTensorFlowopsにどのように変換されるかを確認できます。これはほとんど読めませんが、変換を見ることができます。

# 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などを自動的に変換します。

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

スピードアップを見る

テンソルを使用する関数をtf.functionラップするだけでは、コードが自動的に高速化されるわけではありません。単一のマシンで数回呼び出される小さな関数の場合、グラフまたはグラフフラグメントを呼び出すオーバーヘッドがランタイムを支配する可能性があります。また、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: 5.302399797999897
Graph time: 2.3688509589999285

多形関数

関数をトレースするときは、多態性のFunctionオブジェクトを作成します。多態性関数は、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 0x7fe28ce18cf8>
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-09-30 01:22:01.861126

Currently running eagerly 2020-09-30 01:22:01.982367
2/2 [==============================] - 0s 2ms/step - loss: 1.5409
Epoch 2/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0021
Epoch 3/3
2/2 [==============================] - 0s 1ms/step - loss: 0.0013

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

ただし、熱心に1つのエポックを実行した場合でも、熱心な副作用が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-09-30 01:22:02.196963
1/2 [==============>...............] - ETA: 0s - loss: 9.7674e-04
Currently running eagerly 2020-09-30 01:22:02.220018
2/2 [==============================] - 0s 6ms/step - loss: 5.2117e-04

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

run_functions_eagerly使用

また、すべてをグローバルに設定して、熱心に実行することもできます。これは、多態性関数のトレースされた関数をバイパスし、元の関数を直接呼び出すスイッチです。これをデバッグに使用できます。

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

# Create a polymorphic function
polymorphic_function = tf.function(model)

print("Tracing")
# This does, in fact, trace the function
print(polymorphic_function.get_concrete_function(input_data))

print("\nCalling twice eagerly")
# When you run the function again, you will see the side effect
# twice, as the function is running eagerly.
result = polymorphic_function(input_data)
result = polymorphic_function(input_data)
Run all functions eagerly.
Tracing

Currently running eagerly 2020-09-30 01:22:02.249703
ConcreteFunction function(self)
  Args:
    self: float32 Tensor, shape=(60, 28, 28)
  Returns:
    float32 Tensor, shape=(60, 10)

Calling twice eagerly

Currently running eagerly 2020-09-30 01:22:02.254759

Currently running eagerly 2020-09-30 01:22:02.256224

# Don't forget to set it back when you are done
tf.config.experimental_run_functions_eagerly(False)
WARNING:tensorflow:From <ipython-input-1-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 changes,
# 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リファレンスページとガイドの両方で読むことができます。