TensorFlow 2.4 : ガイド : 基本 – Eager 実行 (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 12/19/2020
* 本ページは、TensorFlow org サイトの Guide – TensorFlow Basics の以下のページを翻訳した上で
適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、必要な場合には適宜、追加改変しています。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
- お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。
- Windows PC のブラウザからご参加が可能です。スマートデバイスもご利用可能です。
人工知能研究開発支援 | 人工知能研修サービス | テレワーク & オンライン授業を支援 |
PoC(概念実証)を失敗させないための支援 (本支援はセミナーに参加しアンケートに回答した方を対象としています。 |
◆ お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。
株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション |
E-Mail:sales-info@classcat.com ; WebSite: https://www.classcat.com/ |
Facebook: https://www.facebook.com/ClassCatJP/ |
ガイド : 基本 – Eager 実行
TensorFlow の eager 実行は命令型プログラミング環境で、これはグラフを構築することなく演算を直ちに評価します : 演算は後で実行する計算グラフを構築する代わりに具体的な値を返します。これは TensorFlow を始めてモデルをデバッグすることを容易にして、そしてまたそれはボイラープレート (なコード) を削減します。このガイドに沿ってフォローするためには、対話的 python インタープリタで下のコード・サンプルを実行してください。
Eager 実行は研究と実験のための柔軟な機械学習プラットフォームで、以下を提供します :
- 直感的なインターフェイス — 貴方のコードを自然に構造化して Python データ構造を使用します。小さなモデルと小さなデータ上で迅速に反復します。
- より容易なデバッギング — 実行中のモデルを調査して変更をテストするために ops を直接的に呼び出します。即時のエラー報告のために標準的な Python デバッギング・ツールを使用します。
- 自然な制御フロー — グラフ制御フローの代わりに Python 制御フローを使用し、動的モデルの仕様を単純化します。
Eager 実行は殆どの TensorFlow 演算と GPU アクセラレーションをサポートします。
Note: 幾つかのモデルでは eager 実行が有効であると増大したオーバーヘッドを経験するかもしれません。パフォーマンス改良は進行中ですが、問題を見つける場合にはバグをファイルしてベンチマークを共有してください。
セットアップと基本的な使用方法
import os import tensorflow as tf import cProfile
TensorFlow 2.0 では、eager 実行はデフォルトで有効にされています。
tf.executing_eagerly()
True
今では貴方は TensorFlow 演算を実行できて結果は直ちに返ります :
x = [[2.]] m = tf.matmul(x, x) print("hello, {}".format(m))
hello, [[4.]]
eager 実行を有効にすると TensorFlow 演算がどのように動作するかを変更します — 今ではそれらは即時に評価してそれらの値を Python に返します。tf.Tensor オブジェクトは計算グラフのノードへのシンボリックなハンドルの代わりに具体的な値を参照します。セッション内で構築して後で実行する計算グラフはありませんので、print() やデバッガーを使用して結果を調査することは容易です。tensor 値の評価、プリントそして確認は勾配を計算するためのフローを壊しません。
Eager 実行は NumPy と共に素晴らしく動作します。NumPy 演算は tf.Tensor 引数を受け取ります。TensorFlow tf.math 演算は Python オブジェクトと NumPy 配列を tf.Tensor オブジェクトに変換します。tf.Tensor.numpy メソッドはオブジェクトの値を NumPy ndarray として返します。
a = tf.constant([[1, 2], [3, 4]]) print(a)
tf.Tensor( [[1 2] [3 4]], shape=(2, 2), dtype=int32)
# Broadcasting support b = tf.add(a, 1) print(b)
tf.Tensor( [[2 3] [4 5]], shape=(2, 2), dtype=int32)
# Operator overloading is supported print(a * b)
tf.Tensor( [[ 2 6] [12 20]], shape=(2, 2), dtype=int32)
# Use NumPy values import numpy as np c = np.multiply(a, b) print(c)
[[ 2 6] [12 20]]
# Obtain numpy value from a tensor: print(a.numpy()) # => [[1 2] # [3 4]]
[[1 2] [3 4]]
動的制御フロー
eager 実行の主要なメリットはモデルが実行されている間にホスト言語の総ての機能が利用可能であることです。そのため例えば、fizzbuzz を書くことが容易です :
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
fizzbuzz(15)
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz
これは tensor 値に依拠する条件節を持ちこれらの値を実行時にプリントします。
Eager 訓練
勾配を計算する
自動微分 はニューラルネットワークを訓練するための バックプロパゲーション のような機械学習アルゴリズムを実装するために有用です。eager 実行の間は、後で勾配を計算するための演算を追跡するために tf.GradientTape を利用します。
eager で訓練する、そして/あるいは勾配を計算するために tf.GradientTape を使用できます。それは複雑な訓練ループのために特に有用です。
異なる演算が各呼び出しの間に発生しますので、総ての forward パス演算は「テープ」に記録されます。勾配を計算するために、テープを逆向きに再生してから破棄します。特定の tf.GradientTape は 1 つの勾配を計算するだけです ; 続く呼び出しはランタイム・エラーを投げます。
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)
tf.Tensor([[2.]], shape=(1, 1), dtype=float32)
モデルを訓練する
次のサンプルは標準的な MNIST 手書き数字を分類する多層モデルを作成します。eager 実行環境で訓練可能なグラフを構築する optimizer と層 API を実演します。
# Fetch and format the mnist data (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)
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz 11493376/11490434 [==============================] - 0s 0us/step
# Build the model mnist_model = tf.keras.Sequential([ tf.keras.layers.Conv2D(16,[3,3], activation='relu', input_shape=(None, None, 1)), tf.keras.layers.Conv2D(16,[3,3], activation='relu'), tf.keras.layers.GlobalAveragePooling2D(), tf.keras.layers.Dense(10) ])
訓練なしでさえも、eager 実行ではモデルを呼び出して出力を調べてください :
for images,labels in dataset.take(1): print("Logits: ", mnist_model(images[0:1]).numpy())
Logits: [[ 0.02873135 0.01367911 -0.00195673 0.01703224 0.0010254 0.02827391 0.02236997 0.038897 0.03638642 0.06484333]]
keras モデルが (fit メソッドを使用した) 組み込み訓練ループを持つ一方、時には更なるカスタマイズが必要でしょう。ここに、eager で実装された訓練ループのサンプルがあります :
optimizer = tf.keras.optimizers.Adam() loss_object = tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True) loss_history = []
Note: 条件が成り立つかを確認するために tf.debugging の assert 関数を使用してください。これは eager とグラフ実行で動作します。
def train_step(images, labels): with tf.GradientTape() as tape: logits = mnist_model(images, training=True) # Add asserts to check the shape of the output. tf.debugging.assert_equal(logits.shape, (32, 10)) loss_value = loss_object(labels, logits) loss_history.append(loss_value.numpy().mean()) grads = tape.gradient(loss_value, mnist_model.trainable_variables) optimizer.apply_gradients(zip(grads, mnist_model.trainable_variables))
def train(epochs): for epoch in range(epochs): for (batch, (images, labels)) in enumerate(dataset): train_step(images, labels) print ('Epoch {} finished'.format(epoch))
train(epochs = 3)
Epoch 0 finished Epoch 1 finished Epoch 2 finished
import matplotlib.pyplot as plt plt.plot(loss_history) plt.xlabel('Batch #') plt.ylabel('Loss [entropy]')
Text(0, 0.5, 'Loss [entropy]')
Variable と optimizer
tf.Variable オブジェクトは自動微分をより容易にするために訓練の間にアクセスされるミュータブルな tf.Tensor ライクな値をストアします。
変数のコレクションは、それらの上で動作するメソッドとともに層やモデルにカプセル化できます。詳細は カスタム Keras 層とモデル を見てください。層とモデルの主要な違いはモデルは Model.fit, Model.evaluate と Model.save のようなメソッドを追加することです。
例えば、上の自動微分サンプルは次のように書き換えることができます :
class Linear(tf.keras.Model): def __init__(self): super(Linear, 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
# A toy dataset of points around 3 * x + 2 NUM_EXAMPLES = 2000 training_inputs = tf.random.normal([NUM_EXAMPLES]) noise = tf.random.normal([NUM_EXAMPLES]) training_outputs = training_inputs * 3 + 2 + noise # The loss function to be optimized 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])
Next:
- モデルを作成する。
- モデルパラメータに関する損失関数の導関数。
- 導関数に基づいて変数を更新するためのストラテジー。
model = Linear() optimizer = tf.keras.optimizers.SGD(learning_rate=0.01) print("Initial loss: {:.3f}".format(loss(model, training_inputs, training_outputs))) steps = 300 for i in range(steps): grads = grad(model, training_inputs, training_outputs) optimizer.apply_gradients(zip(grads, [model.W, model.B])) if i % 20 == 0: print("Loss at step {:03d}: {:.3f}".format(i, loss(model, training_inputs, training_outputs)))
Initial loss: 69.507 Loss at step 000: 66.748 Loss at step 020: 29.894 Loss at step 040: 13.695 Loss at step 060: 6.571 Loss at step 080: 3.437 Loss at step 100: 2.058 Loss at step 120: 1.450 Loss at step 140: 1.183 Loss at step 160: 1.065 Loss at step 180: 1.013 Loss at step 200: 0.990 Loss at step 220: 0.979 Loss at step 240: 0.975 Loss at step 260: 0.973 Loss at step 280: 0.972
print("Final loss: {:.3f}".format(loss(model, training_inputs, training_outputs)))
Final loss: 0.972
print("W = {}, B = {}".format(model.W.numpy(), model.B.numpy()))
W = 2.998509407043457, B = 2.0533735752105713
Not: 変数は python オブジェクトへの最後の参照が除去されるまで存続し、それから変数が削除されます。
オブジェクト・ベースのセービング
tf.keras.Model はチェックポイントを容易に作成することを可能にする便利な save_weights メソッドを含みます :
model.save_weights('weights') status = model.load_weights('weights')
tf.train.Checkpoint を使用してこのプロセスを完全に制御できます。
このセクションは 訓練チェックポイントへのガイド の短縮バージョンです。
x = tf.Variable(10.) checkpoint = tf.train.Checkpoint(x=x)
x.assign(2.) # Assign a new value to the variables and save. checkpoint_path = './ckpt/' checkpoint.save('./ckpt/')
'./ckpt/-1'
x.assign(11.) # Change the variable after saving. # Restore values from the checkpoint checkpoint.restore(tf.train.latest_checkpoint(checkpoint_path)) print(x) # => 2.0
<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=2.0>
モデルをセーブしてロードするためには、隠れ変数を必要とすることなく、tf.train.Checkpoint はオブジェクトの内部状態をストアします。モデル、optimizer そしてグローバルステップの状態を記録するためには、それらを tf.train.Checkpoint に渡します :
model = tf.keras.Sequential([ tf.keras.layers.Conv2D(16,[3,3], activation='relu'), tf.keras.layers.GlobalAveragePooling2D(), tf.keras.layers.Dense(10) ]) optimizer = tf.keras.optimizers.Adam(learning_rate=0.001) checkpoint_dir = 'path/to/model_dir' if not os.path.exists(checkpoint_dir): os.makedirs(checkpoint_dir) checkpoint_prefix = os.path.join(checkpoint_dir, "ckpt") root = tf.train.Checkpoint(optimizer=optimizer, model=model) root.save(checkpoint_prefix) root.restore(tf.train.latest_checkpoint(checkpoint_dir))
<tensorflow.python.training.tracking.util.CheckpointLoadStatus at 0x7fe7eb90b080>
Note: 多くの訓練ループで、変数は tf.train.Checkpoint.restore が呼び出された後で作成されます。これらの変数はそれらが作成されるとすぐにリストアされて、チェックポイントが完全にロードされたかを確かなものにするために assertion が利用可能です。詳細は 訓練チェックポイントへのガイド を見てください。
オブジェクト指向メトリクス
tf.keras.metrics はオブジェクトとしてストアされます。新しいデータを callable に渡すことでメトリクスを更新して、そして tf.keras.metrics.result メソッドを使用して結果を取得します、例えば :
m = tf.keras.metrics.Mean("loss") m(0) m(5) m.result() # => 2.5 m([8, 9]) m.result() # => 5.5
<tf.Tensor: shape=(), dtype=float32, numpy=5.5>
要約と TensorBoard
TensorBoard はモデル訓練プロセスを理解し、デバッグして最適化するための可視化ツールです。それはプログラムを実行する間に書かれる要約イベントを利用します。
eager 実行で変数の要約を記録するために tf.summary を使用できます。例えば、100 訓練ステップ毎に一度損失の要約を記録するには :
logdir = "./tb/" writer = tf.summary.create_file_writer(logdir) steps = 1000 with writer.as_default(): # or call writer.set_as_default() before the loop. for i in range(steps): step = i + 1 # Calculate loss with your real train function. loss = 1 - 0.001 * step if step % 100 == 0: tf.summary.scalar('loss', loss, step=step)
ls tb/
events.out.tfevents.1608312504.eb7bff84630e.75.636510.v2
上級者のための自動微分トピック
動的モデル
tf.GradientTape はまた動的モデルでも利用できます。バックトラックする直線探索 (= line search) アルゴリズムのためのこのサンプルは、複雑な制御フローにもかかわらず、(勾配があり微分可能であることを除けば) 普通の NumPy コードのように見えます :
def line_search_step(fn, init_x, rate=1.0): with tf.GradientTape() as tape: # Variables are automatically tracked. # But to calculate a gradient from a tensor, you must `watch` it. 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
カスタム勾配
カスタム勾配は勾配を override するための簡単な方法です。forward 関数内で、入力、出力、または中間結果に関する勾配を定義します。例えば、backward パスで勾配のノルムをクリップするための容易な方法がここにあります :
@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
カスタム勾配は演算のシークエンスのための数値的安定な勾配を提供するために一般に使用されます :
def log1pexp(x): return tf.math.log(1 + tf.exp(x)) def grad_log1pexp(x): with tf.GradientTape() as tape: tape.watch(x) value = log1pexp(x) return tape.gradient(value, x)
# The gradient computation works fine at x = 0. grad_log1pexp(tf.constant(0.)).numpy()
0.5
# However, x = 100 fails because of numerical instability. grad_log1pexp(tf.constant(100.)).numpy()
nan
ここで、log1pexp 関数はカスタム勾配で解析的に単純化できます。下の実装は forward パスの間に計算された tf.exp(x) のための値を再利用しています — 冗長な計算を除去することでそれをより効率的にしています :
@tf.custom_gradient def log1pexp(x): e = tf.exp(x) def grad(dy): return dy * (1 - 1 / (1 + e)) return tf.math.log(1 + e), grad def grad_log1pexp(x): with tf.GradientTape() as tape: tape.watch(x) value = log1pexp(x) return tape.gradient(value, x)
# As before, the gradient computation works fine at x = 0. grad_log1pexp(tf.constant(0.)).numpy()
0.5
# And the gradient computation also works at x = 100. grad_log1pexp(tf.constant(100.)).numpy()
1.0
パフォーマンス
eager 実行の間は計算は GPU へと自動的にオフロードされます。計算がどこで実行されるかについて制御を望む場合にはそれを tf.device(‘/gpu:0’) ブロック (または CPU の同値のもの) で囲むことができます :
import time def measure(x, steps): # TensorFlow initializes a GPU the first time it's used, exclude from timing. tf.matmul(x, x) start = time.time() for i in range(steps): x = tf.matmul(x, x) # tf.matmul can return before completing the matrix multiplication # (e.g., can return after enqueing the operation on a CUDA stream). # The x.numpy() call below will ensure that all enqueued operations # have completed (and will also copy the result to host memory, # so we're including a little more than just the matmul operation # time). _ = 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)) # Run on CPU: with tf.device("/cpu:0"): print("CPU: {} secs".format(measure(tf.random.normal(shape), steps))) # Run on GPU, if available: if tf.config.experimental.list_physical_devices("GPU"): with tf.device("/gpu:0"): print("GPU: {} secs".format(measure(tf.random.normal(shape), steps))) else: print("GPU: not found")
Time to multiply a (1000, 1000) matrix by itself 200 times: CPU: 5.130153179168701 secs GPU: 0.16303229331970215 secs
tf.Tensor オブジェクトはその演算を実行するために異なるデバイスへとコピーできます :
if tf.config.experimental.list_physical_devices("GPU"): x = tf.random.normal([10, 10]) x_gpu0 = x.gpu() x_cpu = x.cpu() _ = tf.matmul(x_cpu, x_cpu) # Runs on CPU _ = tf.matmul(x_gpu0, x_gpu0) # Runs on GPU:0
WARNING:tensorflow:From:4: _EagerTensorBase.gpu (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version. Instructions for updating: Use tf.identity instead. WARNING:tensorflow:From :5: _EagerTensorBase.cpu (from tensorflow.python.framework.ops) is deprecated and will be removed in a future version. Instructions for updating: Use tf.identity instead.
ベンチマーク
GPU 上の ResNet50 訓練のような計算が重いモデルについては、eager 実行パフォーマンスは tf.function 実行に匹敵します。しかしこの隔たりはより少ない計算を持つモデルのためにはより大きくなり、多くの小さい演算を持つモデルのためにホットコード・パスを最適化するために行われなければならない作業があります。
Work with functions
eager 実行が開発とデバッグをより対話的にする一方で、TensorFlow 1.x スタイルのグラフ実行は分散訓練、パフォーマンス最適化、そしてプロダクション配備のために優位点を持ちます。この隔たりを埋めるために、TensorFlow 2.0 は tf.function API を通した関数を導入します。より多くの情報は、tf.function ガイドを見てください。
以上