20210908のTensorFlowに関する記事は1件です。

自動微分と勾配テープ(TensorFlow)入門

はじめに 自動微分と勾配テープでつまずいているので、調査しまとめることにしました。 主に以下のサイトを参考にしました。 公式より、「自動微分と勾配テープ」とは TensorFlow には、自動微分、すなわち、入力変数に対する計算結果の勾配を計算するためのtf.GradientTape API があります。TensorFlow は、tf.GradientTape のコンテキスト内で行われる演算すべてを「テープ」に「記録」します。その後 TensorFlow は、そのテープと、そこに記録された演算ひとつひとつに関連する勾配を使い、トップダウン型自動微分(リバースモード)を使用して、「記録」された計算の勾配を計算します。 レポジトリはこちらです。 import import tensorflow as tf 基礎 1回微分 y = x^2 を微分して見ます。 \frac{dy}{dx} = 2x \\ \frac{dy}{dx} |_{x=8} = 16 操作は、このコンテキストマネージャー内で実行され、その入力の少なくとも1つが「監視」されている場合に記録されます。 x = tf.constant(8.0) # 8.0, shape=() with tf.GradientTape() as t: t.watch(x) y = x * x # 元の入力テンソル x に対する z の微分 dy_dx = t.gradient(y, x) # 16.0, shape=() 2回微分 y = x^2 を微分して見ます。 \frac{dy}{dx} = 2x \\ \frac{dy^2}{d^2x} = 2 \\ x = tf.constant(3.0) # 3.0, shape=() print(x) with tf.GradientTape() as t: t.watch(x) with tf.GradientTape() as tt: tt.watch(x) y = x * x dy_dx = tt.gradient(y, x) # 6.0, shape=() dy2_dx2 = t.gradient(dy_dx, x) # 2.0, shape=() 定数で微分できない場合 公式サイトによれば、微分できない場合は, '1' のテンソルを返すそうです。 x = tf.constant(8.0) # 8.0, shape=() with tf.GradientTape() as t: t.watch(x) y = x dy_dx = t.gradient(y, x) # 1.0, shape=() 制御フローの記録 勾配テープは演算を実行の都度記録するため、(たとえば if や while を使った)Python の制御フローも自然に扱われます。 def f(x, y): output = 1.0 for i in range(y): if i > 1 and i < 5: output = tf.multiply(output, x) return output def grad(x, y): with tf.GradientTape() as t: t.watch(x) out = f(x, y) return t.gradient(out, x) x = tf.convert_to_tensor(2.0) grad(x, 6).numpy() # 12.0 勾配テープ内で使用するメソッド テンソルのまま処理できます。 よく使うメソッド reduce_sum = 配列内のすべての要素を足し算する関数 multiply = テンソルの要素の掛け算する関数 z = y^2 \\ \frac{dz}{dy} = 2x \\ \frac{dz}{dy} |_{x=4} = 8 より \frac{dz}{dx} = 8 \cdot \begin{bmatrix} 1 & 1 \\ 1 & 1 \end{bmatrix} =\begin{bmatrix} 8 & 8 \\ 8 & 8 \end{bmatrix} x = tf.ones((2, 2)) # x = [[1. 1.] [1. 1.]] with tf.GradientTape() as t: t.watch(x) y = tf.reduce_sum(x) # 4.0, shape=() z = tf.multiply(y, y) # 16.0, shape=() dz_dx = t.gradient(z, x) # [[8. 8.] [8. 8.]], shape=(2, 2) ループ add = 関数の足し算 less = 未満の条件式 i = tf.constant(0) c = lambda i: tf.less(i, 10) # b = lambda i: (tf.add(i, 1), ) def b(i): print(i) return (tf.add(i, 1), ) r = tf.while_loop(c, b, [i]) tf.Tensor(0, shape=(), dtype=int32) tf.Tensor(1, shape=(), dtype=int32) tf.Tensor(2, shape=(), dtype=int32) tf.Tensor(3, shape=(), dtype=int32) tf.Tensor(4, shape=(), dtype=int32) tf.Tensor(5, shape=(), dtype=int32) tf.Tensor(6, shape=(), dtype=int32) tf.Tensor(7, shape=(), dtype=int32) tf.Tensor(8, shape=(), dtype=int32) tf.Tensor(9, shape=(), dtype=int32) Out[27]: (<tf.Tensor: shape=(), dtype=int32, numpy=10>,) 指定したインデックスを出力 gather_nd = テンソルと、そのテンソル内の位置を表すインデックスを提供します。指定したインデックスに対応するテンソルの要素を返します。第一引数は、探すテンソル、第二引数は、そのインデックスを指定します。 一次元配列の場合 x = tf.constant([0.1, 0.1, 1.5, 4.5, 3.6, 1.2]) y = tf.constant([3.1, 2.3, 1.4, 2.3, 4.4, 3.1]) uniqueIndices = tf.constant([0, 1, 2, 3, 4, 5]) x = tf.gather_nd(x, uniqueIndices[:,None]) y = tf.gather_nd(y, uniqueIndices[:,None]) tf.multiply(x, y) # shape=(6,), array([ 0.31 , 0.23 , 2.1 , 10.349999, 15.84 , 3.72 ]) array([ 0.31 , 0.23 , 2.1 , 10.349999, 15.84 , 3.72 ]) の間が空いているのは、その分のメモリを開けているからだと思われます。 二次元配列の場合 x = [[1,2,3],[4,5,6]] y = tf.gather_nd(x, [[1,1],[1,2]]) # shape=(2,) numpy=array([5, 6], dtype=int32) 条件分岐 less = 大小比較のメソッド cond = いわゆるif。条件分岐します。 x = tf.constant(2) y = tf.constant(5) def f1(): return tf.multiply(x, 17) def f2(): return tf.add(y, 23) r = tf.cond(tf.less(x, y), f1, f2) rはf1()の値が設定されます。f2の操作(例:tf.add)は実行されません。 (x= 2, r = 34) 関数 公式より tf.functionを用いてある関数にアノテーションを付けたとしても、一般の関数と変わらずに呼び出せます。一方、実行時にはその関数はグラフへとコンパイルされます。これにより、より高速な実行や、 GPU や TPU での実行、SavedModel へのエクスポートといった利点が得られます。 @tf.function def mini_func(x): return x * x x = tf.constant(5.0) with tf.GradientTape() as t: t.watch(x) result = mini_func(x) dy_dx = t.gradient(result, x) print(dy_dx) # tf.Tensor(10.0, shape=(), dtype=float32) constantとVariableの違いについて TensorFlowにおけるconstantとVariableの違いは、constantを宣言した場合、その値は将来的に変更できない(また、初期化は操作ではなく値で行う必要がある)ことです。 また、デフォルトでは、GradientTapeは、コンテキスト内でアクセスされるすべてのtrainable変数を自動的に監視します。(t.watch(v)しなくてよい。) constantの場合 @tf.function def mini_func(x): return x * x x = tf.constant(5.0) with tf.GradientTape() as t: t.watch(x) result = mini_func(x) dy_dx = t.gradient(result, x) print(dy_dx) # tf.Tensor(10.0, shape=(), dtype=float32) Variableの場合 @tf.function def mini_func(x): return x * x x = tf.Variable(5.0) with tf.GradientTape() as t: result = mini_func(x) dy_dx = t.gradient(result, x) print(dy_dx) # tf.Tensor(10.0, shape=(), dtype=float32) クリッピング clip_value_min ≤ value ≤ clip_value_max の場合は value または - value < clip_value_min の場合は clip_value_min または - clip_value_max < value の場合は clip_value_max t = tf.constant([[-10., -1., 0.], [0., 2., 10.]]) t2 = tf.clip_by_value(t, clip_value_min=-1, clip_value_max=1) t2.numpy() array([[-1., -1., 0.], [ 0., 1., 1.]], dtype=float32) -1 ≤ value ≤ 1 の場合は value または - value < -1 の場合は -1 または - 1 < value の場合は 1 を満たす形になっていることがわかります。 tensorの操作 全て0のtensorを作る tf.zeros([2, 3]) <tf.Tensor: shape=(2, 3), dtype=float32, numpy= array([[0., 0., 0.], [0., 0., 0.]], dtype=float32)> 全て1のtensorを作る tf.ones((2, 3)) <tf.Tensor: shape=(2, 3), dtype=float32, numpy= array([[1., 1., 1.], [1., 1., 1.]], dtype=float32)> 全て同じ値のtensorを作る tf.fill([2, 3], 9) <tf.Tensor: shape=(2, 3), dtype=int32, numpy= array([[9, 9, 9], [9, 9, 9]], dtype=int32)> キャスト tf.float32に揃えていないことで、以下のエラー文が出ます。なので、揃っていない時は、揃えましょう。 InvalidArgumentError: cannot compute Mul as input #1(zero-based) was expected to be a float tensor but is a double tensor [Op:Mul] labels = tf.constant([0, 3, 1]) print(labels) tf.cast(labels, tf.float32) tf.Tensor([0 3 1], shape=(3,), dtype=int32) <tf.Tensor: shape=(3,), dtype=float32, numpy=array([0., 3., 1.], dtype=float32)> numpyに変換 tensorに慣れてないうちは、かなり使います。 labels = tf.constant([0, 3, 1]) print(labels) labels.numpy() tf.Tensor([0 3 1], shape=(3,), dtype=int32) array([0, 3, 1], dtype=int32)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む