機械学習・自然言語処理の勉強メモ

学んだことのメモやまとめ

TheanoのSCANについて

SCANとは

theanoにおける繰り返し処理に対応する関数。

基本的な理解のために下記ページを参考にさせて頂いた。
sinhrks.hatenablog.com

fnの引数は、

fn に渡す引数は (最大で) 以下の 3 つになる。それぞれ、対応する引数がない場合は省略される (fnに渡される引数の数自体が変わる)。

  1. シーケンスの要素 (sequences が指定されている場合)
  2. 直前の繰り返し処理の結果 (outputs_info が指定されている場合)
  3. シーケンスでない引数 = 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]

違いは、関数呼び出し時に値を渡すかどうか
シンボルとして定義した場合、関数呼び出し時に指定する必要がある。
一方、共有変数の場合、呼び出し時の指定は不要。