TheanoのSCANについて
SCANとは
theanoにおける繰り返し処理に対応する関数。
基本的な理解のために下記ページを参考にさせて頂いた。
sinhrks.hatenablog.com
fn
の引数は、
fn
に渡す引数は (最大で) 以下の 3 つになる。それぞれ、対応する引数がない場合は省略される (fn
に渡される引数の数自体が変わる)。
- シーケンスの要素 (sequences が指定されている場合)
- 直前の繰り返し処理の結果 (outputs_info が指定されている場合)
- シーケンスでない引数 = non_sequencesそのもの (non_sequencesが指定されている場合)
となるらしい。
なるほど、引数は明示的に与えるのではなく、scanの指定方法で変わるらしい。
では、実際にどのように変わるかを簡単なコードを書いて試してみた。
テスト①
- outputs_info:v
- scanの引数:prior, seq1
- scanの戻り値:prior
import theano from theano import tensor as T n = T.iscalar('n') v = T.ivector('v') def scan(prior, seq1): return prior result, _ = theano.scan(fn=scan, sequences=None, outputs_info=v, non_sequences=v, n_steps=n) f = theano.function(inputs=[v, n], outputs=result) f([1, 2, 3], 2) # [[1 2 3] [1 2 3]]
初期値をそのまま返す。(繰り返し2回)
テスト②
- outputs_info:[v, None]
- scanの引数:prior, seq1
- scanの戻り値:prior
def scan(prior, seq1): return prior result, _ = theano.scan(fn=scan, sequences=None, outputs_info=[v, None], non_sequences=v, n_steps=n) f = theano.function(inputs=[v, n], outputs=result) print f([1, 2, 3], 2) # ValueError: Please provide None as outputs_info for any output that does not feed back into scan (i.e. it behaves like a map)
outputs_infoのNoneを渡してるけど、対応する戻り値が無いと怒られる。
テスト③
- outputs_info:[v, None]
- scanの引数:prior, seq1
- scanの戻り値:prior + seq1
def scan(prior, seq1): return prior, prior + seq1 result, _ = theano.scan(fn=scan, sequences=None, outputs_info=[v, None], non_sequences=v, n_steps=n) f = theano.function(inputs=[v, n], outputs=result) print f([1, 2, 3], 2) # [array([[1, 2, 3], [1, 2, 3]]), array([[2, 4, 6], [2, 4, 6]])]
array([[1, 2, 3], [1, 2, 3]])は、priorの戻り値、
array([[2, 4, 6], [2, 4, 6]])は、prior + seq1の戻り値。
*繰り返しでのseqの値は保持されない。
ここまでで、分かったことは、
fn
の戻り値は、outputs_infoと揃える必要がある。
考えてみれば当然だが、outputs_infoは初期値&直前の繰り返し処理の結果を保持する役割があるので、そこは揃えないと怒られる。
テスト④
- outputs_info:[v, v]
- scanの引数:prior, seq1
- scanの戻り値:prior + seq1
def scan(prior, seq1): return prior, prior + seq1 result, _ = theano.scan(fn=scan, sequences=None, outputs_info=[v, v], non_sequences=v, n_steps=n) f = theano.function(inputs=[v, n], outputs=result) print f([1, 2, 3], 3) # TypeError: scan() takes exactly 2 arguments (3 given)
outputs_infoを[v, v]としたので、scan関数に渡す引数が、3つになってしまった。(全部v)
テスト⑤
- outputs_info:[v, v]
- scanの引数:prior, seq1, seq2
- scanの戻り値:prior + seq1
def scan(prior, seq1, seq2): return prior, prior + seq1 result, _ = theano.scan(fn=scan, sequences=None, outputs_info=[v, v], non_sequences=v, n_steps=n) f = theano.function(inputs=[v, n], outputs=result) print f([1, 2, 3], 2) [array([[1, 2, 3], [1, 2, 3]]), array([[2, 4, 6], [3, 6, 9]])]
テスト③との違いは、2回目の結果([3, 6, 9])
違いはoutputs_info=[v, None]がoutputs_info=[v, v]
1回目のprior + seq1の結果がoutputs_infoで保持されるようになったので、2回目の結果が初期値(1, 2, 3)+1回目の結果(2, 4, 6)の結果となった。
テスト⑥
- outputs_info:v
- scanの引数:prior, seq
- scanの戻り値:seq + prior * 2
iterationでもやってみた。
def recurrence(seq, prior): return seq + prior * 2 x = T.matrix('x') v = T.vector('v') result, _ = theano.scan(fn=recurrence, outputs_info=v, sequences=x, non_sequences=None) f = theano.function(inputs=[x, v], outputs=result) print f([[2., 0.], [0., 2.]], [1., 1.]) # [[ 4. 2.] [ 8. 6.]]
他の場合と同じであるが、fn
に渡す引数の順序は「seq(sequences or non_sequences), outputs_info」となることに注意する。
これでscanの引数についてある程度整理できた気がする。
追記
scan
の挙動の再整理
import theano from theano import tensor as T init = T.iscalar('init') seq = T.ivector('seq') def scan(prior, seq): return prior + seq result, _ = theano.scan(fn=scan, sequences=seq, outputs_info=init, non_sequences=None) f = theano.function(inputs=[seq, init], outputs=result[-1]) print f([1, 2, 3], 1) [2 4 7]
sequencesに渡すシンボルは、イテレーションするオブジェクトの型。
(この場合、ベクトル)
変数は、シンボルと共有変数どちらでも問題ない。
import theano from theano import tensor as T # init = T.iscalar('init') init = theano.shared(1) seq = T.ivector('seq') def scan(prior, seq): return prior + seq result, _ = theano.scan(fn=scan, sequences=seq, outputs_info=init, non_sequences=None) f = theano.function(inputs=[seq], outputs=result) print f([1, 2, 3]) [2 4 7]
違いは、関数呼び出し時に値を渡すかどうか
シンボルとして定義した場合、関数呼び出し時に指定する必要がある。
一方、共有変数の場合、呼び出し時の指定は不要。