Theano 0.8.0: Tutorial: Theano における導関数

Theano 0.8.0: Tutorial: Theano における導関数(翻訳/要約)

* Theano 0.8.0: Tutorial: Derivatives in Theano の簡単な要約です。

 

勾配を計算する

さて Theano を少しばかりより洗練されたタスクに使ってみましょう : ある式 y の導関数をそのパラメータ x について計算する関数を作成します。これを行なうためには macro T.grad を使用します。例えば、x^2 の勾配を x に関して計算することができます。d(x^2)/dx = 2 \cdot x であることに注意してください。

この勾配を計算するコードをここに示します :

>>> import numpy
>>> import theano
>>> import theano.tensor as T
>>> from theano import pp
>>> x = T.dscalar('x')
>>> y = x ** 2
>>> gy = T.grad(y, x)
>>> pp(gy)  # print out the gradient prior to optimization
'((fill((x ** TensorConstant{2}), TensorConstant{1.0}) * TensorConstant{2}) * (x ** (TensorConstant{2} - TensorConstant{1})))'
>>> f = theano.function([x], gy)
>>> f(4)
array(8.0)
>>> numpy.allclose(f(94.2), 188.4)
True

この例では、正しいシンボリック(式の)勾配を計算していることが pp(gy) から見て取れます。fill((x ** 2), 1.0) は x**2 と同じ shape の行列を作成して 1.0 で満たします。

[Note] オプティマイザはシンボリック勾配式を単純化します。コンパイルされた関数の内部プロパティの内側を dig することでこれを見ることができます。

pp(f.maker.fgraph.outputs[0])
'(2.0 * x)'

最適化後はグラフには一つの Apply ノードだけが残され、これは入力を2倍します。

先に定義したロジスティック関数のような複雑な式の勾配も計算できます。ロジスティックの導関数は次であると分かっています: ds(x)/dx = s(x) \cdot (1 - s(x))

../_images/dlogistic.png

x-軸上の x と y 軸上の ds(x)/dx による、ロジスティック関数の勾配のプロット
>>> x = T.dmatrix('x')
>>> s = T.sum(1 / (1 + T.exp(-x)))
>>> gs = T.grad(s, x)
>>> dlogistic = theano.function([x], gs)
>>> dlogistic([[0, 1], [-1, -2]])
array([[ 0.25      ,  0.19661193],
       [ 0.19661193,  0.10499359]])

一般的に、任意のスカラ式 s, T.grad(s, w) は \frac{\partial s}{\partial w} を計算するための Theano 式を提供します。このようにして複数の入力を持つ関数のためでさえ、Theano は(コンパイルの間に最適化される、T.grad で返される式として)効率的なシンボリック(式の)微分をするために使用できます(シンボリック(式の)微分の説明は automatic differentiation 参照)。

[Note] T.grad の2番目の引数はリストを取れます、この場合出力もまたリストです。両者のリストの順序は重要です : 出力リストの要素 i は、2番目の引数として与えられたリストの i-th 要素に関する、T.grad の最初の引数の勾配です。 T.grad の最初の引数はスカラ(サイズ1のテンソル)でなければなりません。T.grad の引数のセマンティックス上の詳細と実装の詳細については、ライブラリの このセクション を参照。

微分の内部作業上の追加情報はまたより進んだチュートリアル Extending Theano で見つかります。

 

ヤコビアン (Jacobian) を計算する

Theano の用法では、用語ヤコビアンは、その入力に関する関数の出力の一階偏微分から成るテンソルをさします。(これは数学におけるいわゆるヤコビアン行列の一般化です。)Theano は、ヤコビアンを計算するに必要な全てを行なう theano.gradient.jacobian() マクロを実装しています。次のテキストはそれを手動でどのように行なうかを説明しています。

手動であるパラメータ x に関するある関数 y のヤコビアンを計算するためには scan を使用する必要があります。私たちがすることは、y のエントリについて loop over して x に関する y[i] の勾配を計算することです。

[Note] scan は Theano における一般的な op です、これはあらゆる種類の再帰的な方程式をシンボリックなマナーで書くことを可能にします。シンボリックなループを作成する(そして性能のためにそれらを最適化する)ことは難しいタスクですが、scan の性能を改善するために努力がなされます。このチュートリアルで後で scan に戻るでしょう。

>>> import theano
>>> import theano.tensor as T
>>> x = T.dvector('x')
>>> y = x ** 2
>>> J, updates = theano.scan(lambda i, y,x : T.grad(y[i], x), sequences=T.arange(y.shape[0]), non_sequences=[y,x])
>>> f = theano.function([x], J, updates=updates)
>>> f([4, 4])
array([[ 8.,  0.],
       [ 0.,  8.]])

このコードで行なっていることは、T.arange を使用して 0 から y.shape[0] までの int のシーケンスを生成していることです。そしてこのシーケンスを loop through して各ステップで、要素 y[i] の x に関する勾配を計算します。scan はこれらの行を自動的に concatenate し、ヤコビアンに相当する行列を生成します。

[Note] T.grad について理解するには幾つかの落とし穴があります。その一つは、scan のドキュメントからはこれが可能に見えても、上のヤコビアンの式を theano.scan(lambda y_i,x: T.grad(y_i,x), sequences=y, non_sequences=x) とは書き直せないことです。その理由は、y_i が既に x の関数ではないからです、y[i] が依然としてそうであっても。

 

Hessian(ヘッセ行列)を計算する

Theano では、用語 Hessian は通常の数学的な acception を持ちます : スカラ出力とベクトル入力を持つ関数の二階偏微分から成る行列です。Theano は theano.gradient.hessian() マクロを実装し、これはヘシアンを計算するに必要なこと全てを行ないます。次のテキストはどのように手動でそれを行なうかを説明しています。

Hessian もヤコビアンと同様に手動で計算できます。唯一の違いは、ある式 y のヤコビアンを計算する代わりに、T.grad(cost,x) のヤコビアンを計算します、ここで cost はあるスカラです。

>>> x = T.dvector('x')
>>> y = x ** 2
>>> cost = y.sum()
>>> gy = T.grad(cost, x)
>>> H, updates = theano.scan(lambda i, gy,x : T.grad(gy[i], x), sequences=T.arange(gy.shape[0]), non_sequences=[gy, x])
>>> f = theano.function([x], H, updates=updates)
>>> f([4, 4])
array([[ 2.,  0.],
       [ 0.,  2.]])
 

Jacobian times a Vector

時に Jacobians times vectors あるいは vectors times Jacobians という見地からアルゴリズムを表現できます。ヤコビアンを評価してそして積を計算することと比較して、ヤコビアンの実際の評価を回避したまま望む結果を計算する方法があります。これは本質的な性能の向上をもたらします。
そのようなアルゴリズムの一つの説明はここで見つかります :

Barak A. Pearlmutter, “Fast Exact Multiplication by the Hessian”, Neural Computation, 1994

原理的には Theano にこれらのパターンを自動的に識別して欲しいのですが、実際には、そのような最適化を一般的なマナーで実装するのは非常に難しいです。それゆえに、これらのタスクに貢献できる特別な関数を提供します。

R-operator

R operator は Jacobian とベクトル間の積を評価するために構築されます、すなわち \frac{\partial f(x)}{\partial x} v です。公式は一般の行列やテンソルである x にさえも拡張可能です、その場合もまたヤコビアンはテンソルで積はある種のテンソル積になります。 実際には重み行列という観点からそのような式を計算する必要があることになるので、Theano はこれをより一般的なオペレーション形式でサポートします。In order to evaluate the R-operation of expression y, with respect to x, multiplying the Jacobian with v you need to do something similar to this:

>>> W = T.dmatrix('W')
>>> V = T.dmatrix('V')
>>> x = T.dvector('x')
>>> y = T.dot(x, W)
>>> JV = T.Rop(y, W, V)
>>> f = theano.function([W, V, x], JV)
>>> f([[1, 1], [1, 1]], [[2, 2], [2, 2]], [0,1])
array([ 2.,  2.])

Rop を実装する Op の リスト です。

L-operator

R-operator と同様に、L-operator は a row vector times the Jacobian を計算します。数式は v \frac{\partial
f(x)}{\partial x} になります。L-operator もまた(ベクトルのためだけでなく)一般的なテンソルのためにサポートされます。同様に、次のように実装されます :

>>> W = T.dmatrix('W')
>>> v = T.dvector('v')
>>> x = T.dvector('x')
>>> y = T.dot(x, W)
>>> VJ = T.Lop(y, W, v)
>>> f = theano.function([v,x], VJ)
>>> f([2, 2], [0, 1])
array([[ 0.,  0.],
       [ 2.,  2.]])

[Note] v, the point of evaluation, differs between the L-operator and the R-operator. L-operator に対しては、the point of evaluation は出力と同じ shape を持つ必要がありmさうが、R-operator に対してはこのポイントは入力パラメータと同じ shape を持つべきです。更に、これら2つのオペレーションの結果は異なります。L-operator の結果は入力パラメータと同じ shape になります、一方で R-operator の結果は出力のそれと同様の shape を持ちます。

List of op with r op support.

 

Hessian times a Vector

If you need to compute the Hessian times a vector, you can make use of the above-defined operators to do it more efficiently than actually computing the exact Hessian and then performing the product. Due to the symmetry of the Hessian matrix, you have two options that will give you the same result, though these options might exhibit differing performances. Hence, we suggest profiling the methods before using either one of the two:

>>> x = T.dvector('x')
>>> v = T.dvector('v')
>>> y = T.sum(x ** 2)
>>> gy = T.grad(y, x)
>>> vH = T.grad(T.sum(gy * v), x)
>>> f = theano.function([x, v], vH)
>>> f([4, 4], [2, 2])
array([ 4.,  4.])

or, making use of the R-operator:

>>> x = T.dvector('x')
>>> v = T.dvector('v')
>>> y = T.sum(x ** 2)
>>> gy = T.grad(y, x)
>>> Hv = T.Rop(gy, x, v)
>>> f = theano.function([x, v], Hv)
>>> f([4, 4], [2, 2])
array([ 4.,  4.])
 

Final Pointers

  • grad 関数はシンボリックに動作します: Theano 変数を受け取り返します。
  • grad はマクロと比較できます、何故ならそれは繰り返し適用できるからです。
  • Scalar costs only can be directly handled by grad. Arrays are handled through repeated applications.
  • Built-in functions allow to compute efficiently vector times Jacobian and vector times Hessian.
  • Work is in progress on the optimizations required to compute efficiently the full Jacobian and the Hessian matrix as well as the Jacobian times vector.
 

以上