ホーム » Eager execution » TensorFlow 2.0 Beta : 上級 Tutorials : カスタマイズ :- tf.function

TensorFlow 2.0 Beta : 上級 Tutorials : カスタマイズ :- tf.function

TensorFlow 2.0 Beta : 上級 Tutorials : カスタマイズ :- tf.function (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 07/04/2019

* 本ページは、TensorFlow の本家サイトの TF 2.0 Beta – Advanced Tutorials – Customization の以下のページを翻訳した上で適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

カスタマイズ :- tf.function

TensorFlow 2.0 では eager execution はデフォルトで有効になっています。ユーザインターフェイスは直感的ですが (一度限りの (= one-off) 演算の実行は遥かに容易で高速です)、これはパフォーマンスと配備性 (= deployability) の犠牲をもたらす可能性があります。

最高点のパフォーマンスを得て貴方のモデルをどこでも配備可能にするために、貴方のプログラムからグラフを作成するために tf.function を使用します。AutoGraph のおかげで、Python コードの驚くべき総量が tf.function で単に動作しますが、依然として注意すべき落とし穴があります。

主な重要な点と推奨は :

  • オブジェクト mutation やリスト append のような Python 副作用に依拠しないでください。
  • tf.function は NumPy ops や Python プリミティブよりも TensorFlow ops で最善に動作します。
  • 疑わしいときには、”for x in y” イディオムを使用してください。
from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow==2.0.0-beta1
import tensorflow as tf
import contextlib

# Some helper code to demonstrate the kinds of errors you might encounter.
@contextlib.contextmanager
def assert_raises(error_class):
  try:
    yield
  except error_class as e:
    print('Caught expected exception \n  {}: {}'.format(error_class, e))
  except Exception as e:
    print('Got unexpected exception \n  {}: {}'.format(type(e), e))
  else:
    raise Exception('Expected {} to be raised but no error was raised!'.format(
        error_class))

貴方が定義する tf.function はちょうどコア TensorFlow 演算のようなものです : それを eagerly に実行できます; それをグラフで使用できます; それは勾配を持ちます; 等々。

# A function is like an op

@tf.function
def add(a, b):
  return a + b

add(tf.ones([2, 2]), tf.ones([2, 2]))  #  [[2., 2.], [2., 2.]]
<tf.Tensor: id=14, shape=(2, 2), dtype=float32, numpy=
array([[2., 2.],
       [2., 2.]], dtype=float32)>
# Functions have gradients

@tf.function
def add(a, b):
  return a + b

v = tf.Variable(1.0)
with tf.GradientTape() as tape:
  result = add(v, 1.0)
tape.gradient(result, v)
<tf.Tensor: id=40, shape=(), dtype=float32, numpy=1.0>
# You can use functions inside functions

@tf.function
def dense_layer(x, w, b):
  return add(tf.matmul(x, w), b)

dense_layer(tf.ones([3, 2]), tf.ones([2, 2]), tf.ones([2]))
<tf.Tensor: id=67, shape=(3, 2), dtype=float32, numpy=
array([[3., 3.],
       [3., 3.],
       [3., 3.]], dtype=float32)>

 

Tracing とポリモーフィズム

Python の動的型付けは関数を様々な引数型で呼び出すことができて、そして Python は各シナリオで異なる何かを行なうことを意味します。

その一方で、TensorFlow グラフは静的 dtype と shape 次元を要求します。tf.function は正しいグラフを生成するために必要なとき関数を辿り直す (= retrace) ことによりこの隔たりの橋渡しをします。tf.function の利用方法の微妙な点の殆どはこの retracing 動作から生じています。

何が起きるかを見るために異なる型の引数で関数を呼び出せます。

# Functions are polymorphic

@tf.function
def double(a):
  print("Tracing with", a)
  return a + a

print(double(tf.constant(1)))
print()
print(double(tf.constant(1.1)))
print()
print(double(tf.constant("a")))
print()
Tracing with Tensor("a:0", shape=(), dtype=int32)
tf.Tensor(2, shape=(), dtype=int32)

Tracing with Tensor("a:0", shape=(), dtype=float32)
tf.Tensor(2.2, shape=(), dtype=float32)

Tracing with Tensor("a:0", shape=(), dtype=string)
tf.Tensor(b'aa', shape=(), dtype=string)

tracing 動作を制御するには、次のテクニックを使用します :

  • 新しい tf.function を作成します。別々の tf.function オブジェクトは trace を共有しないことが保証されます。
  • 特定の trace を得るために get_concrete_function メソッドを使用します。
  • グラフ呼び出し毎に一度だけ trace するために tf.function を呼び出すとき input_signature を指定します。
print("Obtaining concrete trace")
double_strings = double.get_concrete_function(tf.TensorSpec(shape=None, dtype=tf.string))
print("Executing traced function")
print(double_strings(tf.constant("a")))
print(double_strings(a=tf.constant("b")))
print("Using a concrete trace with incompatible types will throw an error")
with assert_raises(tf.errors.InvalidArgumentError):
  double_strings(tf.constant(1))
Obtaining concrete trace
Tracing with Tensor("a:0", dtype=string)
Executing traced function
tf.Tensor(b'aa', shape=(), dtype=string)
tf.Tensor(b'bb', shape=(), dtype=string)
Using a concrete trace with incompatible types will throw an error
Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.InvalidArgumentError'>: cannot compute __inference_double_98 as input #0(zero-based) was expected to be a string tensor but is a int32 tensor [Op:__inference_double_98]
@tf.function(input_signature=(tf.TensorSpec(shape=[None], dtype=tf.int32),))
def next_collatz(x):
  print("Tracing with", x)
  return tf.where(tf.equal(x % 2, 0), x // 2, 3 * x + 1)

print(next_collatz(tf.constant([1, 2])))
# We specified a 1-D tensor in the input signature, so this should fail.
with assert_raises(ValueError):
  next_collatz(tf.constant([[1, 2], [3, 4]]))
Tracing with Tensor("x:0", shape=(None,), dtype=int32)
tf.Tensor([4 1], shape=(2,), dtype=int32)
Caught expected exception 
  <class 'ValueError'>: Python inputs incompatible with input_signature: inputs ((<tf.Tensor: id=125, shape=(2, 2), dtype=int32, numpy=
array([[1, 2],
       [3, 4]], dtype=int32)>,)), input_signature ((TensorSpec(shape=(None,), dtype=tf.int32, name=None),))

 

いつ retrace するか?

多態的な (= polymorphic) tf.function は tracing により生成された具体的な関数のキャッシュを保持します。キャッシュキーは効果的に関数引数と kwargs から生成されたキーのタプルです。tf.Tensor 引数のために生成されたキーはその shape と型です。Python プリミティブのために生成されたキーはその値です。総ての他の Python 型については、キーはオブジェクト id() に基づきその結果メソッドはクラスの各インスタンスのために独立的に trace されます。将来的には、TensorFlow は Python オブジェクトのためにより洗練されたキャッシングを追加するかもしれません、それは tensor に安全に変換できます。

 

Python あるいは Tensor args?

しばしば、Python 引数はハイパーパラメータとグラフ構築を制御するために使用されます – 例えば、num_layers=10 や training=True や nonlinearity=’relu’ です。そのため Python 引数が変わる場合、グラフを retrace しなければならないことは意味をなします。

けれども、Python 引数がグラフ構築を制御するために使用されていないこともあり得ます。これらのケースでは、Python 値の変更は必要のない retrace を引き起こす可能性があります。例えばこの訓練ループを取れば、AutoGraph はそれを動的に展開するでしょう。複数の trace にもかかわらず、生成されたグラフは実際には同一です、従ってこれは少し非効率的です。

def train_one_step():
  pass

@tf.function
def train(num_steps):
  print("Tracing with num_steps = {}".format(num_steps))
  for _ in tf.range(num_steps):
    train_one_step()

train(num_steps=10)
train(num_steps=20)
Tracing with num_steps = 10
Tracing with num_steps = 20

ここでの最も単純な回避方法は、それら (引数) が生成されたグラフの shape に影響しない場合に引数を Tensor にキャストすることです。

train(num_steps=tf.constant(10))
train(num_steps=tf.constant(20))
Tracing with num_steps = Tensor("num_steps:0", shape=(), dtype=int32)

 

tf.function の副作用

一般に、(プリントや mutating オブジェクトのような) Python 副作用は tracing の間だけ発生します。それでは tf.function からどのように副作用を確実に引き起こせるのでしょう?

一般的な経験則は trace をデバッグするために Python 副作用だけを使用することです。さもなければ、 tf.Variable.assign, tf.printtf.summary のような TensorFlow ops は各呼び出しに伴う TensorFlow ランタイムにより貴方のコードが trace されて実行されることを確かなものにする最善の方法です。一般に functional スタイルの使用は最善の結果を生じるでしょう。

@tf.function
def f(x):
  print("Traced with", x)
  tf.print("Executed with", x)

f(1)
f(1)
f(2)
Traced with 1
Executed with 1
Executed with 1
Traced with 2
Executed with 2

tf.function の各起動中に Python コードを実行することを望むのであれば、tf.py_function が脱出ハッチ (= exit hatch) です。tf.py_function の欠点はそれは可搬ではなく特に非効率的で、分散セットアップ (マルチ GPU, TPU) では上手く動作しません。また、tf.py_function はグラフに配線 (= wire) されなければならず、それは総ての入力/出力を tensor にキャストします。

external_list = []

def side_effect(x):
  print('Python side effect')
  external_list.append(x)

@tf.function
def f(x):
  tf.py_function(side_effect, inp=[x], Tout=[])

f(1)
f(1)
f(1)
assert len(external_list) == 3
# .numpy() call required because py_function casts 1 to tf.constant(1)
assert external_list[0].numpy() == 1
WARNING: Logging before flag parsing goes to stderr.
W0629 01:08:56.691862 140487172290304 backprop.py:842] The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int32
W0629 01:08:56.694054 140487155504896 backprop.py:842] The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int32
W0629 01:08:56.695917 140487172290304 backprop.py:842] The dtype of the watched tensor must be floating (e.g. tf.float32), got tf.int32

Python side effect
Python side effect
Python side effect

 

Python 状態に注意する

generator と iterator のような、多くの Python 特徴は状態を追跡するために Python ランタイムに依拠します。一般に、これらの構成物が Eager モードで期待どおりに動作する一方で、多くの予期せぬことが tracing 動作により tf.function 内で起きるかもしれません。

一つのサンプルを与えるために、進んだ iterator 状態は Python 副作用で従って tracing の間だけに発生します。

external_var = tf.Variable(0)
@tf.function
def buggy_consume_next(iterator):
  external_var.assign_add(next(iterator))
  tf.print("Value of external_var:", external_var)

iterator = iter([0, 1, 2, 3])
buggy_consume_next(iterator)
# This reuses the first value from the iterator, rather than consuming the next value.
buggy_consume_next(iterator)
buggy_consume_next(iterator)
Value of external_var: 0
Value of external_var: 0
Value of external_var: 0

iterator が完全に tf.function 内で生成されて消費される場合には、それは正しく動作します。けれども、全体の iterator が多分 trace され、これは巨大グラフ (= giant graph) に繋がるかもしれません。これは貴方が望むものかもしれません。しかし貴方が Python リストで表わされる巨大な in-memory データセット上で訓練している場合、これは非常に巨大なグラフを生成するかもしれず、そして tf.function はスピードアップを生み出さないでしょう。

Python データに渡り iterate することを望む場合、最も安全な方法はそれを tf.data.Dataset でラップして “for x in y” イディオムを使用することです。AutoGraph は y が tensor か tf.data.Dataset のときループのための安全に変換するための特別なサポートを持ちます。

def measure_graph_size(f, *args):
  g = f.get_concrete_function(*args).graph
  print("{}({}) contains {} nodes in its graph".format(
      f.__name__, ', '.join(map(str, args)), len(g.as_graph_def().node)))

@tf.function
def train(dataset):
  loss = tf.constant(0)
  for x, y in dataset:
    loss += tf.abs(y - x) # Some dummy computation.
  return loss

small_data = [(1, 1)] * 2
big_data = [(1, 1)] * 10
measure_graph_size(train, small_data)
measure_graph_size(train, big_data)

measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: small_data, (tf.int32, tf.int32)))
measure_graph_size(train, tf.data.Dataset.from_generator(
    lambda: big_data, (tf.int32, tf.int32)))
W0629 01:08:57.038732 140490414335744 deprecation.py:323] From /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/data/ops/dataset_ops.py:505: py_func (from tensorflow.python.ops.script_ops) is deprecated and will be removed in a future version.
Instructions for updating:
tf.py_func is deprecated in TF V2. Instead, there are two
    options available in V2.
    - tf.py_function takes a python function which manipulates tf eager
    tensors instead of numpy arrays. It's easy to convert a tf eager tensor to
    an ndarray (just call tensor.numpy()) but having access to eager tensors
    means <a href="../../../versions/r2.0/api_docs/python/tf/py_function"><code>tf.py_function</code></a>s can use accelerators such as GPUs as well as
    being differentiable using a gradient tape.
    - tf.numpy_function maintains the semantics of the deprecated tf.py_func
    (it is not differentiable, and manipulates numpy arrays). It drops the
    stateful argument making all functions stateful.
    

train([(1, 1), (1, 1)]) contains 8 nodes in its graph
train([(1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1), (1, 1)]) contains 32 nodes in its graph
train(<DatasetV1Adapter shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 4 nodes in its graph
train(<DatasetV1Adapter shapes: (<unknown>, <unknown>), types: (tf.int32, tf.int32)>) contains 4 nodes in its graph

Python/Numpy データを Dataset にラッピングするとき、tf.data.Dataset.from_generator versus tf.data.Dataset.from_tensors に留意してください。前者はデータを Python で保持して tf.py_function を通してそれを取得します、これはパフォーマンスと密接な関係を持つかもしれず、その一方で後者はデータのコピーをグラフの一つの巨大な tf.constant() ノードとして束ねます、これはメモリとの密接な関係を持つかもしれません。

TFRecordDataset/CsvDataset/etc. を通してファイルからデータを読むのがデータを消費する最も効果的な方法です、何故ならば TensorFlow 自身がデータの非同期ロードと先読みを管理できるからです、Python を巻き込まなければならないことなしに。

 

自動制御依存性

functions のプログラミングモデルとしての非常に魅力的な特質は、一般的なデータフロー・グラフに渡り、functions がランタイムに何がコードの意図された動作であったかについてより多くの情報を与えられることです。

例えば、同じ変数への複数の読み書きを持つコードを書くとき、データフロー・グラフは演算の元々意図された順序を自然にエンコードしないかもしれません。tf.function では、元の Python コードのステートメントの実行順序を参照して実行順序の曖昧さを解決します。このようにして、tf.function のステートフルな演算の順序は Eager モードのセマンティクスをレプリケートします。

これは手動の制御依存性を追加する必要がないことを意味します ; tf.function は貴方のコードが正しく実行されるために必要で十分な制御依存性の最小セットを追加するために十分にスマートです。

# Automatic control dependencies

a = tf.Variable(1.0)
b = tf.Variable(2.0)

@tf.function
def f(x, y):
  a.assign(y * b)
  b.assign_add(x * a)
  return a + b

f(1.0, 2.0)  # 10.0
<tf.Tensor: id=460, shape=(), dtype=float32, numpy=10.0>

 

Variables

tf.function で変数作成と利用を非常に容易にするためにコードの意図された実行順序を活用するのと同じアイデアを利用できます。けれども一つの非常に重要な警告があります、それは variable では eager モードと graph モードで異なる動作をするコードを書くことが可能であることです。

特に、これは各呼び出しで新しい Variable を作成するときに発生します。tracing セマンティクスにより、tf.function は各呼び出しで同じ変数を再利用しますが、eager モードは各呼び出しで新しい変数を作成します。この間違いから守るために、危険な変数作成動作を検出する場合には tf.function はエラーを上げます。

@tf.function
def f(x):
  v = tf.Variable(1.0)
  v.assign_add(x)
  return v

with assert_raises(ValueError):
  f(1.0)
Caught expected exception 
  <class 'ValueError'>: in converted code:

    <ipython-input-17-73e410646579>:3 f  *
        v = tf.Variable(1.0)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/variables.py:262 __call__
        return cls._variable_v2_call(*args, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/variables.py:256 _variable_v2_call
        shape=shape)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/variables.py:60 getter
        return captured_getter(captured_previous, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/eager/def_function.py:364 invalid_creator_scope
        "tf.function-decorated function tried to create "

    ValueError: tf.function-decorated function tried to create variables on non-first call.
# Non-ambiguous code is ok though

v = tf.Variable(1.0)

@tf.function
def f(x):
  return v.assign_add(x)

print(f(1.0))  # 2.0
print(f(2.0))  # 4.0
tf.Tensor(2.0, shape=(), dtype=float32)
tf.Tensor(4.0, shape=(), dtype=float32)
# You can also create variables inside a tf.function as long as we can prove
# that those variables are created only the first time the function is executed.

class C: pass
obj = C(); obj.v = None

@tf.function
def g(x):
  if obj.v is None:
    obj.v = tf.Variable(1.0)
  return obj.v.assign_add(x)

print(g(1.0))  # 2.0
print(g(2.0))  # 4.0
tf.Tensor(2.0, shape=(), dtype=float32)
tf.Tensor(4.0, shape=(), dtype=float32)
# Variable initializers can depend on function arguments and on values of other
# variables. We can figure out the right initialization order using the same
# method we use to generate control dependencies.

state = []
@tf.function
def fn(x):
  if not state:
    state.append(tf.Variable(2.0 * x))
    state.append(tf.Variable(state[0] * 3.0))
  return state[0] * x * state[1]

print(fn(tf.constant(1.0)))
print(fn(tf.constant(3.0)))
tf.Tensor(12.0, shape=(), dtype=float32)
tf.Tensor(36.0, shape=(), dtype=float32)

 

AutoGraph を使用する

autograph ライブラリは tf.function と完全に統合され、そしてそれはグラフで動的に実行するための Tensor に依拠する条件節とループを書き換えます。

tf.condtf.while_loop は tf.function とともに動作し続けますが、制御フローを持つコードはしばしば命令型スタイルで書かれるとき書いて理解することが容易です。

# Simple loop

@tf.function
def f(x):
  while tf.reduce_sum(x) > 1:
    tf.print(x)
    x = tf.tanh(x)
  return x

f(tf.random.uniform([5]))
[0.604465961 0.235631824 0.292372584 0.0933123827 0.8393749]
[0.540219843 0.231365576 0.284317046 0.093042478 0.685477853]
[0.493154347 0.227323771 0.276895881 0.0927749 0.595069051]
[0.456716418 0.223487303 0.270029694 0.0925096273 0.533531547]
[0.427404225 0.219839349 0.263652474 0.0922466218 0.488075942]
[0.403149694 0.21636492 0.257708609 0.0919858441 0.452688]
[0.382640719 0.213050663 0.252151072 0.0917272717 0.424106032]
[0.364998549 0.209884614 0.2469396 0.0914708674 0.40038386]
[0.349609256 0.206856042 0.242039666 0.091216594 0.380277365]
[0.336028934 0.203955248 0.237421349 0.0909644365 0.362948328]
[0.323927581 0.201173469 0.233058617 0.0907143652 0.347808361]
[0.313053906 0.198502809 0.228928685 0.0904663429 0.334430456]
[0.303212792 0.195936009 0.225011513 0.0902203396 0.322496086]
[0.294249982 0.193466514 0.221289411 0.0899763331 0.311762124]
[0.286041766 0.191088319 0.21774666 0.0897343 0.302039325]
[0.278487593 0.188795939 0.214369327 0.0894942135 0.293177754]
[0.27150473 0.186584324 0.21114485 0.0892560408 0.285056978]
[0.265024424 0.184448823 0.208062038 0.0890197605 0.27757892]
[0.258989 0.182385176 0.205110788 0.0887853503 0.270662814]

<tf.Tensor: id=727, shape=(5,), dtype=float32, numpy=
array([0.25334966, 0.1803894 , 0.202282  , 0.08855279, 0.26424146],
      dtype=float32)>
# If you're curious you can inspect the code autograph generates.
# It feels like reading assembly language, though.

def f(x):
  while tf.reduce_sum(x) > 1:
    tf.print(x)
    x = tf.tanh(x)
  return x

print(tf.autograph.to_code(f))
def tf__f(x):
  do_return = False
  retval_ = ag__.UndefinedReturnValue()

  def loop_test(x_1):
    return ag__.converted_call('reduce_sum', tf, ag__.ConversionOptions(recursive=True, force_conversion=False, optional_features=(), internal_convert_user_code=True), (x_1,), None) > 1

  def loop_body(x_1):
    ag__.converted_call('print', tf, ag__.ConversionOptions(recursive=True, force_conversion=False, optional_features=(), internal_convert_user_code=True), (x_1,), None)
    x_1 = ag__.converted_call('tanh', tf, ag__.ConversionOptions(recursive=True, force_conversion=False, optional_features=(), internal_convert_user_code=True), (x_1,), None)
    return x_1,
  x, = ag__.while_stmt(loop_test, loop_body, (x,))
  do_return = True
  retval_ = x
  cond = ag__.is_undefined_return(retval_)

  def get_state():
    return ()

  def set_state(_):
    pass

  def if_true():
    retval_ = None
    return retval_

  def if_false():
    return retval_
  retval_ = ag__.if_stmt(cond, if_true, if_false, get_state, set_state)
  return retval_

 

AutoGraph: 条件節

AutoGraph は if ステートメントを等値の tf.cond 呼び出しに変換します。

この置き換えは条件が Tensor である場合に行われます。さもなければ、条件節は tracing の間に実行されます。

def test_tf_cond(f, *args):
  g = f.get_concrete_function(*args).graph
  if any(node.name == 'cond' for node in g.as_graph_def().node):
    print("{}({}) uses tf.cond.".format(
        f.__name__, ', '.join(map(str, args))))
  else:
    print("{}({}) executes normally.".format(
        f.__name__, ', '.join(map(str, args))))
@tf.function
def hyperparam_cond(x, training=True):
  if training:
    x = tf.nn.dropout(x, rate=0.5)
  return x

@tf.function
def maybe_tensor_cond(x):
  if x < 0:
    x = -x
  return x

test_tf_cond(hyperparam_cond, tf.ones([1], dtype=tf.float32))
test_tf_cond(maybe_tensor_cond, tf.constant(-1))
test_tf_cond(maybe_tensor_cond, -1)
hyperparam_cond(tf.Tensor([1.], shape=(1,), dtype=float32)) executes normally.
maybe_tensor_cond(tf.Tensor(-1, shape=(), dtype=int32)) uses tf.cond.
maybe_tensor_cond(-1) executes normally.

tf.cond は多くの微妙な点を持ちます。- それは条件節の両側を trace し、それから条件に依拠して、ランタイムに適切な分岐を選択することにより動作します。両側の trace は Python コードの予期せぬ実行という結果になるかもしれません - 一つの分岐が downstream で使用される tensor を作成する場合、他の分岐もまた tensor を作成しなければならないことを必要とします。

@tf.function
def f():
  x = tf.constant(0)
  if tf.constant(True):
    x = x + 1
    print("Tracing `then` branch")
  else:
    x = x - 1
    print("Tracing `else` branch")
  return x

f()
Tracing `then` branch
Tracing `else` branch

<tf.Tensor: id=802, shape=(), dtype=int32, numpy=1>
@tf.function
def f():
  if tf.constant(True):
    x = tf.ones([3, 3])
  return x

# Throws an error because both branches need to define `x`.
with assert_raises(ValueError):
  f()
Caught expected exception 
  <class 'ValueError'>: in converted code:

    <ipython-input-26-3ae6a92ff50d>:3 f  *
        if tf.constant(True):
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:439 if_stmt
        return tf_if_stmt(cond, body, orelse, get_state, set_state)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:456 tf_if_stmt
        outputs, final_state = control_flow_ops.cond(cond, body, orelse)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/util/deprecation.py:507 new_func
        return func(*args, **kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/control_flow_ops.py:1147 cond
        return cond_v2.cond_v2(pred, true_fn, false_fn, name)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/cond_v2.py:86 cond_v2
        op_return_value=pred)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/framework/func_graph.py:716 func_graph_from_py_func
        func_outputs = python_func(*func_args, **func_kwargs)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:486 wrapper
        outputs = func()
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:512 wrapper
        tuple(s.symbol_name for s in undefined)))

    ValueError: The following symbols must also be initialized in the else branch: ('x',). Alternatively, you may initialize them before the if statement.

 

AutoGraph とループ

AutoGraph はループを変換するための 2, 3 の単純なルールを持ちます。

  • for: iterable が tensor である場合に変換します。
  • while: while 条件が tensor に依拠する場合に変換されます。

ループが変換される場合、それは tf.while_loop で動的に変換され、あるいは "for x in tf.data.Dataset" の特別なケースでは、tf.data.Dataset.reduce に変換されます。

ループが変換されない場合には、それは静的に展開されます。

def test_dynamically_unrolled(f, *args):
  g = f.get_concrete_function(*args).graph
  if any(node.name == 'while' for node in g.as_graph_def().node):
    print("{}({}) uses tf.while_loop.".format(
        f.__name__, ', '.join(map(str, args))))
  elif any(node.name == 'ReduceDataset' for node in g.as_graph_def().node):
    print("{}({}) uses tf.data.Dataset.reduce.".format(
        f.__name__, ', '.join(map(str, args))))
  else:
    print("{}({}) gets unrolled.".format(
        f.__name__, ', '.join(map(str, args))))
@tf.function
def for_in_range():
  x = 0
  for i in range(5):
    x += i
  return x

@tf.function
def for_in_tfrange():
  x = tf.constant(0, dtype=tf.int32)
  for i in tf.range(5):
    x += i
  return x

@tf.function
def for_in_tfdataset():
  x = tf.constant(0, dtype=tf.int64)
  for i in tf.data.Dataset.range(5):
    x += i
  return x

test_dynamically_unrolled(for_in_range)
test_dynamically_unrolled(for_in_tfrange)
test_dynamically_unrolled(for_in_tfdataset)
for_in_range() gets unrolled.
for_in_tfrange() uses tf.while_loop.
for_in_tfdataset() uses tf.data.Dataset.reduce.
@tf.function
def while_py_cond():
  x = 5
  while x > 0:
    x -= 1
  return x

@tf.function
def while_tf_cond():
  x = tf.constant(5)
  while x > 0:
    x -= 1
  return x

test_dynamically_unrolled(while_py_cond)
test_dynamically_unrolled(while_tf_cond)
while_py_cond() gets unrolled.
while_tf_cond() uses tf.while_loop.

tensor に依拠する break か早期の return 節を持つのであれば、top-level 条件や iterable もまた tensor であるべきです。

@tf.function
def buggy_while_py_true_tf_break(x):
  while True:
    if tf.equal(x, 0):
      break
    x -= 1
  return x

@tf.function
def while_tf_true_tf_break(x):
  while tf.constant(True):
    if tf.equal(x, 0):
      break
    x -= 1
  return x

with assert_raises(TypeError):
  test_dynamically_unrolled(buggy_while_py_true_tf_break, 5)
test_dynamically_unrolled(while_tf_true_tf_break, 5)

@tf.function
def buggy_py_for_tf_break():
  x = 0
  for i in range(5):
    if tf.equal(i, 3):
      break
    x += i
  return x

@tf.function
def tf_for_tf_break():
  x = 0
  for i in tf.range(5):
    if tf.equal(i, 3):
      break
    x += i
  return x

with assert_raises(TypeError):
  test_dynamically_unrolled(buggy_py_for_tf_break)
test_dynamically_unrolled(tf_for_tf_break)
Caught expected exception 
  <class 'TypeError'>: in converted code:

    <ipython-input-30-220fba6e1df8>:3 buggy_while_py_true_tf_break  *
        while True:
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:313 while_stmt
        return _py_while_stmt(test, body, init_state, opts)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:401 _py_while_stmt
        while test(*state):
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/framework/ops.py:698 __bool__
        raise TypeError("Using a `tf.Tensor` as a Python `bool` is not allowed. "

    TypeError: Using a `tf.Tensor` as a Python `bool` is not allowed. Use `if t is not None:` instead of `if t:` to test if a tensor is defined, and use TensorFlow ops such as tf.cond to execute subgraphs conditioned on the value of a tensor.

while_tf_true_tf_break(5) uses tf.while_loop.
Caught expected exception 
  <class 'TypeError'>: in converted code:

    <ipython-input-30-220fba6e1df8>:24 buggy_py_for_tf_break  *
        for i in range(5):
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:110 for_stmt
        return _py_for_stmt(iter_, extra_test, body, init_state)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:117 _py_for_stmt
        if extra_test is not None and not extra_test(*state):
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/framework/ops.py:698 __bool__
        raise TypeError("Using a `tf.Tensor` as a Python `bool` is not allowed. "

    TypeError: Using a `tf.Tensor` as a Python `bool` is not allowed. Use `if t is not None:` instead of `if t:` to test if a tensor is defined, and use TensorFlow ops such as tf.cond to execute subgraphs conditioned on the value of a tensor.

tf_for_tf_break() uses tf.while_loop.

動的に展開されたループからの結果を累積するために、tf.TensorArray を使用することを望むでしょう。

batch_size = 2
seq_len = 3
feature_size = 4

def rnn_step(inp, state):
  return inp + state

@tf.function
def dynamic_rnn(rnn_step, input_data, initial_state):
  # [batch, time, features] -> [time, batch, features]
  input_data = tf.transpose(input_data, [1, 0, 2])
  max_seq_len = input_data.shape[0]

  states = tf.TensorArray(tf.float32, size=max_seq_len)
  state = initial_state
  for i in tf.range(max_seq_len):
    state = rnn_step(input_data[i], state)
    states = states.write(i, state)
  return tf.transpose(states.stack(), [1, 0, 2])
  
dynamic_rnn(rnn_step,
            tf.random.uniform([batch_size, seq_len, feature_size]),
            tf.zeros([batch_size, feature_size]))
<tf.Tensor: id=1307, shape=(2, 3, 4), dtype=float32, numpy=
array([[[0.2641269 , 0.32148373, 0.27760386, 0.871022  ],
        [0.36447   , 0.82768893, 1.1970165 , 1.2398282 ],
        [1.1957974 , 1.242557  , 1.2163385 , 1.7522211 ]],

       [[0.11496973, 0.8242916 , 0.5828695 , 0.01092482],
        [0.64337647, 1.7383248 , 1.182523  , 0.94805145],
        [1.2105927 , 1.9715753 , 1.2999045 , 1.6146696 ]]], dtype=float32)>

tf.cond と同様に、tf.while_loop もまた微妙な点を伴います。- ループは 0 回の実行が可能ですから、while_loop の downstream で使用される総ての tensor は上のループで初期化されなければなりません。- 総てのループ変数の shape/dtype は各 iteration と一環していなければなりません。

@tf.function
def buggy_loop_var_uninitialized():
  for i in tf.range(3):
    x = i
  return x

@tf.function
def f():
  x = tf.constant(0)
  for i in tf.range(3):
    x = i
  return x

with assert_raises(ValueError):
  buggy_loop_var_uninitialized()
f()
Caught expected exception 
  <class 'ValueError'>: in converted code:

    <ipython-input-32-4012abce963a>:3 buggy_loop_var_uninitialized  *
        for i in tf.range(3):
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:95 for_stmt
        return _known_len_tf_for_stmt(iter_, extra_test, body, init_state)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:125 _known_len_tf_for_stmt
        _disallow_undefs_into_loop(*init_state)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:50 _disallow_undefs_into_loop
        tuple(s.symbol_name for s in undefined)))

    ValueError: TensorFlow requires that the following symbols must be defined before the loop: ('x',)


<tf.Tensor: id=1371, shape=(), dtype=int32, numpy=2>
@tf.function
def buggy_loop_type_changes():
  x = tf.constant(0, dtype=tf.float32)
  for i in tf.range(3): # Yields tensors of type tf.int32...
    x = i
  return x

with assert_raises(tf.errors.InvalidArgumentError):
  buggy_loop_type_changes()
Caught expected exception 
  <class 'tensorflow.python.framework.errors_impl.InvalidArgumentError'>: Input 1 of node while/merge/_10 was passed int32 from while/next_iteration/_28:0 incompatible with expected float. [Op:__inference_buggy_loop_type_changes_1428]
@tf.function
def buggy_concat():
  x = tf.ones([0, 10])
  for i in tf.range(5):
    x = tf.concat([x, tf.ones([1, 10])], axis=0)
  return x

with assert_raises(ValueError):
  buggy_concat()
  
@tf.function
def concat_with_padding():
  x = tf.zeros([5, 10])
  for i in tf.range(5):
    x = tf.concat([x[:i], tf.ones([1, 10]), tf.zeros([4-i, 10])], axis=0)
    x.set_shape([5, 10])
  return x

concat_with_padding()
Caught expected exception 
  <class 'ValueError'>: in converted code:

    <ipython-input-34-d212bdeeeb5e>:4 buggy_concat  *
        for i in tf.range(5):
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:95 for_stmt
        return _known_len_tf_for_stmt(iter_, extra_test, body, init_state)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:156 _known_len_tf_for_stmt
        opts=dict(maximum_iterations=n))
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/autograph/operators/control_flow.py:327 _tf_while_stmt
        retval = control_flow_ops.while_loop(test, body, init_state, **opts)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/control_flow_ops.py:2646 while_loop
        return_same_structure=return_same_structure)
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/while_v2.py:213 while_loop
        len_orig_loop_vars], expand_composites=True))
    /tmpfs/src/tf_docs_env/lib/python3.5/site-packages/tensorflow/python/ops/while_v2.py:869 _check_shapes_compat
        "specify a less-specific shape." % (input_t.name, shape, t.shape))

    ValueError: Input tensor 'ones:0' enters the loop with shape (0, 10), but has shape (1, 10) after one iteration. To allow the shape to vary across iterations, use the `shape_invariants` argument of tf.while_loop to specify a less-specific shape.


<tf.Tensor: id=1549, shape=(5, 10), dtype=float32, numpy=
array([[1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.],
       [1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]], dtype=float32)>
 

以上






AI導入支援 #2 ウェビナー

スモールスタートを可能としたAI導入支援   Vol.2
[無料 WEB セミナー] [詳細]
「画像認識 AI PoC スターターパック」の紹介
既に AI 技術を実ビジネスで活用し、成果を上げている日本企業も多く存在しており、競争優位なビジネスを展開しております。
しかしながら AI を導入したくとも PoC (概念実証) だけでも高額な費用がかかり取組めていない企業も少なくないようです。A I導入時には欠かせない PoC を手軽にしかも短期間で認知度を確認可能とするサービの紹介と共に、AI 技術の特性と具体的な導入プロセスに加え運用時のポイントについても解説いたします。
日時:2021年10月13日(水)
会場:WEBセミナー
共催:クラスキャット、日本FLOW(株)
後援:働き方改革推進コンソーシアム
参加費: 無料 (事前登録制)
人工知能開発支援
◆ クラスキャットは 人工知能研究開発支援 サービスを提供しています :
  • テクニカルコンサルティングサービス
  • 実証実験 (プロトタイプ構築)
  • アプリケーションへの実装
  • 人工知能研修サービス
◆ お問合せ先 ◆
(株)クラスキャット
セールス・インフォメーション
E-Mail:sales-info@classcat.com