TensorFlow 2.0 Beta : ガイド : TensorFlow 2.0 の tf.function と AutoGraph (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 06/09/2019
* 本ページは、TensorFlow の本家サイトの TF 2.0 Beta の以下のページを翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
ガイド : TensorFlow 2.0 の tf.function と AutoGraph
TF 2.0 は eager execution の容易さと TF 1.0 のパワーを一つにまとめています。この融合の中心には tf.function があります、これは Python シンタクスのサブセットを可搬な、高パフォーマンスの TensorFlow グラフに変換することを可能にします。
tf.function のクールな新しい特徴は AutoGraph です、これは貴方に自然な Python シンタクスを使用してグラフコードを書かせます。AutoGraph で使用できる Python 特徴のリストについては、AutoGraph Capabilities and Limitations を見てください。tf.function についてのより多くの詳細は、RFC TF 2.0: Functions, not Sessions を見てください。AutoGraph についてのより詳細は、tf.autograph を見てください。
このチュートリアルは tf.function と AutoGraph の基本的な特徴を貴方にウォークスルーしてもらいます。
セットアップ
TensorFlow 2.0 Preview Nightly をインポートして TF 2.0 モードを有効にします :
from __future__ import absolute_import, division, print_function, unicode_literals import numpy as np
!pip install -q tensorflow==2.0.0-beta0 import tensorflow as tf
tf.function デコレータ
関数を tf.function でアノテートするとき、依然として任意の他の関数のようにそれを呼び出すことができます。しかしそれはグラフ内にコンパイルされ、これは高速な実行の恩恵を得ることを意味し、GPU か TPU で実行されるか、SavedModel にエクスポートされます。
@tf.function def simple_nn_layer(x, y): return tf.nn.relu(tf.matmul(x, y)) x = tf.random.uniform((3, 3)) y = tf.random.uniform((3, 3)) simple_nn_layer(x, y)
<tf.Tensor: id=23, shape=(3, 3), dtype=float32, numpy=
array([[0.28093395, 0.38601664, 0.1492369 ],
       [0.18872553, 0.49841696, 0.21768065],
       [0.5309121 , 0.94758576, 0.38262835]], dtype=float32)>
アノテーションの結果を調べればそれが TensorFlow ランタイムとの総ての相互作用を扱う特別な callable であることが見て取れるでしょう。
simple_nn_layer
<tensorflow.python.eager.def_function.Function at 0x7efc950bfef0>
貴方のコードが複数の関数を使用する場合、それら総てをアノテートする必要はありません – アノテートされた関数から呼び出された任意の関数もまたグラフモードで動作します。
def linear_layer(x): return 2 * x + 1 @tf.function def deep_net(x): return tf.nn.relu(linear_layer(x)) deep_net(tf.constant((1, 2, 3)))
<tf.Tensor: id=36, shape=(3,), dtype=int32, numpy=array([3, 5, 7], dtype=int32)>
多くの小さい ops を持つグラフに対しては、Functions は eager コードよりも高速であり得ます。しかし少数の高価な ops を持つグラフに対しては、多量のスピードアップを見ないかもしれません。
import timeit
conv_layer = tf.keras.layers.Conv2D(100, 3)
@tf.function
def conv_fn(image):
  return conv_layer(image)
image = tf.zeros([1, 200, 200, 100])
# warm up
conv_layer(image); conv_fn(image)
print("Eager conv:", timeit.timeit(lambda: conv_layer(image), number=10))
print("Function conv:", timeit.timeit(lambda: conv_fn(image), number=10))
print("Note how there's not much difference in performance for convolutions")
Eager conv: 0.2274835030000304 Function conv: 0.1842926219999299 Note how there's not much difference in performance for convolutions
lstm_cell = tf.keras.layers.LSTMCell(10)
@tf.function
def lstm_fn(input, state):
  return lstm_cell(input, state)
input = tf.zeros([10, 10])
state = [tf.zeros([10, 10])] * 2
# warm up
lstm_cell(input, state); lstm_fn(input, state)
print("eager lstm:", timeit.timeit(lambda: lstm_cell(input, state), number=10))
print("function lstm:", timeit.timeit(lambda: lstm_fn(input, state), number=10))
eager lstm: 0.005612198999983775 function lstm: 0.004945082999938677
Python 制御フローを使用する
tf.function 内でデータ依存制御フローを使用しているとき、Python 制御フロー・ステートメントを使用することができてそして AutoGraph はそれらを適切な TensorFlow ops に変換します。例えば、if ステートメントはそれらが Tensor に依存する場合 tf.cond() に変換されるでしょう。
下の例では、x は Tensor ですがステートメントは期待通りに動作します :
@tf.function
def square_if_positive(x):
  if x > 0:
    x = x * x
  else:
    x = 0
  return x
print('square_if_positive(2) = {}'.format(square_if_positive(tf.constant(2))))
print('square_if_positive(-2) = {}'.format(square_if_positive(tf.constant(-2))))
square_if_positive(2) = 4 square_if_positive(-2) = 0
 
Note: 前の例はスカラー値を伴う単純な条件節を使用しています。現実世界のコードでは典型的にはバッチ処理が使用されます。
AutoGraph は while, for, if, break, continue と return のような一般的な Python ステートメントをネスティングのためのサポートとともに、サポートします。それは while と if ステートメントの条件で Tensor 式を使用したり、for ループで Tensor に渡り iterate できることを意味します。
@tf.function
def sum_even(items):
  s = 0
  for c in items:
    if c % 2 > 0:
      continue
    s += c
  return s
sum_even(tf.constant([10, 12, 15, 20]))
<tf.Tensor: id=606, shape=(), dtype=int32, numpy=42>
AutoGraph はまた上級ユーザのために低位 API も提供します。例えば生成されたコードを見るためにそれを使用できます。
print(tf.autograph.to_code(sum_even.python_function))
def tf__sum_even(items):
  do_return = False
  retval_ = ag__.UndefinedReturnValue()
  s = 0
  def loop_body(loop_vars, s_2):
    c = loop_vars
    continue_ = False
    cond = c % 2 > 0
    def get_state():
      return ()
    def set_state(_):
      pass
    def if_true():
      continue_ = True
      return continue_
    def if_false():
      return continue_
    continue_ = ag__.if_stmt(cond, if_true, if_false, get_state, set_state)
    cond_1 = ag__.not_(continue_)
    def get_state_1():
      return ()
    def set_state_1(_):
      pass
    def if_true_1():
      s_1, = s_2,
      s_1 += c
      return s_1
    def if_false_1():
      return s_2
    s_2 = ag__.if_stmt(cond_1, if_true_1, if_false_1, get_state_1, set_state_1)
    return s_2,
  s, = ag__.for_stmt(items, None, loop_body, (s,))
  do_return = True
  retval_ = s
  cond_2 = ag__.is_undefined_return(retval_)
  def get_state_2():
    return ()
  def set_state_2(_):
    pass
  def if_true_2():
    retval_ = None
    return retval_
  def if_false_2():
    return retval_
  retval_ = ag__.if_stmt(cond_2, if_true_2, if_false_2, get_state_2, set_state_2)
  return retval_
ここにより複雑な制御フローのサンプルがあります :
@tf.function
def fizzbuzz(n):
  msg = tf.constant('')
  for i in tf.range(n):
    if tf.equal(i % 3, 0):
      tf.print('Fizz')
    elif tf.equal(i % 5, 0):
      tf.print('Buzz')
    else:
      tf.print(i)
fizzbuzz(tf.constant(15))
Fizz 1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14
Keras と AutoGraph
tf.function はオブジェクトのメソッドでもまた使用できます。例えば、典型的にはモデルの call 関数をアノテートすることで貴方のカスタム Keras モデルをデコレートできます。より多くの情報については、tf.keras を見てください。
class CustomModel(tf.keras.models.Model):
  @tf.function
  def call(self, input_data):
    if tf.reduce_mean(input_data) > 0:
      return input_data
    else:
      return input_data // 2
model = CustomModel()
model(tf.constant([-2, -4]))
<tf.Tensor: id=723, shape=(2,), dtype=int32, numpy=array([-1, -2], dtype=int32)>
副作用
ちょうど eager モードでのように、通常は tf.function 内で tf.assign や tf.print のような、副作用を持つ演算を使用することができて、それはそれらが順番に実行されることを確実にするために必要な制御依存性を挿入します。
v = tf.Variable(5)
@tf.function
def find_next_odd():
  v.assign(v + 1)
  if tf.equal(v % 2, 0):
    v.assign(v + 1)
find_next_odd()
v
<tf.Variable 'Variable:0' shape=() dtype=int32, numpy=7>
例題: 単純なモデルを訓練する
AutoGraph はまたより多くの計算を TensorFlow 内に移すことを可能にします。例えば、訓練ループはちょうど制御フローですので、それは実際には TensorFlow に持ち込むことができます。
データをダウンロードする
def prepare_mnist_features_and_labels(x, y): x = tf.cast(x, tf.float32) / 255.0 y = tf.cast(y, tf.int64) return x, y def mnist_dataset(): (x, y), _ = tf.keras.datasets.mnist.load_data() ds = tf.data.Dataset.from_tensor_slices((x, y)) ds = ds.map(prepare_mnist_features_and_labels) ds = ds.take(20000).shuffle(20000).batch(100) return ds train_dataset = mnist_dataset()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz 11493376/11490434 [==============================] - 0s 0us/step
モデルを定義する
model = tf.keras.Sequential((
    tf.keras.layers.Reshape(target_shape=(28 * 28,), input_shape=(28, 28)),
    tf.keras.layers.Dense(100, activation='relu'),
    tf.keras.layers.Dense(100, activation='relu'),
    tf.keras.layers.Dense(10)))
model.build()
optimizer = tf.keras.optimizers.Adam()
訓練ループを定義する
compute_loss = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True)
compute_accuracy = tf.keras.metrics.SparseCategoricalAccuracy()
def train_one_step(model, optimizer, x, y):
  with tf.GradientTape() as tape:
    logits = model(x)
    loss = compute_loss(y, logits)
  grads = tape.gradient(loss, model.trainable_variables)
  optimizer.apply_gradients(zip(grads, model.trainable_variables))
  compute_accuracy(y, logits)
  return loss
@tf.function
def train(model, optimizer):
  train_ds = mnist_dataset()
  step = 0
  loss = 0.0
  accuracy = 0.0
  for x, y in train_ds:
    step += 1
    loss = train_one_step(model, optimizer, x, y)
    if tf.equal(step % 10, 0):
      tf.print('Step', step, ': loss', loss, '; accuracy', compute_accuracy.result())
  return step, loss, accuracy
step, loss, accuracy = train(model, optimizer)
print('Final step', step, ': loss', loss, '; accuracy', compute_accuracy.result())
Step 10 : loss 1.83085191 ; accuracy 0.336 Step 20 : loss 1.03369474 ; accuracy 0.532 Step 30 : loss 0.636070907 ; accuracy 0.627 Step 40 : loss 0.49779284 ; accuracy 0.6745 Step 50 : loss 0.485161096 ; accuracy 0.7116 Step 60 : loss 0.574727654 ; accuracy 0.7365 Step 70 : loss 0.307435066 ; accuracy 0.756714284 Step 80 : loss 0.495543599 ; accuracy 0.7725 Step 90 : loss 0.34826988 ; accuracy 0.785 Step 100 : loss 0.334337503 ; accuracy 0.7955 Step 110 : loss 0.290210605 ; accuracy 0.805090904 Step 120 : loss 0.320541888 ; accuracy 0.813166678 Step 130 : loss 0.307672709 ; accuracy 0.821230769 Step 140 : loss 0.235306799 ; accuracy 0.826928556 Step 150 : loss 0.252060503 ; accuracy 0.833266675 Step 160 : loss 0.177218169 ; accuracy 0.8395 Step 170 : loss 0.292944938 ; accuracy 0.844470561 Step 180 : loss 0.249169499 ; accuracy 0.848555565 Step 190 : loss 0.43364051 ; accuracy 0.852210522 Step 200 : loss 0.25526756 ; accuracy 0.85565 Final step tf.Tensor(200, shape=(), dtype=int32) : loss tf.Tensor(0.25526756, shape=(), dtype=float32) ; accuracy tf.Tensor(0.85565, shape=(), dtype=float32)
バッチ処理
実際のアプリケーションではバッチ処理はパフォーマンスのために重要です。AutoGraph に変換する最善のコードはそこでは制御フローがバッチレベルで決定されるようなコードです。個々のサンプルレベルで決定を行なう場合には、パフォーマンスを維持するために batch API を利用してみてください。
例えば、Python で次のようなコードを持つ場合 :
def square_if_positive(x): return [i ** 2 if i > 0 else i for i in x] square_if_positive(range(-5, 5))
[-5, -4, -3, -2, -1, 0, 1, 4, 9, 16]
TensorFlow で次のようにそれを書く誘惑にかられるかもしれません (そしてこれは動作します!) :
@tf.function
def square_if_positive_naive(x):
  result = tf.TensorArray(tf.int32, size=x.shape[0])
  for i in tf.range(x.shape[0]):
    if x[i] > 0:
      result = result.write(i, x[i] ** 2)
    else:
      result = result.write(i, x[i])
  return result.stack()
square_if_positive_naive(tf.range(-5, 5))
<tf.Tensor: id=1834, shape=(10,), dtype=int32, numpy=array([-5, -4, -3, -2, -1, 0, 1, 4, 9, 16], dtype=int32)>
しかしこの場合、次を書くことができることが判明します :
def square_if_positive_vectorized(x): return tf.where(x > 0, x ** 2, x) square_if_positive_vectorized(tf.range(-5, 5))
<tf.Tensor: id=1844, shape=(10,), dtype=int32, numpy=array([-5, -4, -3, -2, -1, 0, 1, 4, 9, 16], dtype=int32)>
以上