TensorFlow : Tutorials : Eager : 自動微分と gradient tape (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 07/17/2018
* TensorFlow 1.9 でドキュメント構成が変わり新規にチュートリアル・ページも追加されました。
* 本ページは、TensorFlow 本家サイトの Tutorials – Research and Experimentation – Automatic differentiation and gradient tape を翻訳した上で適宜、補足説明したものです:
* サンプルコードの動作確認はしておりますが、適宜、追加改変している場合もあります。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。
前のチュートリアル では Tensor とそれらの上の演算を紹介しました。このチュートリアルでは 自動微分 をカバーします、機械学習モデルを最適化するための主要なテクニックです。
セットアップ
import tensorflow as tf tf.enable_eager_execution() tfe = tf.contrib.eager # Shorthand for some symbols
関数の導関数
TensorFlow は自動微分のための API を提供します – 関数の導関数を計算します。数学をより密接に模倣する方法は Python 関数、例えば f、の計算をカプセル化して f のその引数に関する導関数を計算する関数を作成するために tfe.gradients_function を使用します。もし numpy 関数を微分するために autograd に精通しているのであれば、これは馴染みがあるでしょう。例えば :
from math import pi def f(x): return tf.square(tf.sin(x)) assert f(pi/2).numpy() == 1.0 # grad_f は f のその引数に関する導関数のリストを返します。 # f() は単一の引数持ちますので # grad_f は単一の要素を持つリストを返します。 grad_f = tfe.gradients_function(f) assert tf.abs(grad_f(pi/2)[0]).numpy() < 1e-7
高次勾配
同じ API を望む回数微分するために使用できます :
def f(x): return tf.square(tf.sin(x)) def grad(f): return lambda x: tfe.gradients_function(f)(x)[0] x = tf.lin_space(-2*pi, 2*pi, 100) # 100 points between -2π and +2π import matplotlib.pyplot as plt plt.plot(x, f(x), label="f") plt.plot(x, grad(f)(x), label="first derivative") plt.plot(x, grad(grad(f))(x), label="second derivative") plt.plot(x, grad(grad(grad(f)))(x), label="third derivative") plt.legend() plt.show()
Gradient tape
総ての微分可能な TensorFlow 演算は関連する勾配関数を持ちます。例えば、tf.square(x) の勾配関数は 2.0 * x を返す関数です。(上の例の f(x) のような) ユーザ定義関数の勾配を計算するために、TensorFlow は関数の出力を計算するために適用された総ての演算を最初に「記録」します。この記録を「テーブ」と呼びます。それからそれは reverse モード微分 を使用してユーザ定義関数の勾配を計算するためにそのテープと各プリミティブ演算に関連する勾配関数を使用します。
演算はそれらが実行されるにつれて記録されますので、Python 制御フロー (例えば if と while の使用) は自然に処理されます :
def f(x, y): output = 1 for i in range(y): output = tf.multiply(output, x) return output def g(x, y): # Return the gradient of `f` with respect to it's first parameter return tfe.gradients_function(f)(x, y)[0] assert f(3.0, 2).numpy() == 9.0 # f(x, 2) is essentially x * x assert g(3.0, 2).numpy() == 6.0 # And its gradient will be 2 * x assert f(4.0, 3).numpy() == 64.0 # f(x, 3) is essentially x * x * x assert g(4.0, 3).numpy() == 48.0 # And its gradient will be 3 * x * x
関心のある計算を関数にカプセル化することは時には不便かもしれません。例えば、関数で計算される出力の中間値に関する勾配を望む場合です。そのような場合には、僅かばかり少し冗長ですが明示的な tf.GradientTape コンテキストは有用です。tf.GradientTape のコンテキストの内側の計算総ては「記録」されます。
例えば :
x = tf.ones((2, 2)) # TODO(b/78880779): Remove the 'persistent=True' argument and use # a single t.gradient() call when the bug is resolved. with tf.GradientTape(persistent=True) as t: # TODO(ashankar): Explain with "watch" argument better? t.watch(x) y = tf.reduce_sum(x) z = tf.multiply(y, y) # Use the same tape to compute the derivative of z with respect to the # intermediate value y. dz_dy = t.gradient(z, y) assert dz_dy.numpy() == 8.0 # Derivative of z with respect to the original input tensor x dz_dx = t.gradient(z, x) for i in [0, 1]: for j in [0, 1]: assert dz_dx[i][j].numpy() == 8.0
高次勾配
GradientTape コンテキスト・マネージャーの内側の演算は自動微分のために記録されます。そのコンテキストで勾配が計算されれば、勾配計算もまた記録されます。結果的に、高次勾配に対してもまた正確に同じ API が動作します。例えば :
# TODO(ashankar): Should we use the persistent tape here instead? Follow up on Tom and Alex's discussion x = tf.constant(1.0) # Convert the Python 1.0 to a Tensor object with tf.GradientTape() as t: with tf.GradientTape() as t2: t2.watch(x) y = x * x * x # Compute the gradient inside the 't' context manager # which means the gradient computation is differentiable as well. dy_dx = t2.gradient(y, x) d2y_dx2 = t.gradient(dy_dx, x) assert dy_dx.numpy() == 3.0 assert d2y_dx2.numpy() == 6.0
以上