Theano 0.8.0: Tutorial: より多くの例(翻訳/要約)
* Theano 0.8.0: Tutorial: More Examples の簡単な要約です。
ここで Theano の基礎的なオブジェクトと演算に、次のライブラリの章をブラウズすることでシステマチックに慣れ始めるのが賢いでしょう : Basic Tensor Functionality
ロジスティック関数
ここで他の端的な例を示します、2つの数を一緒に加算するよりはもう少し手が込んでいますが。次で与えられるロジスティック曲線を計算したいと仮定しましょう(訳注 : 標準シグモイド関数) :


double の行列上 element-wise に関数を計算することを望むでしょう、これはこの関数を行列の個々の要素に対して適用することを意味しています。
貴方が行なうことはこれです :
>>> import theano >>> import theano.tensor as T >>> x = T.dmatrix('x') >>> s = 1 / (1 + T.exp(-x)) >>> logistic = theano.function([x], s) >>> logistic([[0, 1], [-1, -2]]) array([[ 0.5 , 0.73105858], [ 0.26894142, 0.11920292]])
ロジスティックが element-wise に遂行される理由は、その演算の全て — 除算、加算、べき乗、そして除算(訳注:原文まま)– それら自身が element-wise な演算だからです。
次の場合も同様です:

これらの交互に置き換えられる形式が同じ値を生成することを検証できます :
>>> s2 = (1 + T.tanh(x / 2)) / 2 >>> logistic2 = theano.function([x], s2) >>> logistic2([[0, 1], [-1, -2]]) array([[ 0.5 , 0.73105858], [ 0.26894142, 0.11920292]])
一つ以上のことを同時に計算する
Theano は複数の出力を持つ関数もサポートします。例えば、2つの行列 a と b の間の element-wise な差、絶対差、そして差の二乗を同時に計算することができます :
>>> a, b = T.dmatrices('a', 'b') >>> diff = a - b >>> abs_diff = abs(diff) >>> diff_squared = diff**2 >>> f = theano.function([a, b], [diff, abs_diff, diff_squared])
[Note] dmatrices は提供した名前の数だけ出力を生成します。このチュートリアルで多用される、シンボリック variable を割り当てるためのショートカットです。
この関数 f を使用する時は3つの variable を返します :
>>> f([[1, 1], [1, 1]], [[0, 1], [2, 3]]) [array([[ 1., 0.], [-1., -2.]]), array([[ 1., 0.], [ 1., 2.]]), array([[ 1., 0.], [ 1., 4.]])]
引数のためのデフォルト値を設定する
内部状態とともに function を作ることもまた可能です。例えば、accumulator を作りたいとしましょう: 最初は、状態はゼロに初期化されています。そして各関数呼び出し毎に、状態は関数引数によってインクリメントされます。
最初に accumulator 関数を定義しましょう。それは内部状態に引数を加算して古い状態値を返します。
>>> from theano import shared >>> state = shared(0) >>> inc = T.iscalar('inc') >>> accumulator = function([inc], state, updates=[(state, state+inc)])
このコードは2、3の新しい概念を紹介します。shared 関数はいわゆる 共有変数 を構築します。これらはシンボリックと非シンボリック変数のハイブリッドでその値は複数の関数で共有されます。共有変数は、dmatrices(…) で返されるオブジェクトのようにシンボリック式で使用できますが、それらはまたそれを使う全ての関数のシンボリック変数により取られる値を定義する内部値も持ちます。それは共有変数と呼ばれます、何故ならばその値は多くの関数間で共有されるからです。値は .get_value() と .set_value() メソッドでアクセス、変更可能です。私たちはこの点にすぐ戻ります。
このコードの他の新しいことは関数の updates パラメータです。updates は form のペア(共有変数、新しい式)のリストとともに供給されなければなりません。それはまた辞書でもあります、そのキーは共有変数で値は新しい式です。どちらの方法でも、それは「この関数が動作する時、それは各共有変数の .value を該当する式の結果で置き換える」を意味しています。上では、私たちの accumulator は状態値を状態とインクリメント量の合計で置き換えます。
試してみましょう!
>>> print(state.get_value()) 0 >>> accumulator(1) array(0) >>> print(state.get_value()) 1 >>> accumulator(300) array(1) >>> print(state.get_value()) 301
状態をリセットすることもできます。.set_value() メソッドを使うだけです :
>>> state.set_value(-1) >>> accumulator(3) array(-1) >>> print(state.get_value()) 2
上述したように、同じ共有変数を使う一つ以上の関数を定義することができます。これらの関数はすべて値の更新ができます。
>>> decrementor = function([inc], state, updates=[(state, state-inc)]) >>> decrementor(2) array(2) >>> print(state.get_value()) 0
何故 updates メカニズムが存在するのか不思議に思うかもしれません。新しい式を返して、それで Numpy で通常のように作業することで常に同様の結果を得ることができます。update メカニズムはシンタックス的な便宜でもありますが、主として効率のためにあります。共有変数への updates は、in-place アルゴリズム (e.g. low-rank matrix updates) を使用して時により迅速に行なわれます。また、Theano はどこでどのように共有変数が割り当てられるかについてのより一層の制御を持ちます、これは GPU 上で良い性能を得るための重要な要素の一つです。
共有変数を使用して、しかしその値の使用は望まないで、ある式を表現することがあるかもしれません。この場合、関数の givens パラメータを使用することができます、これは一つの特定の関数のためにグラフの特定のノードを置き換えます。
>>> fn_of_state = state * 2 + inc >>> # foo の型は ``givens`` で置き換えようとする共有変数に適合しなければなりません。 >>> foo = T.scalar(dtype=state.dtype) >>> skip_shared = function([inc, foo], fn_of_state, givens=[(state, foo)]) >>> skip_shared(1, 3) # state.value ではなく、state のために 3 を使用します。 array(7) >>> print(state.get_value()) # 古い状態はまだそこにありますが、使用しません。 0
givens パラメータは、共有変数だけでなく任意のシンボリック変数を置き換えるのに使用できます。一般に、定数、そして式を置き換えることができます。けれども注意してください、givens 代用 (substitution) により導入された式に相互依存は許されません、代用の順序は定義されませんので、代用は任意の順序で動作しなければなりません。実際には、givens は式 (formula) の任意の一部を同じ shape と dtype のテンソルとして評価される異なる式 (expression) で置き換えることを可能にするメカニズムと考えるのが良いです。
[Note] Theano 共有変数の broadcast pattern は各次元について False にデフォルト設定されています。共有変数のサイズは時間をかけて変化することができますので、broadcastable pattern を見つけるのに shape は使用できません。異なるパターンを望むのであれば、パラメータ theano.shared(…, broadcastable=(True, False)) としてそれを渡してください。
ランダム数(乱数)を使用する
Theano においては最初に全てをシンボリックに表現してそして結局はこの式を関数を得るためにコンパイルしますので、擬似ランダム数の使用は Numpy におけるそれのように直接的ではありませんが、同時にそれほど複雑でもありません。Theano の計算にランダム性を持ち込むことを考える手段は貴方のグラフにランダム変数を置くことです。Theano は NumPy RandomStream オブジェクト (ランダム数ジェネレータ) をそのような各変数に割り当て、必要に応じてそこから引き出し (draw) ます。この種のランダム数のシーケンスをランダム・ストリームと呼びます。Random streams are at their core shared variables, so the observations on shared variables hold here as well. Theano のランダム・オブジェクトは RandomStreams で、そして低位レベルでは RandomStreamsBase 定義され実装されます。
簡潔な例
ここに簡潔な例を示します。セットアップ・コードは :
from theano.tensor.shared_randomstreams import RandomStreams from theano import function srng = RandomStreams(seed=234) rv_u = srng.uniform((2,2)) rv_n = srng.normal((2,2)) f = function([], rv_u) g = function([], rv_n, no_default_updates=True) # rv_n.rng を更新しない nearly_zeros = function([], rv_u + rv_u - 2 * rv_u)
ここで、 ‘rv_u’ は一様分布から draw する 2×2 行列のランダム・ストリームを表します。同様に、 ‘rv_n’ は正規分布から draw する 2×2 行列のランダム・ストリームを表します。実装されている分布は RandomStreams で、低位レベルでは ran_random で定義されています。それらは CPU 上でのみ動作します。GPU 版には 他の実装 を参照のこと。
さてこれらのオブジェクトを使用しましょう。f() を呼び出せば、一様ランダム数が得られます。ランダム数ジェネレータの内部状態は自動更新されますので、毎回異なるランダム数が得られます。
>>> f_val0 = f() >>> f_val1 = f() # f_val0 とは異なる数値
特別な引数 no_default_updates=True を(g の中のように)関数に付加した時には、ランダム数ジェネレータの状態は返された関数の呼び出しでは影響されません。従って、例えば、g の複数回呼び出しは同じ数字を返します。
>>> g_val0 = g() # f_val0 と f_val1 とは異なる数字 >>> g_val1 = g() # g_val0 と同じ数字!
重要な点はランダム変数は任意の単一の関数実行の間にせいぜい一度引き出されることです。従って、rv_u ランダム変数が出力式に3回出現したとしても、nearly_zeros 関数はおおよそ 0 を返すことが保証されます。
>>> nearly_zeros = function([], rv_u + rv_u - 2 * rv_u)
Seeding Streams
ランダム変数は個々にあるいは集合的に seed できます。.rng.set_value() を使用して .rng 属性に seed あるいは割り当てることでランダム変数に seed できます。
>>> rng_val = rv_u.rng.get_value(borrow=True) # rv_u のために rng を取得。 >>> rng_val.seed(89234) # ジェネレータに seed >>> rv_u.rng.set_value(rng_val, borrow=True) # Assign back seeded rng
RandomStream オブジェクトにより割り当てられたランダム変数の全てに(RandomStream)オブジェクトの seed メソッドで seed することもできます。この seed は一時的なランダム数ジェネレータを seed することに使われ、それは個々のランダム変数のための seed を順番に生成します。
>>> srng.seed(902340) # rv_u を rv_n 異なる seed でそれぞれ seed します。
関数間でストリームを共有する
共有変数の常として、ランダム変数のために使用されるランダム数ジェネレータは関数間で共通です。従って nearly_zeros 関数は上の関数 f で使用されるジェネレータの状態を更新します。
例えば :
>>> state_after_v0 = rv_u.rng.get_value().get_state() >>> nearly_zeros() # これは rv_u ジェネレータに影響します。 array([[ 0., 0.], [ 0., 0.]]) >>> v1 = f() >>> rng = rv_u.rng.get_value(borrow=True) >>> rng.set_state(state_after_v0) >>> rv_u.rng.set_value(rng, borrow=True) >>> v2 = f() # v2 != v1 >>> v3 = f() # v3 == v1
Theano グラフ間でランダム状態をコピーする
あるユースケースでは、ユーザは、与えられた Theano グラフ (e.g. g1, with compiled function f1 below) に関連する全てのランダム数ジェネレータの “状態” を2番目のグラフ (e.g. g2, with function f2) に転送したいかもしれません。これは例えば、以前のモデルの塩漬けになっていたバージョンのパラメータから、モデルの状態を初期化しようとする時に発生します。theano.tensor.shared_randomstreams.RandomStreams と theano.sandbox.rng_mrg.MRG_RandomStreams のためにはこれは state_updates パラメータの要素をコピーすることで達成されます。
RandomStreams オブジェクトからランダム変数を引き出す各回に、state_updates リストにタプルが追加されます。最初の要素は共有変数で、これはこの特別の変数に関連するランダム数ジェネレータの状態を表し、2番目はランダム数生成プロセスに相当する theano グラフを表します (i.e. RandomFunction{uniform}.0)。
“ランダム状態” が一つの theano 関数から他にどのように転送されるの例を下に示します。
>>> from __future__ import print_function >>> import theano >>> import numpy >>> import theano.tensor as T >>> from theano.sandbox.rng_mrg import MRG_RandomStreams >>> from theano.tensor.shared_randomstreams import RandomStreams
>>> class Graph(): ... def __init__(self, seed=123): ... self.rng = RandomStreams(seed) ... self.y = self.rng.uniform(size=(1,))
>>> g1 = Graph(seed=123) >>> f1 = theano.function([], g1.y)
>>> g2 = Graph(seed=987) >>> f2 = theano.function([], g2.y)
>>> # デフォルトでは、2つの関数は同期しません。 >>> f1() array([ 0.72803009]) >>> f2() array([ 0.55056769])
>>> def copy_random_state(g1, g2): ... if isinstance(g1.rng, MRG_RandomStreams): ... g2.rng.rstate = g1.rng.rstate ... for (su1, su2) in zip(g1.rng.state_updates, g2.rng.state_updates): ... su2[0].set_value(su1[0].get_value())
>>> # 今、theano ランダム数ジェネレータの状態をコピーします。 >>> copy_random_state(g1, g2) >>> f1() array([ 0.59044123]) >>> f2() array([ 0.59044123])
他のランダム分布
他の分布の実装 もあります。
他の実装
MRG31k3p と CURAND に基づく他の2つの実装もあります。RandomStream は CPU のみで動作し、 MRG31k3pは CPU と GPU 上で動作します。CURAND は GPU 上でのみ動作します。
[Note] MRG 版を簡単に使うためには、import を変更するだけで良いです :
from theano.sandbox.rng_mrg import MRG_RandomStreams as RandomStreams
現実的な例: ロジスティック回帰
これまでの要素はこのより現実的な例で feature されます。これは繰り返し使用されます。
import numpy import theano import theano.tensor as T rng = numpy.random N = 400 # 訓練サンプル・サイズ feats = 784 # 入力変数の数 # データセット: D = (input_values, target_class) を生成します。 D = (rng.randn(N, feats), rng.randint(size=N, low=0, high=2)) training_steps = 10000 # Theano シンボリック変数を宣言します。 x = T.matrix("x") y = T.vector("y") # 重みベクトルをランダムに初期化します。 # # これと次のバイアス変数 b は共有されますので、 # それらは、それらの値を訓練反復(更新)間で保持します。 w = theano.shared(rng.randn(feats), name="w") # バイアス項を初期化します。 b = theano.shared(0., name="b") print("Initial model:") print(w.get_value()) print(b.get_value()) # Theano 式グラフを構築する p_1 = 1 / (1 + T.exp(-T.dot(x, w) - b)) # Probability that target = 1 prediction = p_1 > 0.5 # 予測しきい値 xent = -y * T.log(p_1) - (1-y) * T.log(1-p_1) # 交差エントロピー損失関数 cost = xent.mean() + 0.01 * (w ** 2).sum()# 最小化するコスト gw, gb = T.grad(cost, [w, b]) # コストの勾配を計算する # w.r.t weight vector w and # bias term b # (we shall return to this in a # following section of this tutorial) # コンパイル train = theano.function( inputs=[x,y], outputs=[prediction, xent], updates=((w, w - 0.1 * gw), (b, b - 0.1 * gb))) predict = theano.function(inputs=[x], outputs=prediction) # 訓練する for i in range(training_steps): pred, err = train(D[0], D[1]) print("Final model:") print(w.get_value()) print(b.get_value()) print("target values for D:") print(D[1]) print("prediction on D:") print(predict(D[0]))
以上